2012-12-28 69 views
2

我正在尝试编写一个程序,向用户返回用于输入用户输入的美元金额(美元)的最小金额美国硬币。C似乎有效0.1比较被跳过

我的问题:当程序达到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的数字?

+2

您应该阅读 - http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html不应该使用浮点数进行货币计算,而应该使用固定点(并且一些可接受的四舍五入)。 – 2012-12-28 20:35:22

+0

@VladLazarenko喜欢它。感谢您的链接。 –

回答

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); 
} 
//... 

感谢弗拉德此链接:What Every Computer Scientist Should Know About Floating-Point Arithmetic

+1

这个答案到处都是。浮点与固定点在这里不是问题。真正的问题是二进制与十进制。 –

+0

@DavidHeffernan嗯,你如何建议我回答不同? –

+0

正如我所说。解决方案是使用十进制数字系统而不是二进制。 –

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,它解释了所有的细节,所以你必须浮点数有更深的了解。

+0

Downvoter:原因? – 2012-12-29 06:17:28

+0

尽管你有两个upvotes。 –

2

What every programmer should know about floating point numbers

欢迎来到奇妙的浮点数字世界。 0.1和0.01不完全表示(类似于1/3没有最终的小数表示形式)。我的猜测是,第一个例子中的0.10实际上是0.099999999998或类似的数字。因此0.099999999998 < 0.10并且比较失败。

有2个解决方案,这一点:

  • 不要使用浮点数,但使用固定点表示(美分数)
  • 使用小量比较。即if (x - 0.005 >= 10) { ...

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

示例:在下面的文本中,我将使用小数3 s.f.数字和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 - 我们数过一枚硬币。

+0

同样,只要您使用十进制而不是二进制,浮点数就可以精确地表示0.1。 –

+0

@David:你知道有多少编译器支持者,甚至默认支持它?我很早以前就知道计算机中的资金是作为整数处理的 - 如果需要的话,可以是大数,也可以是一对“主货币,小数部分”整数,但整数都是一样的。 –

+0

@MatsPetersson大量的编译器支持十进制浮点。这是一个误解很大的领域。人们认为问题是浮点,实际上问题是使用基数2而不是基数10. –

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的隐式乘法移位。

+0

哇,为什么你和H2CO3的答案被降低了? –

+0

@JonathonReinhart嗯,我当然想知道! –