為什麼說浮點數缺乏精確性? python中浮點數運算問題
python中如圖的運算為什麼結果接近零而不為零 是什麼原因造成的? 浮點數缺乏精確性應該怎麼解釋? 為什麼前兩個運算式沒出現問題 最後一個出現了問題? 請教
請將十進位數字0.1轉換成二進位浮點數。
這是因為小數以二進位形式表示時的有窮性導致的。(下面的說法,不完全準確,只是幫助理解)
以下是之前我在另一個地方對一個類似問題的解答,因為內容差不多,就直接搬過來了(本人略懶,希望有所幫助):
#################################此處開始####################################
我們知道,將一個小數轉化為二進位表示的方式是,不斷的乘2,取其中的整數部分。例如:
+----------------------------------------------------------------------------------------------------------------------------------+(1) 0.625*2 = 1.25, 整數部分為1,小數部分為0.25 (2) 0.25 * 2 = 0.5 , 整數部分為0,小數部分為0.5 (3) 0.5 * 2 = 1 , 整數部分為1,小數部分為0 +----------------------------------------------------------------------------------------------------------------------------------+所以0.625的二進位表示就是0.101。然而有些小數,例如0.4,並不能夠精確的轉化為二進位表示,用上面的這種方法計算:
+----------------------------------------------------------------------------------------------------------------------------------+
(1) 0.4*2=0.8 整數部分為0,小數部分為0.8 (2) 0.8*2=1.6 整數部分為1,小數部分為0.6 (3) 0.6*2=1.2 整數部分為1,小數部分為0.2 (4) 0.2*2=0.4 整數部分為0,小數部分為0.4(5) 0.4*2=0.8 整數部分為0,小數部分為0.8
(6) 0.8*2=1.6 整數部分為1,小數部分為0.6 (7) 0.6*2=1.2 整數部分為1,小數部分為0.2 ……+----------------------------------------------------------------------------------------------------------------------------------+
所以0.4轉化為二進位,應該是0.0110... 這樣一個無限循環小數。計算機的內存、cpu寄存器等等這些硬體單元都是有限的,只能表示有限位數的二進位位,因此存儲的二進位小數就會和實際轉換而成的二進位數有一定的誤差。(你可以試著將0.3轉化為二進位表示,也將出現一個循環小數。)
實際上,大多數情況下,小數在計算機中是以一種類似科學計數法的形式表示的,具體的可以參考一下其他的資料。但即便如此,仍然存在誤差。
所以在python中不建議直接將兩個浮點數進行大小比較,或者做精確的計算,往往會得到意想不到的結果。當然,如果非要用,可以參考decimal模塊的相關內容。這不是Python的問題,而是實數的無限精度跟計算機的有限內存之間的矛盾。
舉個例子,假如說我只能使用整數(即只精確到個位,計算機內的浮點數也只有有限精度,以C語言中的雙精度浮點數double為例,精度為52個二進位位),要表示任意實數(無限精度)的時候我就只能通過舍入(rounding)來近似表示。
比如1.2我會表示成1,2.4表示成2,3.6表示成4.所以呢?在算1.2 - 1.2的時候,由於計算機表示的問題,我算的實際上是1 - 1,結果是0,碰巧蒙對了;
在算1.2 + 1.2 - 2.4的時候,由於計算機表示的問題,我算的實際上是1 + 1 - 2,結果是0,再次蒙對了;但是在算1.2 + 1.2 + 1.2 - 3.6的時候,由於計算機表示的問題,我算的實際上是1 + 1 + 1 - 4,結果是-1,運氣沒那麼好啦!這裡的1.2, 2.4, 3.6就相當於你問題里的0.1, 0.2和0.3,1, 2, 4則是真正在計算機內部進行運算的數值,我說清楚了嗎?
其他請看IEEE 754浮點數標準,比如CSAPP第二章啥的(雖然估計你沒興趣看)。另:不僅僅是浮點數的在計算機內部的表示有誤差,運算本身也可能會有誤差。比如整數2可以在計算機內準確表示,但是要算根號2就有誤差了;再比如兩個浮點數相除,本來兩個數都是精確表示的,但除的結果精度卻超出了計算機內實數的表示範圍,然後就有誤差了。解釋下 0.1+0.1-0.2=0 具體是怎麼蒙對的,0.1+0.1+0.1-0.3 怎麼就蒙不對了。
首先在 CPython 中浮點數總是64位的,這裡為了書寫方便,假設為32位的IEEE 754浮點數,計算的道理都一樣,於是第一位是符號位,後面8位是階碼,最後23位是尾數。
0.1換算成二進位:
0.000110011001100110011001100110011001100110011001100110011001...
因為尾數只能有23位所以需要修約,IEEE 754定義的修約的規則可以有四種:取最靠近的(對於中間數0.5,IEEE 754默認修約成偶數,叫做 round half to even,譬如23.5和24.5都取為24),向上取整,向下取整,向靠近0取整。默認的取整規則是取就近的,這個很重要,直接決定了什麼數能蒙對,什麼數蒙不對。
0.1再換成以2為基數的冪數,尾數只取23位的話:
注意最後1100被round成了1101,因為第24位是一個1(於是進位,第23位的0變成1)。因為階碼有-127的移位(單精度),即使真正的2的指數加上127才是存儲階碼,所以階碼應該為
01111111(127)-00000100(4) = 01111011
再加上尾數和符號位,整個32位(最左端為符號位)就為:
00111101110011001100110011001101,即3d cc cc cd
Intel的CPU都是little endian的,我們假設這裡的CPU是Intel,所以在進程的虛擬內存中0.1被表示為cd cc cc 3d。
再看0.2,0.2換成位二進位:
0.001100110011001100110011001100110011001100110011001100110011...
因為0.2=0.1*2,所以就是把0.1向左移一位的結果,他和0.1不出意料地具有相同的尾數,只是階碼多了一位:
同理,這裡階碼相當於0.1的階碼加一,於是就是01111011 + 00000001 = 01111100,同理,整個32位(最左端為符號位)就為:
00111110010011001100110011001101,在little endian的機器內存中就是cd cc 4c 3e。
那0.1+0.1時又發生了什麼呢?兩個浮點數相加時,遵循以下步驟:
- 0 操作數檢查
- 比較階碼大小並完成對階
- 尾數求和運算
- 結果規格化(舍入處理)
首先兩個數都不為0(回憶下0是怎麼在浮點數中被表示的),階碼也相等,所以直接對尾數求和。兩個尾數又相等,求和的結果相當於左移1位,結果是11.00110011001100110011010,溢出了,所以再右移1位以規格化(相當於小數點左移一位),階碼加1,右移的時候末尾的0被捨去,最後尾數變為1.10011001100110011001101,階碼是-4+1=-3,和0.2長得一模一樣。
那如果0.2再加上一個0.1呢?這時候為了得出Python解釋器給出的那個結果,我們需要真正使用雙精度浮點數了(11位的階碼和52的尾數)。0.1的尾數和階碼分別為1.1001100110011001100110011001100110011001100110011010和-4,請注意尾數原本以110011001結尾,只不過最後這個1後面又是一個1,並且後面大於0.5,於是110011001加一變為110011010,這就是導致0.2+0.1和0.3不一樣的根本原因。
0.2則具有相同的尾數和-3的階碼,做0.1和0.2的加法時,首先把0.1的尾數變為0.11001100110011001100110011001100110011001100110011010以對階,然後把這個數再和1.1001100110011001100110011001100110011001100110011010相加,結果是
10.01100110011001100110011001100110011001100110011001110,這個數的尾數是53位的,而且又有溢出,所以應該右移一位,然後捨去最後兩位,注意最後兩位剛好是中間數,但是根據IEEE 754的默認rounding scheme,應該向前進位變成偶數,所以結果就是
1.0011001100110011001100110011001100110011001100110100 (10被捨去),同時階碼加一變為-2。
再看0.3是怎麼表示的,它的階碼和尾數分別為1.0011001100110011001100110011001100110011001100110011和-2。注意尾數結尾0011後面接著的本應是剩下0011的循環,小於0.5,所以無法進位,所以導致了0.3和0.2+0.1不一樣,我們把0.3和0.2+0.1擺在一起,看看區別有多大:
0.2+0.1:
1.0011001100110011001100110011001100110011001100110100
0.3:
1.0011001100110011001100110011001100110011001100110011
也就是說只要0.3在最低位加一的話就和0.2+0.1一樣了,而尾數的最低位是第52位,再乘上-2的階碼,就是2的負54次方,這個數剛好就是:5.551115123125783e-17。
計算機智能處理可數集合的運算,但是全體實數是不可數的,所以計算機只能用一些奇怪的方法來擬合他,於是就產生了浮點數。
這個不是python 的問題,所有基於二進位的浮點數都會有這個問題,原因在於大部分浮點數轉換為二進位後都是無限循環小數,而浮點數不可能用無限大的內存來儲存,所以會有舍入的誤差詳細可以看代碼之謎(五)
來個 @vczh 答案的通俗版,比如用 10 個箱子裝100個球,規定每個箱子只能裝一個球,那顯然是裝不下所有球的。類似,double類型是 64bit 的,最多能表達 2^64 個數,實數有多少呢?無數個,所以 double 類型是裝不下所有實數的,只能表達一個近似值。
現象
不論在python2或者python3,輸入 0.1+0.1 得到 0.2,而輸入 0.1+0.1+0.1 得到 0.30000000000000004
python默認的是17位小數的精度,前16位是準確的,第17位開始不準確
同樣,在 C 語言中只要列印的精度位數足夠多,例如用 %1.20lf
來列印,有
0.1+0.1-0.2 =0.00000000000000000000
0.1+0.1+0.1-0.3=0.00000000000000005551
本質
下面的解釋參見
將一個小數轉化為二進位表示的方式是,不斷的乘2,取其中的整數部分。例如對於0.625:
(0) 整部部分為0
(1) 0.625*2 = 1.25, 整數部分為1
,小數部分為0.25
(2) 0.25 * 2 = 0.5 , 整數部分為0
,小數部分為0.5
(3) 0.5 * 2 = 1 , 整數部分為1
,小數部分為0
所以0.625的二進位表示就是0.101
。
然而有些小數,例如0.1,並不能夠精確的轉化為二進位表示,用上面的這種方法計算:
(0) 0.1整部部分為0
(1) 0.1*2=0.2 整數部分為0
,小數部分為0.2
(2) 0.2*2=0.4 整數部分為0
,小數部分為0.4
(3) 0.4*2=0.8 整數部分為0
,小數部分為0.8
(4) 0.8*2=1.6 整數部分為1
,小數部分為0.6
(5) 0.6*2=1.2 整數部分為1
,小數部分為0.2
(6) 0.2*2=0.4 整數部分為0
,小數部分為0.4
(7) 0.4*2=0.8 整數部分為0
,小數部分為0.8
(8) 0.8*2=1.6 整數部分為1
,小數部分為0.6
(9) 0.6*2=1.2 整數部分為1
,小數部分為0.2
(a) 0.2*2=0.4 整數部分為0
,小數部分為0.4
(b) 0.4*2=0.8 整數部分為0
,小數部分為0.8
……
所以0.4轉化為二進位,應該是0.00011001100...
這樣一個無限循環小數。
計算機的內存、cpu寄存器等等這些硬體單元都是有限的,只能表示有限位數的二進位位,因此存儲的二進位小數就會和實際轉換而成的二進位數有一定的誤差。(你可以試著將0.3轉化為二進位表示,也將出現一個循環小數。)
解答
解釋下 0.1+0.1-0.2=0 具體是怎麼蒙對的,0.1+0.1+0.1-0.3 怎麼就蒙不對了。
首先在 CPython 中浮點數總是64位(即雙精度),這裡為了書寫方便,先以32位的IEEE 754浮點數為例,計算的道理都一樣。
- 第一位是符號位,
- 後面8位是階碼,(雙精度為第2-12位,共11位)
- 最後23位是尾數。(雙精度為第13-64位,共52位,算上隱含的1.xxx,共有53位精度)
Floating Point Components 參見IEEE-754
階碼 The Exponent
The exponent field needs to represent both positive and negative exponents. To do this, a bias is added to the actual exponent in order to get the stored exponent.
For IEEE single-precision floats, this value is 127. Thus, an exponent of zero means that 127 is stored in the exponent field. A stored value of 200 indicates an exponent of (200–127), or 73.
For reasons discussed later, exponents of ?127 (all 0s) and +128 (all 1s) are reserved for special numbers.
正常用法是Normalized Real,其他特殊用法參見下表:
上表中 b 即 bias (對於單精度是127,對於雙精度是1023)
By using denormalized numbers, we were able to make the smallest positive float to be 1.0 X 2-149, instead of 1.0 X 2-127, which we would have had if the number had been normalized. 參見文檔
For double precision, the exponent field is 11 bits, and has a bias of 1023.
尾數 The Mantissa
The mantissa, also known as the significand(有效位), represents the precision bits of the number. It is composed of an implicit leading bit 1
(left of the radix point/小數點) and the fraction bits (to the right of the radix point).
修約 Rounding
IEEE 754定義的 Rounding rules 有五種:參見wiki
- Round to nearest, ties to even – rounds to the nearest value; if the number falls midway it is rounded to the nearest value with an even (zero) least significant bit; this is the default for binary floating-point and the recommended default for decimal.
- Round to nearest, ties away from zero – rounds to the nearest value; if the number falls midway it is rounded to the nearest value above (for positive numbers) or below (for negative numbers); this is intended as an option for decimal floating point.
- Round toward 0 – directed rounding towards zero (also known as truncation).
- Round toward +∞ – directed rounding towards positive infinity (also known as rounding up or ceiling).
- Round toward ?∞ – directed rounding towards negative infinity (also known as rounding down or floor).
Example of rounding to integers using the IEEE 754 rules
注意,第一條規則是默認的取整規則,即 『round to the nearest; ties to even』 規則,是 binary floating-point and the recommended default for decimal. 也就是二進位小數 Rounding 時採用的方法。
對於二進位小數,這條規則就是:
當 rounding 至小數點後第 n 位時,要查看第 n 位後面的數字:
- 如果後面的第一個數字是 0,則不論再往後面跟什麼數字,統統捨棄掉。此時是 round down
- 如果後面的第一個數字是 1,且再往後的數字中只要有一個 1,則應該 round up
- 如果後面的第一個數字是 1,且再往後的數字都是0,則修約後第 n 位必須是偶數,即 0
Rounding 例子
To demonstrate those rules in action let』s round some numbers to 2 places after the radix point(保留兩位小數):
0.11001 —rounds down to 0.11, because the digit at the 3-rd place is 0
0.11101 —rounds up to 1.00, because the digit at the 3-rd place is 1 and there are following digits of 1 (5-th place)
0.11100 —rounds to 1.00, apply the 『ties to even』 tie breaker rule and round up because the digit at 3-rd place is 1 and the following digits are all 0"s.
0.10100 —rounds to 0.10, 因為小數點後第2位已經是 0 (即even)了,所以直接捨棄掉後面的數字。
回到正題,先看 0.1
將0.1寫成二進位模樣:
0.000110011001100110011001100110011001100110011001100110011001...
32位/單精度浮點數表示0.1
對於32位浮點數,尾數只能有23位,所以要存儲"0.1",必須要對它進行修約 / Rounding。
0.000110011001100110011001101 或者 1.10011001100110011001101 x 2^-4
去掉整數位的 1,因此得到 23 位的尾數:
10011001100110011001101
注意最後1100被round成了1101,因為第24位是一個1(於是進位,第23位的0變成1)。
階碼為實際指數e + bias(即127),因此對應的階碼為:
-4 + 127 = 123 = 01111011 ( 理解為:127 - 4, 即 01111111 - 00000100 )
再加上尾數和符號位,整個32位(最左端為符號位)就為:
00111101110011001100110011001101,即大端的
3d cc cc cd
Intel的CPU都是little endian/小端的,所以在Intel機的進程虛擬內存中0.1被表示為cd cc cc 3d
。
把一個float轉化為浮點數表示參見
64位/雙精度表示0.1
On a typical machine running Python, there are 53 bits of precision(算上隱含的1
) available for a Python float, so the value stored internally when you enter the decimal number 0.1 is the binary fraction:
0.00011001100110011001100110011001100110011001100110011010 Exponent 階碼:-4 + 1023 = 01111111111 - 100 = 01111111011 即 浮點數 00111111 10111001 10011001 10011001 10011001 10011001 10011001 10011010 即 大端的
3f b9 99 99 99 99 99 9a
which is close to, but not exactly equal to, 0.1. 參見文檔
python表示0.1
It』s easy to forget that the stored value is an approximation to the original decimal fraction, because of the way that floats are displayed at the interpreter prompt. Python only prints a decimal approximation to the true decimal value of the binary approximation stored by the machine. If Python were to print the true decimal value of the binary approximation stored for 0.1, it would have to display
&>&>&> 0.1
0.1000000000000000055511151231257827021181583404541015625 注意:這是個精確值
也就是說,會把 0.1 表示成3f b9 99 99 99 99 99 9a
這個東西/存儲值,而這個東西實際對應的值是精確值0.1000000000000000055511151231257827021181583404541015625
把0.1的精確存儲值列印出來的方法:Decimal(0.1) 參見文檔
Interestingly, there are many different decimal numbers that share the same nearest approximate binary fraction. For example, the numbers:
0.1 0.1000000000000000055511151231257827021181583404541015625 0.10000000000000001
在python2中:
&>&>&> Decimal(0.1)
0.1000000000000000055511151231257827021181583404541015625
&>&>&> Decimal(0.1000000000000000055511151231257827021181583404541015625)
0.1000000000000000055511151231257827021181583404541015625
&>&>&> Decimal(0.10000000000000001)
0.1000000000000000055511151231257827021181583404541015625
&>&>&> struct.pack("&>d", Decimal(0.1))
"x3fxb9x99x99x99x99x99x9a"
實際上列印 "?xb9x99x99x99x99x99x9a",而"?"對應數字3f
&>&>&> struct.pack("&>d", Decimal(0.1000000000000000055511151231257827021181583404541015625))
"x3fxb9x99x99x99x99x99x9a"
&>&>&> struct.pack("&>d", Decimal(0.10000000000000001))
"x3fxb9x99x99x99x99x99x9a"
這三個數都會被存儲為 3f b9 99 99 99 99 99 9a
這個存儲值。
Since all of these decimal values share the same approximation, any one of them could be displayed while still preserving the invariant eval(repr(x)) == x
.
Historically, the Python prompt and built-in repr() function would choose the one with 17 significant digits, 0.10000000000000001.
這是因為,0.1 首先會被存儲為 0.1000000000000000055511151231257827021181583404541015625,而這個數在顯示時會被反過來計算近似為 0.10000000000000001。
Starting with Python 2.7 and Python 3.1, Python (on most systems) is now able to choose the shortest of these and simply display 0.1. - 參考doc
即既然 0.1 和 0.10000000000000001 都能反算回去 3f b9 99 99 99 99 99 9a
這個存儲值,那就挑其中最短的那個來顯示,也就是 0.1
In current versions, Python displays a value based on the shortest decimal fraction that rounds correctly back to the true binary value, resulting simply in 『0.1』.
0.1 + 0.1 和 0.2
先看 0.1 + 0.1
那0.1+0.1時又發生了什麼呢?兩個浮點數相加時,遵循以下步驟:
- 0 operand/操作數檢查,即看看兩個浮點數有沒有是0
- 比較階碼大小並完成對階 / align radix points - 參見doc
- 尾數求和運算 / add (don"t forget the hidden bit
1
) - 結果規格化(舍入處理) / normalize the result (get the "hidden bit" to be a
1
)
首先兩個數都不為0(回憶下0是怎麼在浮點數中被表示的),階碼也相等,所以直接對尾數求和。兩個尾數又相等,求和的結果相當於左移1位:
原來的數:1.1001100110011001100110011001100110011001100110011010 (不要忘記開頭的
1
) 相加得到:11.0011001100110011001100110011001100110011001100110100
溢出了,所以再右移1位以規格化,並修約,階碼加1,最後尾數變為
1.1001100110011001100110011001100110011001100110011010
和 0.1 的尾數完全相同。只是階碼不同。對應浮點數為:
Exponent 階碼 11 位:-3 + 1023 = 01111111111 - 11 = 01111111100 即 浮點數 00111111 11001001 10011001 10011001 10011001 10011001 10011001 10011010 即 大端的
3f c9 99 99 99 99 99 9a
struct.pack("&>d", Decimal(0.1 + 0.1))看到該結果
注意:3f c9 99 99 99 99 99 9a
是 0.1 + 0.1 的運算結果。
再看0.2
0.2換算成位二進位:
0.001100110011001100110011001100110011001100110011001100110011... 階碼為 -3,尾數取52位:1.100110011001100110011001100110011001100110011001101 得到 浮點數 00111111 11001001 10011001 10011001 10011001 10011001 10011001 10011010 即 大端的
3f c9 99 99 99 99 99 9a
struct.pack("&>d", Decimal(0.2))看到該結果
注意:3f c9 99 99 99 99 99 9a
是 0.2 的內存中的存儲。
比較 0.1 + 0.1 的運算結果和 0.2 的存儲值
這個是最關鍵的步驟了:
現在python要把 0.1 + 0.1 的結果,也就是 3f c9 99 99 99 99 99 9a
顯示出來,那麼它對應的準確的數字是:
Decimal(0.1+0.1)
Decimal("0.200000000000000011102230246251565404236316680908203125")
在舊版本的python中(&< 2.7 和 &< 3.1)保留17位小數,顯示的結果會是
python26&>&>&> 0.1 + 0.1
0.20000000000000001
而在新版本的python中,python會發現其實0.2的浮點數也對應3f c9 99 99 99 99 99 9a
,那麼既然0.2和0.20000000000000001對應的存儲值都是3f c9 99 99 99 99 99 9a
,那麼python會顯示最終結果為0.2。
python27&>&>&> 0.1 + 0.1
0.2
0.1 + 0.1 + 0.1 和 0.3
先看0.1 + 0.1 + 0.1
跟著上面的結果,0.1 + 0.1 已經算出來了:
1.1001100110011001100110011001100110011001100110011010 階碼 -3
然後再和 0.1 相加,即 1.1001100110011001100110011001100110011001100110011010 階碼 -4
下面的0.1先對齊階碼再相加 +,最後得到的結果進行修約,注意規則還是 Round to nearest, ties to even
1.10,0110,0110,0110,0110,0110,0110,0110,0110,0110,0110,0110,0110,10 這是0.2,階碼 -3
0.11,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,01 這是0.1,階碼 -3
10.01,1001,1001,1001,1001,1001,1001,1001,1001,1001,1001,1001,1001,11
1.001,1001,1001,1001,1001,1001,1001,1001,1001,1001,1001,1001,1010,0 這是運算結果,階碼 -2
對應浮點數 0 011,1111,1101, 0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0100
對應hex `3f d3 33 33 33 33 33 34`
struct.pack("&>d",Decimal(0.1+0.1+0.1)).encode("hex") 來直接查看(python2)
再看 0.3
我們先把0.3從原理上轉化為二進位小數為:
0.0100110011001100110011001100110011001100110011001100110011001100110011...
包括第一個出現的`1`,保留53位有效數字:
0.010011001100110011001100110011001100110011001100110011
階碼 -2,對應浮點數 0 011,1111,1101, 0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011,0011
對應hex `3f d3 33 33 33 33 33 33`
struct.pack("&>d",Decimal(0.3)).encode("hex") 來直接查看(python2)
比較 0.1 + 0.1 + 0.1 的運算結果和 0.3 的存儲值
這時你會發現 0.1 + 0.1 + 0.1 的運算結果是 3f d3 33 33 33 33 33 34
,和 0.3 的存儲值不同!這時python要把3f d3 33 33 33 33 33 34
顯示出來,即
Decimal(0.1+0.1+0.1)
Decimal("0.3000000000000000444089209850062616169452667236328125")
保留17位小數:
0.30000000000000004
同時:
Decimal(0.30000000000000004)
Decimal("0.3000000000000000444089209850062616169452667236328125")
是可以算回去同一個存儲值的。
struct.pack("&>d",Decimal(0.30000000000000004)).encode("hex") 對應 `3f d3 33 33 33 33 33 34`
因此,不可以把結果顯示為 0.3,否則算不回去:
Decimal(0.3)
Decimal("0.299999999999999988897769753748434595763683319091796875")
struct.pack("&>d",Decimal(0.3)).encode("hex") 對應 `3f d3 33 33 33 33 33 33`
總結
我們把0.3和0.2+0.1擺在一起,看看區別有多大: 0.2+0.1:1.0011001100110011001100110011001100110011001100110100 0.3: 1.0011001100110011001100110011001100110011001100110011 也就是說只要0.3在最低位加一的話就和0.2+0.1一樣了,而尾數的最低位是第52位,再乘上-2的階碼,就是2的負54次方,這個數剛好就是:5.551115123125783e-17。所以有:
&>&>&> 0.1+0.1-0.2
0.0
&>&>&> 0.1+0.1+0.1-0.3
5.551115123125783e-17
&>&>&> 0.1 + 0.1 == 0.2
True
&>&>&> 0.1 + 0.1 + 0.1 == 0.3
False
補充:
round 函數
對應IEEE 754定義的第一種 Rounding 方法 "Round to nearest, ties to even"。
因此,對於2.675,保留 2 位小數,結果應該是2.68。但是實際上:
&>&>&> round(2.675, 2)
2.67
The documentation for the built-in round() function says that it rounds to the nearest value, rounding ties away from zero. Since the decimal fraction 2.675 is exactly halfway between 2.67 and 2.68, you might expect the result here to be (a binary approximation to) 2.68. It』s not, because when the decimal string 2.675 is converted to a binary floating-point number, it』s again replaced with a binary approximation, whose exact value is
2.67499999999999982236431605997495353221893310546875
Since this approximation is slightly closer to 2.67 than to 2.68, it』s rounded down.
精度誤差
The errors in Python float operations are inherited from the floating-point hardware, and on most machines are on the order of no more than 1 part in 2**53 per operation. That』s more than adequate for most tasks, but you do need to keep in mind that it』s not decimal arithmetic, and that every float operation can suffer a new rounding error.
1 part in 2**53 即 1.1102230246251565e-16,即python浮點數有大約 x ± 1e-16 的精度,即小數點前15位一定是準確的,第16位的誤差是 ± 1,第17位就可以不用看了,沒有精度可言。
因此,對於
&>&>&> 0.1+0.1+0.1
0.30000000000000004
最後一位就是第17位,是不精確的。
大白話版的很多人都說了,就是計算機有限的數字表示不了數學中無窮的數。
樓主如果有一定計算機或者數學基礎可以搜一下IEEE754,電腦是如何表示浮點數的一種協議。看懂了那個協議就知道為什麼了~
如果看不懂IEEE754那就需要研究下數學中各種進位~主要是二進位、十進位和他們的關係。9.4. decimal
自傲的人類!有本事你直接喂它2進位數,看看還有沒有這種精度問題。
計算機所有信息的存儲使用二進位表示,像 @vczh 所說,有限的「位」,無法表示無數個數,那麼,小數用二進位表示法,只能表示那些能被寫成 的數。
0.1 的二進位表示為 0.000110011[0011]...,因為是無限循環的,無法用有限的位表示,所以計算機系統會進行舍入,以求用最接近的值來表示,這裡涉及到不同的舍入方式,具體請見深入理解計算機系統,本答案也是參考該書。推薦閱讀:
※Apache伺服器上同時運行php的網站和django的網站,該如何配置Apache和Django的URL?
※Linux 和 系統安全的前景?
※在 Python 中如何判斷輸入數字是實數(整型數字或者浮點型數字)?
※作為一個Python程序員,電腦上應該具備哪些軟體?
※PyQt5如何實現窗口關閉淡出效果?
TAG:Python |