為什么 0.1 + 0.2不等于0.3?浮點(diǎn)數(shù)在計算機(jī)中是如何存儲的?
為什么 0.1 + 0.2 不等于 0.3?這篇文章,我們將通過這個示例來分析浮點(diǎn)數(shù)在計算機(jī)中是如何存儲的?
一、定點(diǎn)數(shù)
1. 定義
定點(diǎn)數(shù),比較簡單,從字面上理解為小數(shù)點(diǎn)固定的數(shù)。比如,100,3.14,200.08等等都可以看成是定點(diǎn)數(shù)。
通常意義上,定點(diǎn)數(shù)表示整數(shù)或小數(shù),可以分為以下三種情況:
- 純整數(shù):例如,400,小數(shù)點(diǎn)在最后一位,可以忽略
- 純小數(shù):例如,0.68,小數(shù)點(diǎn)固定在最高位
- 整數(shù)+小數(shù):例如,3.14、9.18,小數(shù)點(diǎn)在指定某個位置
接下來,我們一起看看定點(diǎn)數(shù)的十進(jìn)制和二進(jìn)制的相互轉(zhuǎn)換。
2. 十進(jìn)制整數(shù)轉(zhuǎn)二進(jìn)制
將十進(jìn)制數(shù)轉(zhuǎn)換為二進(jìn)制數(shù),通過不斷地除以 2,直到商為 0。步驟:
- 將十進(jìn)制數(shù)除以2,記錄商和余數(shù)
- 將商再次除以2,記錄新的商和余數(shù)
- 重復(fù)這個過程,直到商為 0為止
- 從下往上讀取所有的余數(shù),就得到了轉(zhuǎn)換后的二進(jìn)制數(shù)
比如:將十進(jìn)制的 38轉(zhuǎn)換成二進(jìn)制:
38 / 2 = 19 余 0
19 / 2 = 9 余 1
9 / 2 = 4 余 1
4 / 2 = 2 余 0
2 / 2 = 1 余 0
1 / 2 = 0 余 1
所以,(38)?? 的二進(jìn)制是 (100110)?
3. 二進(jìn)制整數(shù)轉(zhuǎn)十進(jìn)制
將二進(jìn)制數(shù)轉(zhuǎn)換為十進(jìn)制數(shù),根據(jù)權(quán)值展開法,將每一位上的數(shù)字與其對應(yīng)的權(quán)值相乘,然后將所有結(jié)果相加。權(quán)值是 2的冪,從右向左依次增加。
例如,將二進(jìn)制數(shù) 100110轉(zhuǎn)換成十進(jìn)制:
1*2? + 0*2? + 0*23 + 1*22 + 1*21 + 0*2?
= 32 + 0 + 0 + 4 + 2 + 0
= 38
所以,(100110)? 的十進(jìn)制是 (38)??
4. 十進(jìn)制小數(shù)轉(zhuǎn)二進(jìn)制
十進(jìn)制小數(shù)轉(zhuǎn)二進(jìn)制分兩部分:整數(shù)部分轉(zhuǎn)換上面已經(jīng)講解了,小數(shù)部分采用“乘2取整,從上到下順序排列”。
例如,十進(jìn)制小數(shù) 10.75 轉(zhuǎn)為二進(jìn)制小數(shù):
# 整數(shù)部分
10 / 2 = 5 余 0
5 / 2 = 2 余 1
2 / 2 = 1 余 0
1 / 2 = 0 余 1
# 小數(shù)部分
0.75 * 2 = 1.5 取整數(shù)部分 1
0.5 * 2 = 1.0 取整數(shù)部分 1
所以,(10.75)?? 的二進(jìn)制是 (1010.11)?
5 二進(jìn)制小數(shù)轉(zhuǎn)十進(jìn)制
十進(jìn)制小數(shù)轉(zhuǎn)二進(jìn)制分兩部分,整數(shù)部分轉(zhuǎn)為二進(jìn)制上面已經(jīng)講解了,小數(shù)部分采用“乘2取整,從上到下順序排列”。
例如,二進(jìn)制小數(shù)轉(zhuǎn)十進(jìn)制小數(shù):
# 整數(shù)部分
1*23 + 0*22 + 1*21 + 0*2?
= 8 + 0 + 2 + 0 + 2 + 0 = 10
# 小數(shù)部分
1*2?1 + 1*2?2
= 0.5 + 0.25
= 0.75
# 整數(shù)部分 + 小數(shù)部分
10 + 0.75 = 10.7
所以,(1010.11)? 的十進(jìn)制是 (10.75)??
6. 定點(diǎn)數(shù)的優(yōu)缺點(diǎn)
(1) 優(yōu)點(diǎn)
定點(diǎn)數(shù)的精度是固定的,可以根據(jù)需求進(jìn)行靈活調(diào)整,這使得它們在需要固定精度的應(yīng)用中非常有用。
(2) 缺點(diǎn)
定點(diǎn)數(shù)的精度是固定的,這意味著在處理大范圍或者需要高精度的數(shù)據(jù)時,可能會丟失精度或者溢出。
比如,以 1個字節(jié)(8 bit)為例,假如約定前 4位表示整數(shù)部分,后 4位表示小數(shù)部分,因此,可以表達(dá)的最大數(shù)是:(1111.1111)?=(15.9375)??。
如果想要表示更大范圍的值,怎么辦?
- 增加 bit:比如,使用 2個字節(jié)(16 bit)、4個字節(jié)(32bit),這樣整數(shù)部分和小數(shù)部分都增加了,表示的數(shù)字范圍也變大了。
- 小數(shù)點(diǎn)右移:小數(shù)點(diǎn)右移,整數(shù)范圍就變大了,因此整個數(shù)字范圍就變大了,但是小數(shù)部分的精度就會越來越低。
因此,對于表示大范圍或者高精度的數(shù),定點(diǎn)數(shù)存在其局限性,這時候“浮點(diǎn)數(shù)”就派上用場了。
二、IEEE 754
在講解浮點(diǎn)數(shù)之前,我們需要先了解一個很重要的標(biāo)準(zhǔn):IEEE 754,它是 20世紀(jì)80年代以來最廣泛使用的浮點(diǎn)數(shù)運(yùn)算標(biāo)準(zhǔn),為許多 CPU、浮點(diǎn)運(yùn)算器和編程語言(比如 Java)所采用。
這個標(biāo)準(zhǔn)定義了以下規(guī)范:
- 表示浮點(diǎn)數(shù)的格式(包括負(fù)零-0)與反常值(Denormal number);
- 一些特殊數(shù)值(無窮 Inf與非數(shù)值 NaN),以及這些數(shù)值的浮點(diǎn)數(shù)運(yùn)算;
- 指明了四種數(shù)值修約規(guī)則和五種例外狀況(包括例外發(fā)生的時機(jī)與處理方式);
另外,在 IEEE 754標(biāo)準(zhǔn)推出之前,各個計算機(jī)公司對于浮點(diǎn)數(shù)的表示沒有一個業(yè)界通用的標(biāo)準(zhǔn),這給數(shù)據(jù)交換、計算機(jī)協(xié)同工作造成了極大不便,直到 1985年,IEEE 組織推出了 IEEE 754浮點(diǎn)數(shù)標(biāo)準(zhǔn),才結(jié)束了這混亂的局面。
三、浮點(diǎn)數(shù)
1. 什么是浮點(diǎn)數(shù)?
浮點(diǎn)數(shù),是相對定點(diǎn)數(shù)而言的,從字面上可以解釋為:小數(shù)點(diǎn)在浮動的數(shù)。在計算機(jī)科學(xué)中,浮點(diǎn)數(shù)是一種用于表示帶有小數(shù)點(diǎn)的實(shí)數(shù)的數(shù)據(jù)類型,通常采用了科學(xué)計數(shù)法表示。
如下例子,十進(jìn)制小數(shù) 300.14,用科學(xué)計數(shù)法表示,可以有多種方式,整體看上去,小數(shù)點(diǎn)好像在浮動。
300.14 = 30.014 × 101
300.14 = 3.0014 × 102
300.14 = 0.31004 × 103
在 IEEE 754標(biāo)準(zhǔn)中,浮點(diǎn)數(shù)在內(nèi)存中以二進(jìn)制形式存儲,分為四個部分:符號位、基數(shù)、指數(shù)部分和尾數(shù)部分。
- 符號位(Sign bit):用于表示數(shù)值的正負(fù)。0表示正數(shù),1表示負(fù)數(shù)。
- 指數(shù)部分(Exponent):用于表示數(shù)值的大小范圍。該部分存儲的是一個無符號整數(shù),通常采用偏移表示,即用實(shí)際指數(shù)加上一個固定偏移值。
- 基數(shù)(Base):也稱為進(jìn)制或底數(shù),常見的基數(shù)包括十進(jìn)制(基數(shù)為10)、二進(jìn)制(基數(shù)為2)、八進(jìn)制(基數(shù)為8)、十六進(jìn)制(基數(shù)為16)等。
- 尾數(shù)部分(Mantissa/Significand):用于表示數(shù)值的精度和小數(shù)部分。在IEEE 754中,尾數(shù)總是處于[1,2)之間的一個小數(shù),這樣可以省略掉小數(shù)點(diǎn)前面的 1,從而節(jié)省了一個 bit。注意:significand 和 mantissa 都是用來指代浮點(diǎn)數(shù)中的尾數(shù)部分,只不過 mantissa是早期計算機(jī)的叫法。兩個術(shù)語可以互換,用來表示浮點(diǎn)數(shù)中的尾數(shù)部分。
下圖以 300.14為例展示了一個浮點(diǎn)數(shù)的構(gòu)成:
2. 浮點(diǎn)數(shù)的精度
在IEEE 754標(biāo)準(zhǔn)中規(guī)定了 4種主要的浮點(diǎn)數(shù)類型:單精度浮點(diǎn)數(shù)(float)、雙精度浮點(diǎn)數(shù)(double)、延伸單精確度以及延伸雙精確度。
(1) 單精度浮點(diǎn)數(shù)(float)
單精度浮點(diǎn)數(shù)占用 32位(4個字節(jié)),其中,符號占 1位,指數(shù)部分占 8位,尾數(shù)部分占 23位。指數(shù)部分可以表示的范圍是 0~255,因?yàn)榇嬖谄屏?,因此指?shù)的實(shí)際范圍是從-126~+127,即 2?12?~212?,轉(zhuǎn)換成十進(jìn)制,范圍大約為 ±3.4x10?3? 到 ±3.4x103?之間。
(2) 雙精度浮點(diǎn)數(shù)(double)
雙精度浮點(diǎn)數(shù)占用 64位(8個字節(jié)),其中,符號占 1位,指數(shù)部分占 11位,尾數(shù)部分占 52位。指數(shù)部分可以表示的范圍是 0~2047,因?yàn)榇嬖谄屏浚虼酥笖?shù)的實(shí)際范圍是從-1022~+1023,即 2?1?22~ 21?23,轉(zhuǎn)換成十進(jìn)制,范圍大約為 ±1.7x10?3?? 到 ±1.7x103??之間。
(3) 延伸單精確度
因?yàn)椴怀S?,所以本文不進(jìn)行講解。
(4) 延伸雙精確度
因?yàn)椴怀S?,所以本文不進(jìn)行講解。
關(guān)于單精度浮點(diǎn)數(shù)和雙精度浮點(diǎn)數(shù)的二進(jìn)制表示如下圖:
在了解完定點(diǎn)數(shù)和浮點(diǎn)數(shù)的一些基本概念之后,接下來講解浮點(diǎn)數(shù)使用IEEE 754標(biāo)準(zhǔn)在內(nèi)存是如何使用二進(jìn)制存儲的, 這里是IEEE 754標(biāo)準(zhǔn)的精華部分,也是比較難理解的部分,我會通過實(shí)際的例子結(jié)合圖形進(jìn)行分析。
3. 浮點(diǎn)數(shù)的IEEE754轉(zhuǎn)換
(1) 十進(jìn)制轉(zhuǎn)二進(jìn)制
這里以十進(jìn)制轉(zhuǎn) IEEE754單精度浮點(diǎn)數(shù)并且無精度丟失為例,核心流程包含 5個步驟:
① 確認(rèn) Sign符號位
比如,19.59375的符號為 0(正數(shù)),-1.1的符號為 1(負(fù)數(shù)),將結(jié)果值(0/1)填充到二進(jìn)制的 Sign bit區(qū)域。
② 將十進(jìn)制轉(zhuǎn)成純二進(jìn)制
這個步驟,只是純粹地將十進(jìn)制轉(zhuǎn)換成二進(jìn)制。
- 對于整數(shù)部分,通過不斷地除以 2,直到商為 0;
- 對于小數(shù)部分,采用逐步乘 2取整,直到小數(shù)部分為 0或達(dá)到尾數(shù)所需的精度;
需要注意,對于小數(shù)部分處理結(jié)束的條件有 2個:小數(shù)部分為0 或 達(dá)到尾數(shù)所需的精度。只要滿足一個就OK,這里也是為什么小數(shù)會丟精度的關(guān)鍵所在,在下面的例子會進(jìn)行講解。
對于沒有精度丟失的轉(zhuǎn)換,結(jié)束的條件是:小數(shù)部分為 0。
比如,0.25轉(zhuǎn)成二進(jìn)制
0.25 x 2 = 0.5 0
0.5 x 2 = 1.0 1 小數(shù)部分為0,結(jié)束
③ 標(biāo)準(zhǔn)化以確定尾數(shù)和無偏移指數(shù)
根據(jù) IEEE 754標(biāo)準(zhǔn),需要將二進(jìn)制小數(shù)點(diǎn)放在最左邊的 1 之后,比如,100.101需要左移 2位,變成 1.00101x22,無偏移指數(shù)是 2;0.0011需要右移 3位,變成 1.1x2?3,無偏移指數(shù)是 -3。
這個步驟其實(shí)就是 IEEE 754標(biāo)準(zhǔn)的一個硬性規(guī)定,解決了上面提到的浮點(diǎn)數(shù)漂浮不定的問題。
④ 確認(rèn)偏移指數(shù)
偏移指數(shù),即用無偏移指數(shù)(步驟3 產(chǎn)生的結(jié)果)加上固定的偏移值127(2?-1=127),再轉(zhuǎn)換成二進(jìn)制。
假如,無偏移指數(shù)是 4,那么,偏移指數(shù)就等于4+127=131,轉(zhuǎn)換成二進(jìn)制就是10000011,然后,將二進(jìn)制結(jié)果值10000011填充到 Exponent指數(shù)區(qū)域。
⑤ 移除尾數(shù)的前導(dǎo) 1
在步驟3中,需要將二進(jìn)制小數(shù)點(diǎn)放在最左邊的 1 之后,因此,每個小數(shù)點(diǎn)前面的值都是固定的 1(也叫做前導(dǎo) 1),在 IEEE 754標(biāo)準(zhǔn)中,會將這個前導(dǎo) 1移除,從而節(jié)省了 1個bit,再將結(jié)果值填充到二進(jìn)制的 Significand尾數(shù)區(qū)域,不足部分填 0。
比如,1.001110011 移除小數(shù)點(diǎn)前固定的 1 變成了001110011,然后將結(jié)果值001110011填充到 Significand尾數(shù)區(qū)域。
為了更好的解釋上面 5個步驟,這里以十進(jìn)制19.59375轉(zhuǎn)換成IEEE 754二進(jìn)制為例進(jìn)行講解,整個過程如下圖:
(2) 十進(jìn)制轉(zhuǎn)二進(jìn)制
這里以十進(jìn)制轉(zhuǎn) IEEE754單精度浮點(diǎn)數(shù)并且有精度丟失為例,核心流程包含 6個步驟:
① 確認(rèn) Sign符號位
比如,19.59375的符號為 0(正數(shù)),-1.1的符號為 1(負(fù)數(shù)),將結(jié)果值(0/1)填充到二進(jìn)制的 Sign bit區(qū)域。
② 將十進(jìn)制轉(zhuǎn)成純二進(jìn)制
這個步驟,只是純粹地將十進(jìn)制轉(zhuǎn)換成二進(jìn)制。
- 對于整數(shù)部分,通過不斷地除以 2,直到商為 0;
- 對于小數(shù)部分,采用逐步乘 2取整,直到小數(shù)部分為 0或達(dá)到所需的精度;
需要注意,對于小數(shù)部分處理結(jié)束的條件有 2個:小數(shù)部分為0 或 達(dá)到尾數(shù)所需的精度。
對于有精度丟失的轉(zhuǎn)換,結(jié)束的條件是:達(dá)到所需的精度。
比如,0.3轉(zhuǎn)成二進(jìn)制
0.3 x 2 = 0.6 0
0.6 x 2 = 1.2 1
0.2 x 2 = 0.4 0
0.4 x 2 = 0.8 0
0.8 x 2 = 1.6 1
0.6 x 2 = 1.2 1 開始進(jìn)入循環(huán),只能達(dá)到所需的精度后按需舍入結(jié)束
0.2 x 2 = 0.4 0
③ 標(biāo)準(zhǔn)化以確定尾數(shù)和無偏移指數(shù)
根據(jù) IEEE 754標(biāo)準(zhǔn),需要將二進(jìn)制小數(shù)點(diǎn)放在最左邊的 1 之后,比如,100.101需要左移 2位,變成 1.00101x22,無偏移指數(shù)是 2;0.0011需要右移 3位,變成 1.1x2?3,無偏移指數(shù)是 -3。
這個步驟其實(shí)就是 IEEE 754標(biāo)準(zhǔn)的一個硬性規(guī)定,解決了上面提到的浮點(diǎn)數(shù)漂浮不定的問題。
④ 確認(rèn)偏移指數(shù)
偏移指數(shù),即用無偏移指數(shù)(步驟3 產(chǎn)生的結(jié)果)加上固定的偏移值127(2?-1=127),再轉(zhuǎn)換成二進(jìn)制。
假如,無偏移指數(shù)是 4,那么,偏移指數(shù)就等于4+127=131,轉(zhuǎn)換成二進(jìn)制就是10000011,然后,將二進(jìn)制結(jié)果值10000011填充到 Exponent指數(shù)區(qū)域。
⑤ 移除尾數(shù)的前導(dǎo) 1
在步驟3中,需要將二進(jìn)制小數(shù)點(diǎn)放在最左邊的 1 之后,因此,每個小數(shù)點(diǎn)前面的值都是固定的 1(也叫做前導(dǎo) 1),在 IEEE 754標(biāo)準(zhǔn)中,會將這個前導(dǎo) 1移除,從而節(jié)省了 1bit,再將結(jié)果值填充到二進(jìn)制的 Significand尾數(shù)區(qū)域,不足部分填0。
比如,1.001110011 移除小數(shù)點(diǎn)前固定的 1 變成了001110011,然后將結(jié)果值001110011填充到 Significand尾數(shù)區(qū)域。
⑥ 按需向上或者向下舍入
在步驟2中,小數(shù)部分轉(zhuǎn)換成二進(jìn)制的時候不是因?yàn)槌?2使得小數(shù)部分為 0結(jié)束,而是因?yàn)楫a(chǎn)生了循環(huán),導(dǎo)致達(dá)到了尾數(shù)所需的精度(單精度 23bit,雙精度 52bit),對于超出的精度范圍,需要如何處理?
答案:按需向上或者向下舍入
為了更好的解釋上面 6個步驟,這里以十進(jìn)制-123.3轉(zhuǎn)換成 IEEE 754二進(jìn)制為例進(jìn)行講解,整體流程如下圖:
注意:截圖中步驟6黃色字體1001,是指超出尾數(shù) 23bit范圍的二進(jìn)制數(shù),需要被舍入
通過上面兩個例子的分析,相信大家還是會有困惑:為什么是二進(jìn)制中存儲的是偏移指數(shù)而不是指針?為什么偏移指數(shù)是通過指數(shù)加上一個固定的偏移值?這個固定的偏移值是怎么計算的?為什么尾數(shù)需要把前導(dǎo) 1移除?IEEE 754 的舍入規(guī)則是什么?下面我們就一一解答。
(3) 二進(jìn)制轉(zhuǎn)十進(jìn)制
為了幫助大家更好地理解 IEEE 754的轉(zhuǎn)換,這里還提供了 2個 IEEE 754單精度浮點(diǎn)數(shù)二進(jìn)制轉(zhuǎn)十進(jìn)制的逆向例子,如下圖:
(4) 偏移指數(shù)
偏移指數(shù),也叫指數(shù)偏移值(exponent bias),即浮點(diǎn)數(shù)表示法中指數(shù)域的編碼值,等于指數(shù)的實(shí)際值加上某個固定的偏移值,IEEE 754標(biāo)準(zhǔn)規(guī)定該固定值偏移為2 ??1-1,其中 e為存儲指數(shù)的 bit長度,因此,單精度浮點(diǎn)數(shù)的固定偏移值是2??1-1=127,雙精度浮點(diǎn)數(shù)的固定偏移值是211?1-1=1023。
(5) 為什么需要偏移指數(shù)?
從 1.00101 x 22 和 1.1 x 2?3 可以看出來,指數(shù)有正負(fù)數(shù)的區(qū)分,即有符號的區(qū)分,因此,IEEE 754標(biāo)準(zhǔn)中的偏移指數(shù)主要解決兩個問題:
- 表示負(fù)指數(shù):在使用二進(jìn)制表示浮點(diǎn)數(shù)的指數(shù)時,如果采用純粹的二進(jìn)制表示,那么需要額外的符號位來表示指數(shù)的正負(fù)。采用偏移指數(shù)的方式,可以將指數(shù)全部看作非負(fù)數(shù),因?yàn)閷⑵屏刻砑拥街笖?shù)部分后,所有的指數(shù)都是正數(shù),0則表示了最小的指數(shù)。
- 排序浮點(diǎn)數(shù):使用偏移指數(shù)的方式可以更容易地對浮點(diǎn)數(shù)進(jìn)行排序。因此第 1點(diǎn)已經(jīng)把指數(shù)全部轉(zhuǎn)換成了無符號,所以,浮點(diǎn)數(shù)的比較直接變成了對二進(jìn)制的自然排序比較,不需要單獨(dú)處理符號位和指數(shù)部分的符號。
(6) 固定值偏移值如何計算?
① 單精度浮點(diǎn)數(shù)
對于單精度浮點(diǎn)數(shù),它的指數(shù)域是 8個bit,表示的有符號范圍是-127~128(-2?-1 ~ 2?),如何讓這個范圍 >=0 ?
答案:加上 127。所以,IEEE 754標(biāo)準(zhǔn)把 127設(shè)定為單精度浮點(diǎn)數(shù)的固定偏移值。
② 雙精度浮點(diǎn)數(shù)
同理,對于雙精度浮點(diǎn)數(shù),它的指數(shù)域是 11個bit,表示的有符號范圍是-1023~1024(-21?-1 ~ 21?),如何讓這個范圍 >=0 ?
答案:加上 1023。所以,IEEE 754標(biāo)準(zhǔn)把 1023設(shè)定為雙精度浮點(diǎn)數(shù)的固定偏移值。
具體信息如下圖:
(7) 為什么要移除尾數(shù)的前導(dǎo) 1?
在講解浮點(diǎn)數(shù)構(gòu)成時提過,浮點(diǎn)數(shù)的小數(shù)點(diǎn)是浮動的,因此,IEEE 754標(biāo)準(zhǔn)定義了一套固定的格式:在二進(jìn)制數(shù)中,通過移位,將小數(shù)點(diǎn)前面的值固定為 1,IEEE754 稱這種形式的浮點(diǎn)數(shù)為規(guī)范化浮點(diǎn)數(shù)。
因此,對于規(guī)范化浮點(diǎn)數(shù),既然尾數(shù)的前導(dǎo)永遠(yuǎn)是 1,那干脆不存儲,尾數(shù)其實(shí)比實(shí)際的多 1位,也就是說單精度的是 24位,雙精度是 52位。
(8) IEEE 754 的舍入規(guī)則
關(guān)于舍入,IEEE 754標(biāo)準(zhǔn)提供了 4種方法:
① 舍入到最接近
這是 IEEE 754標(biāo)準(zhǔn)的默認(rèn)方式,將結(jié)果舍入為最接近且可以表示的值,但是當(dāng)存在兩個數(shù)一樣接近的時候,則取其中的偶數(shù)(在二進(jìn)制中是以0結(jié)尾的)。
取偶數(shù)最關(guān)鍵的步驟是找到一個中間值,先確定要保留的有效數(shù)字,找到要保留的有效數(shù)字最低位的下一位。如果這位是進(jìn)制的一半,而且之后的位數(shù)都為 0,則這個值就是中間值。
這里以二進(jìn)制為例,有效位數(shù)保留到小數(shù)點(diǎn)后 2位:
- 10.00011,中間值為 10.00100,小于中間值,向下舍入為 10.00
- 10.00110,中間值為 10.00100,大于中間值,向上舍入為 10.01
- 10.11100,中間值為 10.11100,等于中間值,要保留的最低有效位 1 為奇數(shù),向上舍入為 11.00
- 10.10100,中間值為 10.10100,等于中間值,要保留的最低有效位 0 為偶數(shù),向下舍入為 10.10
因此,上述十進(jìn)制-123.3轉(zhuǎn)換成 IEEE 754二進(jìn)制例子的舍入方式,采用向上舍入,即最后一位 +1。
② 朝 +∞方向舍入
3個要點(diǎn):
- 正數(shù)多余位不全為 0,進(jìn)位1
- 正數(shù)多余位全為 0,直接截尾
- 負(fù)數(shù)直接截尾
這里以二進(jìn)制為例,有效位數(shù)保留到小數(shù)點(diǎn)后 3位:
- 0.0011001,正數(shù),從小數(shù)點(diǎn)后 4位起,不全為0,則向上進(jìn)位(最后一位 +1),結(jié)果值為 1.010,
- 0.0010000,正數(shù),從小數(shù)點(diǎn)后 4位起,全為0,則直接截尾(從 4位起全部舍棄),結(jié)果值為 0.001
- -0.0011010,負(fù)數(shù)直接截尾(從 4位起全部舍棄),結(jié)果值為 -0.001
③ 朝 -∞方向舍入
3個要點(diǎn):
- 正數(shù)直接截尾
- 負(fù)數(shù)多余位全為0,直接截尾
- 負(fù)數(shù)多余位不全為 0,進(jìn)位1
這里以二進(jìn)制為例,有效位數(shù)保留到小數(shù)點(diǎn)后 3位:
- 0.0011001,正數(shù),則直接截尾(從 4位起全部舍棄),結(jié)果值為 0.001
- -0.001000,負(fù)數(shù),從小數(shù)點(diǎn)后 4位起全為 0,則直接截尾(從 4位起全部舍棄),結(jié)果值 -0.001
- -0.001101,負(fù)數(shù),從小數(shù)點(diǎn)后 4位起不全為 0,則向上進(jìn)位(最后一位 +1),結(jié)果值-0.010
④ 朝 0方向舍入
2個要點(diǎn):
- 正數(shù)直接截尾
- 負(fù)數(shù)直接截尾
數(shù)學(xué)上有 4舍5入,計算機(jī)中 0舍1入,因此,朝 0方向舍入就是直接舍棄。
這里以二進(jìn)制為例,有效位數(shù)保留到小數(shù)點(diǎn)后 3位:
- 0.001100,正數(shù),則直接截尾(從 4位起全部舍棄)棄,結(jié)果值 0.001
- -0.001100,負(fù)數(shù),則直接截尾(從 4位起全部舍棄)棄,結(jié)果值 -0.001
四、非規(guī)范浮點(diǎn)數(shù)
上文提到了規(guī)范化浮點(diǎn)數(shù),既然有規(guī)范化浮點(diǎn)數(shù),是不是也存在非規(guī)范化浮點(diǎn)數(shù)?非規(guī)范化浮點(diǎn)數(shù)又是什么呢?
在 IEEE 754標(biāo)準(zhǔn)中,將“指數(shù)部分全是0,尾數(shù)部分非0”這樣的浮點(diǎn)數(shù)稱為非規(guī)范化浮點(diǎn)數(shù),一般用于表示 0或者無限接近 0的很小的數(shù)字。
另外,IEEE 754標(biāo)準(zhǔn)還規(guī)定:非規(guī)范化浮點(diǎn)數(shù)的指數(shù)偏移值比規(guī)范化浮點(diǎn)數(shù)的指數(shù)偏移值小 1。
例如,最小的規(guī)范化單精度浮點(diǎn)數(shù)的指數(shù)部分編碼值為1,指數(shù)的實(shí)際值為-126;而非規(guī)范化的單精度浮點(diǎn)數(shù)的指數(shù)域編碼值為0,對應(yīng)的指數(shù)實(shí)際值也是 -126 而不是-127。實(shí)際上非規(guī)范化浮點(diǎn)數(shù)仍然是有效可以使用的,只是它們的絕對值已經(jīng)小于所有的規(guī)約浮點(diǎn)數(shù)的絕對值;即所有的非規(guī)范化浮點(diǎn)數(shù)比規(guī)約浮點(diǎn)數(shù)更接近0。規(guī)約浮點(diǎn)數(shù)的尾數(shù)大于等于1且小于2,而非規(guī)范化浮點(diǎn)數(shù)的尾數(shù)小于1且大于0。
下圖展示了非規(guī)范化單精度浮點(diǎn)數(shù)的二進(jìn)制表示:
五、特殊值
另外,IEEE 754中還定義4個特殊值,如下表:
- 正負(fù)無窮大:指數(shù)全是 1,尾數(shù)全是 0,代表這個數(shù)是正負(fù)無窮大(±∞),正負(fù)取決于 S符號位。
- NaN:指數(shù)全是 1,尾數(shù)非 0,代表這不是一個數(shù)字(NaN,Not a Number)。
- 0:指數(shù)全是 0,尾數(shù)全是0,代表這個數(shù)是±0,正負(fù)取決于 S符號位。
- 很小的值:指數(shù)全是 0,尾數(shù)不全是0,代表這個數(shù)是一個很小的非規(guī)范化浮點(diǎn)數(shù)。
六、浮點(diǎn)數(shù)的比較和運(yùn)算
有了 IEEE 754標(biāo)準(zhǔn),浮點(diǎn)數(shù)的比較就很簡單,基本上可以按照符號位、指數(shù)域、尾數(shù)域的順序作字典比較。顯然,所有正數(shù)大于負(fù)數(shù);正負(fù)號相同時,指數(shù)的二進(jìn)制表示法更大的其浮點(diǎn)數(shù)值更大。
浮點(diǎn)數(shù)的運(yùn)算和函數(shù)包含以下幾種:
- 加減乘除(Add、subtract、multiply、divide)。在加減運(yùn)算中負(fù)零與零相等:?0.0=0.0
- 平方根(Square root):
- 浮點(diǎn)余數(shù)。返回值 x-(round(x/y)*y)。
- 近似到最近的整數(shù) ?? ?? ?? ?? ??(??)。如果恰好在兩個相鄰整數(shù)之間,則近似到偶數(shù)。
- 比較運(yùn)算. -Inf <負(fù)的規(guī)范化浮點(diǎn)數(shù)數(shù)<負(fù)的非規(guī)范化浮點(diǎn)數(shù)< -0.0 = 0.0 <正的非規(guī)范化浮點(diǎn)數(shù)<正的規(guī)范化浮點(diǎn)數(shù)< Inf;
- 特殊比較:-Inf = -Inf, Inf = Inf, NaN與任何浮點(diǎn)數(shù)(包括自身)的比較結(jié)果都為假,即 (NaN ≠ x) = false.
七、為什么 1.0-0.9 != 0.1?
先看 Java中的一個例子:
通過運(yùn)行結(jié)果可以發(fā)現(xiàn):在 Java中,不管是單精度 float,還是雙精度 double,1.0 - 0.9 != 0.1,為什么?
從上面的講解,我們已經(jīng)知道浮點(diǎn)數(shù) IEEE 754標(biāo)準(zhǔn)在內(nèi)存中的存儲方式,這里我們再簡單的分析1.0 - 0.9場景:
1.0 可以精確轉(zhuǎn)換成IEEE 754標(biāo)準(zhǔn)二進(jìn)制為:0 01111111 00000000000000000000000
0.9 轉(zhuǎn)換成IEEE 754標(biāo)準(zhǔn)二進(jìn)制為:
符號位:0
偏移指數(shù):-1 + 127 = (126)?? = (0111 1110)?
尾數(shù):
0.9 x 2 = 1.8 1
0.8 x 2 = 1.6 1
0.6 x 2 = 1.2 1
0.2 x 2 = 0.4 0
0.4 x 2 = 0.8 0
0.8 x 2 = 1.6 1 開始進(jìn)入循環(huán),只能達(dá)到所需的精度后按需舍入結(jié)束
0.6 x 2 = 1.2 1
在0.9 轉(zhuǎn)換成 IEEE 754標(biāo)準(zhǔn)的二進(jìn)制時,出現(xiàn)了循環(huán),這樣的話,不管是單精度還是雙精度,0.9轉(zhuǎn)換成二進(jìn)制之后都有精度損失,所以,對于 float 或者 double浮點(diǎn)數(shù) 0.9,轉(zhuǎn)換后其真實(shí)值已經(jīng)不是 0.9,因此,1.0 - 0.9 != 0.1。
當(dāng)然,除了這個例子,還有很多經(jīng)典的例子,比如 w s m 為什么等于 0.30000000000000004?
八、總結(jié)
本文講解了定點(diǎn)數(shù)和浮點(diǎn)數(shù),重點(diǎn)分析了浮點(diǎn)數(shù)以及IEEE 754標(biāo)準(zhǔn)下浮點(diǎn)數(shù)是如何存儲的。
- 浮點(diǎn)數(shù)一般用科學(xué)計數(shù)法表示
- IEEE 754標(biāo)準(zhǔn)的浮點(diǎn)數(shù)二進(jìn)制實(shí)際包含:符號位,偏移指數(shù),尾數(shù) 3部分。
- 十進(jìn)制小數(shù)在轉(zhuǎn)換為二進(jìn)制時,如果可以精確轉(zhuǎn)換,則不存在精度丟失。
- 十進(jìn)制小數(shù)在轉(zhuǎn)換為二進(jìn)制時,如果無法精確轉(zhuǎn)換(存在循環(huán)或者超出尾數(shù)的范圍),則存在精度丟失的問題。
- IEEE 754舍入規(guī)則有 4種方法:舍入到最接近、朝 +∞方向舍入、朝 -∞方向舍入以及朝 0方向舍入。