?閱讀本文大約需要8分鐘...
問題
在計(jì)算機(jī)的世界里,可能有很多常人無法理解的事情劣挫。比如 0.1 + 0.2 = ?东帅。來压固,告訴我你的答案。
有的朋友看到這就迫不及待的說靠闭,這么簡單的問題帐我,很明顯等于 0.3 啊,小學(xué)生都會(huì)算的好伐阎毅。你這是在侮辱我的智商焚刚?
好吧,我來告訴你一個(gè)打臉的事實(shí)扇调,0.1 + 0.2 還真不等于 0.3 。先別急著反駁我抢肛。
打開你的任意一個(gè)瀏覽器(我用chrome做演示)狼钮,F(xiàn)12打開console控制臺(tái),輸入 console.log(0.1 + 0.2) 捡絮。如果你操作正確的話熬芜,你會(huì)看到以下的結(jié)果。
是不是感覺匪夷所思福稳,what涎拉?為什么結(jié)果是0.30000000000000004,這是神魔鬼的圆? 難道鼓拧,我這么多年學(xué)習(xí)的數(shù)學(xué)知識(shí),老師教的都是錯(cuò)的越妈?
別著急季俩。其實(shí),你的老師教的沒錯(cuò)梅掠,在我們的世界中酌住,0.1 + 0.2 確實(shí)是等于 0.3 的店归。但是,在計(jì)算機(jī)中酪我,可就不是這么一回事了消痛。待我娓娓道來。
因?yàn)槎伎蓿覀冊谟?jì)算數(shù)學(xué)問題的時(shí)候秩伞,用的是十進(jìn)制,計(jì)算出來結(jié)果是0.3沒問題质涛。但是稠歉,在計(jì)算機(jī)中用的是二進(jìn)制,都是由0和1來組成汇陆。這就不得不提一下怒炸,十進(jìn)制轉(zhuǎn)換二進(jìn)制了。
二進(jìn)制轉(zhuǎn)換
十進(jìn)制小數(shù)轉(zhuǎn)換二進(jìn)制的步驟:(以10.25為例)
1.先轉(zhuǎn)換整數(shù)部分毡代,除2直到商為0阅羹,倒數(shù)取余。
10/2 ... 商5...余數(shù)0
5/2 ...商2...余數(shù)1
2/2 ...商1...余數(shù)0
1/2 ...商0...余數(shù)1
倒數(shù)取余教寂,就是1010
2.再轉(zhuǎn)換小數(shù)部分捏鱼,乘2取整,直到小數(shù)部分為0.
0.25*2 ... 0.50 ...整數(shù)0
0.50*2 ... 1.0 ...整數(shù)1
小數(shù)部分為0酪耕,結(jié)束导梆,即為01
因此10.25(10)轉(zhuǎn)換成二進(jìn)制,結(jié)果就是 1010.01(2)
聰明的你迂烁,類比以上方法看尼,應(yīng)該可以動(dòng)手去算一下十進(jìn)制0.1轉(zhuǎn)成二進(jìn)制是多少了。
0.1*2 ... 0.2 ...整數(shù)0
0.2*2 ... 0.4 ...整數(shù)0
0.4*2 ... 0.8 ...整數(shù)0
0.8*2 ... 1.6 ...整數(shù)1
0.6*2 ... 1.2 ...整數(shù)1
0.2*2 ... 0.4 ...整數(shù)0
等等盟步,怎么感覺進(jìn)入死循環(huán)了藏斩,小數(shù)部分乘以2,一直乘不到小數(shù)部分為0
就像十進(jìn)制中1/3却盘,結(jié)果是0.3(3...)這樣的問題一樣狰域,0.1轉(zhuǎn)成二進(jìn)制時(shí)也會(huì)存在精度問題,我們需要進(jìn)行取舍黄橘。
我們看一下0.1在計(jì)算機(jī)中是怎么存儲(chǔ)的兆览。對此,需要了解一下浮點(diǎn)數(shù)的概念旬陡。
浮點(diǎn)數(shù)
浮點(diǎn)數(shù)拓颓,顧名思義,小數(shù)點(diǎn)是浮動(dòng)的數(shù)描孟。千萬不要以為浮點(diǎn)數(shù)就是小數(shù)驶睦。因?yàn)榕樽螅趈s中是沒有整數(shù)和小數(shù)的概念的,其實(shí)整數(shù)也是以浮點(diǎn)數(shù)的形式表示的场航,只是小數(shù)部分為0而已缠导。
浮點(diǎn)數(shù)簡單理解,就是類似于我們十進(jìn)制中的科學(xué)計(jì)數(shù)法溉痢。在計(jì)算機(jī)中一般遵循IEEE 754標(biāo)準(zhǔn)僻造。格式如下:
(-1)^S * M * 2^E
1. S表示符號位,當(dāng)S=0時(shí)孩饼,為正數(shù)髓削;當(dāng)S=1時(shí),為負(fù)數(shù)镀娶。
2. M表示有效數(shù)字(尾數(shù))立膛,大于等于1,小于2梯码。
3. E為指數(shù)(也叫階碼)宝泵。
因此,上邊的10.25(二進(jìn)制1010.01)按照此格式表示即為 1.01001 * 2^3
對于32位浮點(diǎn)數(shù)來說轩娶,符號位占一位儿奶,指數(shù)位占8位,尾數(shù)占23位
對于64位浮點(diǎn)數(shù)來說鳄抒,符號位占一位闯捎,指數(shù)位占11位,尾數(shù)占52位
IEEE 754標(biāo)準(zhǔn)
注意:IEEE 754標(biāo)準(zhǔn)規(guī)定许溅,在保存尾數(shù)M時(shí)隙券,第一位默認(rèn)是1,因此可以被舍去闹司,只存儲(chǔ)后邊的部分。例如沐飘,1.01001保存的時(shí)候游桩,只保存01001,等到用的時(shí)候再把1加上去耐朴。這樣借卧,就可以節(jié)省一個(gè)位的有效數(shù)字。
指數(shù)E在存儲(chǔ)的時(shí)候也有些特殊筛峭。若為32位铐刘,指數(shù)占8位,則可表示的大小范圍為0-255 影晓。如為64位镰吵,指數(shù)占11位檩禾,范圍為0-2047 。但是疤祭,指數(shù)是有正有負(fù)的盼产,因此實(shí)際值需要在此基礎(chǔ)上減去一個(gè)中間數(shù)。對于32位勺馆,中間數(shù)為127戏售,對于64位,中間數(shù)為1023 草穆。
還是以1.01001 * 2^3 為例灌灾,若為32位浮點(diǎn)數(shù),則需要保存成 3+ 127 = 130悲柱,即二進(jìn)制的10000010锋喜,若為64位浮點(diǎn)數(shù),則保存成 3+ 1023 = 1026 诗祸,即二進(jìn)制的10000000010跑芳。
計(jì)算步驟
好了。巴拉巴拉了這么多直颅。終于博个,要進(jìn)入我們今天的正題了。
我們看一下 0.1 在計(jì)算機(jī)中是怎么用 IEEE 754標(biāo)準(zhǔn)存儲(chǔ)的功偿。
十進(jìn)制0.1轉(zhuǎn)為二進(jìn)制為0.0001100110011(0011循環(huán))盆佣,即 1.100110011(0011)*2^-4,因此符號位為0械荷,尾數(shù)1.100110011(0011)共耍,階碼為 -4,實(shí)際存儲(chǔ)為 -4+1023 = 1019 的二進(jìn)制 1111111011
0 01111111011 1001100110011001100110011001100110011001100110011010
S E指數(shù) M尾數(shù)
十進(jìn)制0.2轉(zhuǎn)為二進(jìn)制為0.001100110011(0011循環(huán))吨瞎,即 1.100110011(0011)*2^-3 痹兜,存儲(chǔ)時(shí),符號位為0颤诀,尾數(shù) 1.100110011(0011)字旭,階碼為-3,實(shí)際存儲(chǔ)為 -3+1023 = 1020 的二進(jìn)制 1111111100崖叫。
0 01111111100 1001100110011001100110011001100110011001100110011010
S E指數(shù) M尾數(shù)
接下來遗淳,計(jì)算 0.1 + 0.2 。
浮點(diǎn)數(shù)進(jìn)行計(jì)算時(shí)心傀,需要對階屈暗。即把兩個(gè)數(shù)的階碼設(shè)置為一樣的值,然后再計(jì)算尾數(shù)部分。其實(shí)對階很好理解养叛,就和我們十進(jìn)制科學(xué)記數(shù)法加法一個(gè)道理种呐,先把指數(shù)部分化成一樣,再計(jì)算尾數(shù)一铅。
另外陕贮,需要注意一下,對階時(shí)需要小階對大階潘飘。因?yàn)榘怪@樣相當(dāng)于,小階指數(shù)乘以倍數(shù)卜录,尾數(shù)部分相對應(yīng)的除以倍數(shù)戈擒,在二進(jìn)制中即右移倍數(shù)位。這樣艰毒,不會(huì)影響到尾數(shù)的高位筐高,只會(huì)移出低位,損失相對較少的精度丑瞧。
因此柑土,0.1的階碼為 -4 , 需要對階為 0.2的階碼 -3 。尾數(shù)部分整體右移一位绊汹。
原來的0.1
0 01111111011 1001100110011001100110011001100110011001100110011010
對階后的0.1
0 01111111100 1100110011001100110011001100110011001100110011001101
然后進(jìn)行尾數(shù)部分相加
0 01111111100 1100110011001100110011001100110011001100110011001101
+ 0 01111111100 1001100110011001100110011001100110011001100110011010
= 0 01111111100 10110011001100110011001100110011001100110011001100111
可以看到稽屏,產(chǎn)生了進(jìn)位。因此西乖,階碼需要 +1狐榔,即為 -2,尾數(shù)部分進(jìn)行低位四舍五入處理获雕。因尾數(shù)最低位為1薄腻,需要進(jìn)位。所以存儲(chǔ)為:
0 1111111101 1011001100110011001100110011001100110011001100110100
最后把二進(jìn)制轉(zhuǎn)換為十進(jìn)制届案,
二進(jìn)制為:
1.1011001100110011001100110011001100110011001100110100 * 2^-2
轉(zhuǎn)為十進(jìn)制為:
2^-2 * (1*2^0 + 1*2^-1 + 0 + 1*2^-3 + 1*2^-4 + ...)
最終結(jié)果為:
0.3000000000000000444089209850062616169452667236328125
因?yàn)榫葐栴}庵楷,只取到:
0.30000000000000004
問題總結(jié)
1.在十進(jìn)制轉(zhuǎn)換為二進(jìn)制的過程中,會(huì)產(chǎn)生精度的損失楣颠。
2.二進(jìn)制浮點(diǎn)數(shù)進(jìn)行對階運(yùn)算時(shí)嫁乘,也會(huì)產(chǎn)生精度的損失。
因此球碉,最終結(jié)果才產(chǎn)生了偏差。
看完的小伙伴仓蛆,現(xiàn)在應(yīng)該能理解睁冬,為什么0.1 + 0.2 ≠ 0.3 這個(gè)問題了吧。