2012-12-28 70 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嗯,我當然想知道! –