2

我正在尝试编写一个程序,该程序向用户返回用于构成用户输入的美元金额(美国货币)的最小数量的美国硬币。

我的问题:当程序达到 0.1 时,程序不会减去一角钱,而是减去一个镍和 5 个便士。这只发生在大于 1.85 的数字上。当小于 1.85 时,成功减去一角钱。

这是我的代码:

 while (Money >= 0.25){
   Money = Money - 0.25;
   Coins = Coins + 1;
   printf ("Current money: %f \n", Money);
 }
while (Money >= 0.1) { 
  Money = Money - 0.1;
  Coins = Coins + 1;
  printf ("Current money: %f \n", Money);
}
while (Money >= 0.05) {
 Money = Money - 0.05;
 Coins = Coins + 1;
 printf ("Current money: %f \n", Money);
}
while (Money >= 0.01) {
 Money = Money - 0.01;
 Coins = Coins + 1;
 printf ("Current money: %f \n", Money);
}

这是我使用数字 2.1 时的输出:

2.1
Current money: 1.850000 
Current money: 1.600000 
Current money: 1.350000 
Current money: 1.100000 
Current money: 0.850000 
Current money: 0.600000 
Current money: 0.350000 
Current money: 0.100000 
Current money: 0.050000 
Current money: 0.040000 
Current money: 0.030000 
Current money: 0.020000 
Current money: 0.010000 
Used 13 

这是我使用数字 1.85 时的输出:

1.85
Current money: 1.600000 
Current money: 1.350000 
Current money: 1.100000 
Current money: 0.850000 
Current money: 0.600000 
Current money: 0.350000 
Current money: 0.100000 
Current money: 0.000000 
 Used 8 

为什么会这样?为什么大于 1.85 的数字不使用角钱?

4

4 回答 4

4

因为 0.1 并不一定意味着 0.10000000000000000...。您只能看到数字printf显示为%f. 如果您逐步执行此操作并查看调试器中的值,您可能会看到在 2.1 循环结束时,该值类似于 0.099999999,即 < 0.10。

这就是为什么您不应该使用浮点值 ( double) 作为货币。相反,您应该使用类似 C# 的decimal数字,它不依赖于二进制浮点值。这是C++ 中的一个实现

在您的情况下,只需保留整数美分(并了解您需要除以 100 才能获得美元)将使您的计算准确。

int money = 281;  // $2.81

while (money >= 25) {    // Quarter
   money -= 25;
   coins++;
   printf("Current money: $%d.%d \n", money/100, money%100);
}
//...

感谢 Vlad 提供此链接:每个计算机科学家都应该知道的关于浮点运算的知识

于 2012-12-28T20:35:14.837 回答
2

每个程序员都应该了解浮点数

欢迎来到浮点数的美妙世界。0.1 和 0.01 没有精确表示(类似于 1/3 没有最终的十进制表示)。我的猜测是第一个示例中的 0.10 实际上是 0.099999999998 或类似数字。因此 0.099999999998 < 0.10 并且比较失败。

有2个解决方案:

  • 不要使用浮点数,而是使用定点表示(美分数)
  • 使用 epsilon 比较。IEif (x - 0.005 >= 10) { ...

我个人会推荐第一个解决方案。

示例:在下面的文本中,为了简单起见,我将使用十进制 3 sf 数字和 1/3 和 1/999(三指外星人的硬币计算外包给地球)。

你的代码大致是:

while (money >= 1/3) {
    money -= 1/3;
    coins++;
}
while (money >= 1/999) {
    money -= 1/999;
    coins++;
}

编译后看起来像:

while (money >= 0.333) {
    money -= 0.333;
    coins++;
}
while (money >= 0.001) {
    money -= 0.001;
    coins++;
}

让我们输入一个大数字,比如 10。然后在初始运行之后,我们有:

money = 10 - 0.333 = 9.667 ≈ 9.67
money = 9.67 - 0.333 = 9.334 ≈ 9.33
money = 9.00 - 0.333 = 8.667 ≈ 8.66
...
money = 1.00 - 0.333 = 0.667 ≈ 0.667
money = 0.667 - 0.333 = 0.333 ≈ 0.334
money = 0.334 - 0.333 = 0.001 ≈ 0.001
// Next loop
money = 0.001 - 0.001 = 0.000

Ups - 我们计算了一枚硬币太多。

于 2012-12-28T20:37:16.777 回答
1

因为浮点数并不精确。

通常,这类数字使用IEEE-754浮点格式表示,它是一种二进制编码。但并非每个分数/有理数/实数都可以用二进制表示,因此您的 1.85 实际上可能是 1.8499274 或 1.85010374 或其他。

这就是为什么你永远不应该依赖==and!=运算符的比较;相反,您应该检查两个数字是否足够接近:

const float eps = 1.0e-5;
if (abs(number1 - number2) < eps) {
    // let's pretend they're equal
} else {
    // they aren't equal
}

为了进一步参考,我建议您阅读这篇论文,它详细解释了所有内容,以便您对浮点数有更深入的了解。

于 2012-12-28T20:35:29.430 回答
0

您的问题都源于使用二进制浮点,即floator double。您问题中的许多数字不能完全用二进制表示。二进制浮点中可精确表示的数字的形式为 k/2^n,其中 k 是整数,n 是非负整数。

因此,这些值不能完全表示:

0.1
0.05
0.01
2.1
1.85
1.6
1.35
1.1
0.85
0.6
0.35

等等。是不是很吓人?!

由于您使用的数字无法准确表示,因此您可能会出现舍入误差。这就是为什么你的程序没有按照你想要的方式运行。

浮点运算的标准参考是:What Every Computer Scientist Should Know About Floating-Point Arithmetic

这项出色的工作涵盖了基础级别的浮点运算。它不限于二进制浮点。它还考虑使用十进制系统的表示。这是解决问题的真正关键。为了使您的算术准确,您需要使用十进制而不是二进制表示。如果你这样做,那么你可以准确地表示货币价值。

遗憾的是,常见的 C 实现不带有十进制浮点或定点数据类型。所以你需要自己动手,或者找第三方库。

作为一个非常简单的解决方案,假设您只需要表示最多 2 个小数位,您可以使用定点十进制表示。将您的值存储在int变量中并假设100.

于 2012-12-28T21:36:26.037 回答