在知乎上看到有人提出類似的問題橄教,我總結(jié)表述一下琳拭,做一下筆記:
一步步解釋:
存儲問題
首先要了解刻伊,從根本上講露戒,計算機只識別二進制數(shù),無論我們使用何種編程語言捶箱,在何種編譯環(huán)境下工作智什,都需要將程序編譯成二進制機器碼才能讓計算機執(zhí)行
那么這樣會導(dǎo)致什么問題呢?會導(dǎo)致小數(shù)的十進制轉(zhuǎn)為二進制出現(xiàn)誤差丁屎,這就是所謂的精度缺失。
為什么會出現(xiàn)誤差?這就涉及到了計算機原理的問題:
我們知道數(shù)字在計算機中是以二進制存儲的。對于整數(shù)來說叫搁,只要不超過整數(shù)的表示范圍,一定都可以表示成二進制的形式惨奕,比如8是1000梨撞,88是1011000雹洗,可是小數(shù)小數(shù)部分就沒有那么幸運了,根據(jù)二進制小數(shù)轉(zhuǎn)換成十進制的規(guī)則(把該小數(shù)不斷乘2卧波,再取所得的整數(shù)部份时肿,直至沒有小數(shù)或足夠長度為止)(二進制整數(shù)換成十進制規(guī)則 除二取余倒記發(fā))。
由于二進制中所有的小數(shù)存儲的位數(shù)是有限,因此我們得知“任何十進制整數(shù)都可以精確轉(zhuǎn)換成一個二進制整數(shù),但任何十進制小數(shù)卻不一定能精確轉(zhuǎn)換成一個二進制小數(shù),只要轉(zhuǎn)換過程中乘積的小數(shù)部分滿足所需精度即可”。比如對于0.1來說就不能精確轉(zhuǎn)換為一個二進制小數(shù),在16位小數(shù)的限制條件下遥昧,離它最近的二進制小數(shù)是0.0001100110011覆醇,也就是十進制的0.0999755859375鞋仍。所以雖然你寫程序的時候?qū)懙氖?.1開始這個數(shù)存儲到b這個float變量里的時候就變成了0.0001100110011常摧,也就是0.0999755859375,因此你輸出它的時候就會出現(xiàn)精度誤差了威创,這種誤差是不可避免的。
這里我有個疑問肚豺,為什么我們不把小數(shù)也當做整數(shù)存儲呢溃斋?我懷疑是因為存儲方式的問題,然后我找到了原碼吸申、反碼梗劫、補碼的文章享甸,然后我又想到為什么要用補碼?然后找到了加法器梳侨,然后就想為什么只有加法器蛉威?到這感覺有點不清楚了,我要好好找一下走哺,有情況再回來更新蚯嫌。
再來,根據(jù)IEEE754(二進制浮點數(shù)算術(shù)標準) 來說:
V = (-1)^s × 2^E × M
”铩(1)(-1)^s表示符號位择示,當s=0,V為正數(shù)彼哼;當s=1对妄,V為負數(shù)湘今。(2)2^E表示指數(shù)位敢朱。
(3)M表示有效數(shù)字,大于等于1摩瞎,小于2拴签。
IEEE 754規(guī)定,有四種精度的浮點數(shù):單精確度(32位)旗们、雙精確度(64位)蚓哩、延伸單精確度(43比特以上,很少使用)與延伸雙精確度(79比特以上上渴,通常以80位實現(xiàn))岸梨。
對于32位的浮點數(shù),最高的1位是符號位s稠氮,接著的8位是指數(shù)E曹阔,剩下的23位為有效數(shù)字M。
對于64位的浮點數(shù)隔披,最高的1位是符號位S赃份,接著的11位是指數(shù)E,剩下的52位為有效數(shù)字M奢米。
其實看看上圖你就應(yīng)該明白抓韩,還有一種精度丟失就是長度丟失(不同類型存儲結(jié)構(gòu)不同),但那種比較簡單鬓长,就不做討論谒拴。
繼續(xù)來講,上圖到底是什么意思呢涉波。咱們先慢慢來講:
正規(guī)化(規(guī)約形式)
如果浮點數(shù)中指數(shù)部分的編碼值在[圖片上傳失敗...(image-c806de-1554705679571)]之間彪薛,且在科學表示法的表示方式下茂装,分數(shù) (fraction) 部分最高有效位(即整數(shù)字)是1,那么這個浮點數(shù)將被稱為規(guī)約形式的浮點數(shù)善延。
如上存儲格式中所示:
首先對于第一段sign少态,就是符號位,0代表正易遣,1代表負彼妻。。
第二段exponent指數(shù)位豆茫,實際也是有正負的侨歉,但是沒有單獨的符號位,在計算機的世界里揩魂,進位都是二進制的幽邓,指數(shù)表示的也是2的N次冪,8位指數(shù)表達的范圍是0到255火脉,而對應(yīng)的實際的指數(shù)是-127到128牵舵。也就是說實際的指數(shù)等于指數(shù)位表示的數(shù)值減127。這里特殊說明倦挂,-127和+128這兩個指數(shù)數(shù)值在IEEE當中是保留的用作多種用途的畸颅,
關(guān)于指數(shù)位與實際的指數(shù)之差叫做移碼,移碼的值
精度 | M(階/指數(shù)數(shù)位) | 移碼 | 二進制表示 |
---|---|---|---|
單精度 | 8 | 127 | 0111 1111 |
雙精度 | 11 | 1023 | 011 1111 1111 |
長雙精度 | 15 | 16383 | 011 1111 1111 1111 |
- 第三段fraction尾數(shù)位(也叫有效數(shù)字)方援,只代表了二進制的小數(shù)點后的部分没炒,小數(shù)點前的那位被省略了,當指數(shù)位全部為0時省略的是0否則省略的是1犯戏。
舉個栗子(以32位浮點數(shù)為例送火,這樣誤差比較大):
比如有個浮點數(shù)17.35需要保存。
17.35轉(zhuǎn)為二進制:17轉(zhuǎn)為10001(這就不用說了吧)
0.35呢先匪?(乘2取整法)
0.35 * 2 = 0.7 記為 0
0.70 * 2 = 1.4 記為 1
0.40 * 2 = 0.8 記為 0
0.80 * 2 = 1.6 記為 1
.......這么算有結(jié)束嗎种吸?當然沒有...所以就按照能保存的極限位數(shù)保存相對精度。
這樣我們17.35就變成了10001.0101100110011001100共預(yù)留24位數(shù)
然后把這串數(shù)字右移至小數(shù)點前只剩1位:
1.00010101100110011001100---》 右移了4位
這樣得到了指數(shù)位和尾數(shù)位:
指數(shù):實際為4胚鸯,必須加上127(轉(zhuǎn)出的時候骨稿,減去127),所以為131姜钳。也就是10000011坦冠。
尾數(shù):00010101100100100100100 (1就不用保存了)。
到此哥桥,十進制浮點數(shù)就變成了二進制浮點數(shù)存儲了辙浑。
非正規(guī)化:0的表示(非規(guī)約形式)(-127)
如果浮點數(shù)的指數(shù)部分的編碼值是0,分數(shù)部分非零拟糕,那么這個浮點數(shù)將被稱為非規(guī)約形式的浮點數(shù)判呕。分數(shù)部分全為0倦踢,就是表示浮點數(shù)0
比如說我們要存儲0.35這個浮點數(shù),按上述正規(guī)化的形式就無法存儲侠草,因為不知道指數(shù)位寫多少辱挥,于是就有了約定,約定指數(shù)位為0就表明非正規(guī)化边涕。
所以晤碘,當見到指數(shù)部分為0是,尾數(shù)部分就不再是1.bbbbb...而是0.bbbbb...了功蜓。
無窮大與NAN
上面說了园爷,指數(shù)位預(yù)留了兩個值(-127和128),-127是做非規(guī)約形式的式撼,那么128呢童社?
當指數(shù)位達到當前浮點數(shù)最大指數(shù)值時:
- 尾數(shù)位全為0就表示無窮大。
- 尾數(shù)位不全為0就表示NaN著隆。
舍入規(guī)則
還是以32位單精度浮點數(shù)為例扰楼。
32位單精度浮點數(shù)存儲23位尾數(shù)位,那么就以第24位位判斷依據(jù)
- 如果 24位為1 24位之后都是0 :如果23位為0旅东,則舍去不管灭抑;如果23位為1十艾,則24位向23位進1抵代,使23位還是0。
- 如果 24位為1 24位之后不全0 :24位向23位進1忘嫉。
- 如果 24位為0 舍
** 因為位數(shù)荤牍、算法、規(guī)則之類的約定庆冕,得知計算機浮點數(shù)并不是嚴格存儲這就是為什么浮點數(shù)運算出現(xiàn)問題的原因 **
有事情要做康吵,先寫到這。
待說明問題:
- 關(guān)于漸進式下溢出的理論與由來访递。
- 關(guān)于以下代碼出現(xiàn)情況的說明:
System.out.println(0.1 * 1);
System.out.println(0.1 * 2);
System.out.println(0.1 * 3);
System.out.println(0.1 * 4);
System.out.println(0.1 * 5);
System.out.println(0.1 * 6);
System.out.println(0.1 * 7);
System.out.println(0.1 * 8);
System.out.println(0.1 * 9);
System.out.println("=====================");
System.out.println(0.0999999999999999999999999999999999999999999999999);
double f1 = 9.7;
double f2 = 0.7;
double f3 = 9;
System.out.println(f1-f2-f3);
System.out.println(f1-f3-f2);
輸出結(jié)果:
0.1
0.2
0.30000000000000004
0.4
0.5
0.6000000000000001
0.7000000000000001
0.8
0.9
=====================
0.1
-6.661338147750939E-16
問題:
- 為什么有的輸出正確晦嵌,有的不行
- 為什么0.0999--的輸出為0.1
- 為什么f1-f2-f3得到0.0,而f1-f3-f2卻得到那樣的結(jié)果
這些問題我現(xiàn)在也并沒有思考清楚拷姿,等待后續(xù)思考惭载。
當然,有問題可以在評論中討論指出响巢。