我正在尝试编写一个程序,向用户返回用于弥补用户输入的美元金额(美国货币)的最小美国硬币数量。
我的问题:当程序达到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的角钱?
答案 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并且比较失败。
有两个解决方案:
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)
您的问题都源于使用二进制浮点,即float
或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
。