C看似有效0.1比较被跳过

时间:2012-12-28 20:30:43

标签: c floating-point

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

我的问题:当程序达到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 个答案:

答案 0 :(得分:4)

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

这就是为什么你不应该使用浮点值(double)作为货币。相反,您应该使用类似C#的decimal数字,这些数字不依赖于二进制浮点值。这是an implementation in 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提供此链接:What Every Computer Scientist Should Know About Floating-Point Arithmetic

答案 1 :(得分:2)

What every programmer should know about floating point numbers

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

有两个解决方案:

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

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

示例:在下面的文字中,我将使用小数3 s.f.数字以及简单的1/3和1/999(3指外星人的硬币计算外包给地球)。

您的代码大致是:

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 - 我们过多计算一枚硬币。

答案 2 :(得分:1)

因为浮点数不准确。

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

这就是为什么你不应该依赖==!=运算符进行比较的原因;你应该检查两个数字是否足够接近:

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

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

答案 3 :(得分:0)

您的问题都源于使用二进制浮点,即floatdouble。你问题中的许多数字都不能完全用二进制表示。二进制浮点中可精确表示的数字的形式为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