2013-05-29 113 views
2

我知道浮點在大多數編程語言中都不是100%準確的,但我剛纔遇到了一個奇怪的問題。我仍然在學習Python,因此製作了一個簡單的程序,用於計算儘可能少的硬幣數量。然而,當它達到0.02時,它似乎無法給予2便士硬幣,而是將其分解爲2便士硬幣。代碼段是這樣的:浮點問題

.... 
elif amountLeft/0.02 >= 1: 
    changeGiven.append("2p") 
    amountLeft -= 0.02 
else: 
    changeGiven.append("1p") 
    amountLeft -= 0.01 

我在http://www.pythontutor.com看了一下,那裏的amountLeft明確0.02對任何事物的最後一次迭代,將轉降低到。當我檢查print 0.02/0.02 >= 1時,我按預期返回True

我在這裏想念什麼明顯的東西?

+0

如果您願意投入一些時間和精力得到什麼是怎麼回事有了更深的瞭解,我建議你閱讀[什麼每一臺計算機科學家應該瞭解浮點運算](http://docs.oracle.com。com/cd/E19957-01/806-3568/ncg_goldberg.html) –

回答

6

既然您知道浮點數不是100%準確的,那麼您應該不會驚訝地發現0.02不能完全表示爲Python浮點數。事實上,它是存儲的東西略高於0.02,你可以看到,如果你有非常高的精度打印出值:

>>> print '{0:.32f}'.format(0.02) 
0.02000000000000000041633363423443 

當你不斷地減去你的變量0.02這個小錯誤積聚。下面是一個例子,從1.0開始顯示出什麼我談論:

>>> x = 1.0 
>>> for i in range(49): 
...  x -= 0.02 
... 
>>> x 
0.019999999999999383 
>>> x/0.02 >= 1 
False 

爲了避免這種舍入誤差,使用decimal模塊,而不是花車:

>>> from decimal import Decimal 
>>> x = Decimal('1.0') 
>>> for i in range(49): 
...  x -= Decimal('0.02') 
... 
>>> x 
Decimal('0.02') 
>>> x/Decimal('0.02') >= 1 
True 

另外,通過乘以所有你的價值觀100,所以你減去整數2而不是浮點數0.02,這也可以避免舍入誤差。

+1

我沒有意識到減法也會導致浮點問題。這讓我想起來了。謝謝。在python中使用 – ydaetskcoR

+0

(十進制(1.1) - 十進制(0.7) - 十進制(1.2)+十進制(0.8))> 0。十進制比浮點更準確,但仍然不正確 – andrei

-2

歡迎來到浮點。 0.02/0.02不一定等於1.我最好的建議是總是使用整數算術。我整天都在做科學編程,我還沒有發現需要浮點的問題。讓我重申一下,爲了清楚起見:任何你認爲你可以用浮點算法解決的問題可以用整數算法更有效地解決。我能想到的唯一例外是當你需要使用只接受浮點輸入的庫時。

如果你堅持使用浮點數,你需要使用舍入函數。

---------------------或者FJ提示的小數庫。

+0

謝謝,我想我會把它放大。唯一引起我注意的是,如果你簡單地做'0.02/0.02'= 1',那麼它返回'真'。我沒有在'amountLeft'中通過除法(只有減法)得到'0.02',所以它應該完全是'0.02',而不是一些長整數。 – ydaetskcoR

+3

我認爲這有些誇張。你將如何處理像平方根那樣的東西,或者甚至僅僅是7分呢? – BrenBarn

+0

對不起,但對於任何有限(非零)IEEE 754浮點數,「x/x」都等於「1.0」。你可以自由地不使用它們,但是認爲'x/x'計算爲非其他的東西是不這樣做的一個可怕原因。如果你沒有像我這樣的整數平方根計算器,那麼 –

3

首先,amountLeft/0.02 >= 1大部分與amountLeft >= 0.02(假設amountLeft不是負數)相同,並且有點簡單。

使用整數運算(與便士工作直接,會給你確切的結果,但你必須手動添加.顯示結果時:

from Decimal import decimal 

amountLeft = round(amountLeft*100) 

.... 
elif amountLeft >= 2: 
    changeGiven.append("2p") 
    amountLeft -= 2 
else: 
    changeGiven.append("1p") 
    amountLeft -= 1 

如果你真的需要一個程序來處理在小數具體的方式,使用十進制模塊假設輸入是浮點:

# Assume amountLeft contains a floating point number (e.g. 1.99) 
# 2 is the number of decimals you need, the more, the slower. Should be 
# at most 15, which is the machine precision of Python floating point. 

amountLeft = round(Decimal(amountLeft),2) 

.... 
# Quotes are important; else, you'll preserve the same errors 
# produced by the floating point representation. 
elif amountLeft >= Decimal("0.02"): 
    changeGiven.append("2p") 
    amountLeft -= Decimal("0.02") 
else: 
    changeGiven.append("1p") 
    amountLeft -= Decimal("0.01")