浮點(diǎn)數(shù)的格式

Java 中基本類型中,表示小數(shù)的有 float 和 double。與他們相關(guān)的幾個問題會經(jīng)常出現(xiàn),并且困擾著我們,例如:float 和 double 為什么叫做浮點(diǎn)數(shù)?他們的格式是什么樣的?如何把一個十進(jìn)制小數(shù)轉(zhuǎn)化為二進(jìn)制?為什么會出現(xiàn) NaN 這中奇怪的東西?

在接下的的章節(jié)中,我們將逐一討論這些問題。


1. 浮點(diǎn)數(shù)

準(zhǔn)確的說,“浮點(diǎn)數(shù)”是一種表示數(shù)值的方法,類似的還有“定點(diǎn)數(shù)”。定點(diǎn)數(shù)是指數(shù)值中的小數(shù)點(diǎn)位置固定的數(shù)值表示方式。例如 3.141 和 2.500 是小數(shù)點(diǎn)在三位小數(shù)之前的定點(diǎn)數(shù)。而浮點(diǎn)數(shù)就是相對定點(diǎn)數(shù)來說,數(shù)值中的小數(shù)點(diǎn)位置不固定的數(shù)值表示方式。例如,2.500 就可以表示為 0.25* 10^1 或?25 * 10^-1。

可以看出,定點(diǎn)數(shù)在數(shù)值的表示上非常的“死板”,小數(shù)點(diǎn)的位置決定了小數(shù)部份的位數(shù),并且在小數(shù)位數(shù)不足的情況下,還需要使用 0 來補(bǔ)全。浮點(diǎn)數(shù)則不存在這樣的問題。

2. 十進(jìn)制轉(zhuǎn)二進(jìn)制

十進(jìn)制數(shù)轉(zhuǎn)二進(jìn)制通常是將整數(shù)部分和小數(shù)部份分開處理,先將整數(shù)部份轉(zhuǎn)化為二進(jìn)制,然后將小數(shù)部份轉(zhuǎn)化為二進(jìn)制,最后將整數(shù)部份和小數(shù)部份的結(jié)果和并起來。

對于整數(shù)部份來說,十進(jìn)制轉(zhuǎn)二進(jìn)制通常使用除 2 取余數(shù)的方式來計算。如圖 1 。

圖 1

圖 1 展示了十進(jìn)制整數(shù) 26 轉(zhuǎn)化為二進(jìn)制整數(shù) 11010 的過程。十進(jìn)制整數(shù)轉(zhuǎn)二進(jìn)制整數(shù)的基本過程是將十進(jìn)制整數(shù)除以 2 ,記錄余數(shù)后將商再除以 2 記錄余數(shù),如此反復(fù),直至商小于 2 為止,將這個得到的最后的商也記錄,最后把所有記錄下的數(shù)翻轉(zhuǎn),即得到與十進(jìn)制對應(yīng)的二進(jìn)制形式。圖 1 中的具體過程如下:

1. 將 26 除以 2,商 13 余數(shù)為 0,記 0,累計為 0;

2. 將上一步的商做為被除數(shù) 13 除以 2,商 6 余數(shù)為 1,記 1,累計為 01;

3. 將上一步的商做為被除數(shù) 6 除以 2,商 3 余數(shù)為 0,記 0,累計為 010;

4. 將上一步的商做為被除數(shù)?3 除以 2,商?1 余數(shù)為 1,記 1,累計為 0101;

5. 上一步的商 1 ,小于 2 ,不再做除法并記錄商,記 1,累計為 01011;

6. 將所有記錄的數(shù)字翻轉(zhuǎn),得 11010,即為 26 的二進(jìn)制形式。

對于小數(shù)部份來說,十進(jìn)制轉(zhuǎn)二進(jìn)制通常采用乘 2 取整數(shù)的方式來計算。如圖 2 。

圖 2

圖 2 展示了十進(jìn)制小數(shù) 0.8125 轉(zhuǎn)化為二進(jìn)制小數(shù) 0.1101 的過程。十進(jìn)制小數(shù)轉(zhuǎn)二進(jìn)制小數(shù)的基本過程是將十進(jìn)制小數(shù)乘以 2,記錄整數(shù)部分后,將積減去其整數(shù)部分后再乘以 2 記錄整數(shù)部分,如此反復(fù),直至積等于 0 或到達(dá)指定精度 [1]。圖 2 中具體過程如下:

1. 將 0.8125 乘以 2,積為 1.625,取整數(shù)部分記 1,累計 1;

2. 將上一步的積減去整數(shù)部分后得 0.625 ,乘以 2 ,積為 1.25,取整數(shù)部分記 1,累計 11;

3. 將上一步的積減去整數(shù)部分后得 0.25 ,乘以 2 ,積為 0.5,取整數(shù)部分記 0,累計 110;

4. 將上一步的積減去整數(shù)部分后得 0.5 ,乘以 2 ,積為 1,取整數(shù)部分記 1,累計 1101;

5. 積為 1 不再做乘法,記錄加小數(shù)點(diǎn)為 0.1101,即為 0.8125 的二進(jìn)制形式。

經(jīng)過以上計算,分別計算出了 26 和 0.8125 的二進(jìn)制形式,如果我們將其合并起來,就得到了 26.8125 的二進(jìn)制形式 11010.1101。

3. 浮點(diǎn)數(shù)的格式

明白如何從數(shù)的十進(jìn)制形式得到的二進(jìn)制形式后,需要理解在 Java 中是如何存儲浮點(diǎn)數(shù)的。Java 遵循 IEEE 754 標(biāo)準(zhǔn),它將一個浮點(diǎn)數(shù)分為“符號”、“階碼”和“尾數(shù)”3 個部分,并且定義了浮點(diǎn)數(shù)的表達(dá)方式,如圖 3。

圖 3

圖 3 中 d.dd...d 即尾數(shù),β 為基數(shù),e 為指數(shù)。二進(jìn)制中的 β 必然為 2,因此 d 的值必然只能為 0 或 1,并且定義了小數(shù)點(diǎn)前的第一個 d 必須是 1 。接下來圖?4 展示了雙精度浮點(diǎn)數(shù) [2] 的格式。

圖 4

先說明圖 3 與圖 4 間的關(guān)聯(lián)。根據(jù)浮點(diǎn)數(shù)的定義,圖 4 中的“符號”與圖 3 中的 ± 號相對應(yīng),用于表示浮點(diǎn)數(shù)是正數(shù)還是負(fù)數(shù),正數(shù)為 0 負(fù)數(shù)為 1;圖 4 中的“尾數(shù)”與圖 3 中 d.dd...d 相對應(yīng),用于表示浮點(diǎn)數(shù)中的有效數(shù)值,根據(jù)定義,圖 4 中的尾數(shù)將忽略圖 3 中的小數(shù)點(diǎn)以及小數(shù)點(diǎn)前的一個 d,默認(rèn)其總是為 1;圖 4 中的“階碼”與圖 3 中的 e 對應(yīng),根據(jù)定義,雙精度浮點(diǎn)數(shù)的階碼 = e + 1023;最后圖 4 中沒有任何與圖 3 中的 β 所對應(yīng)的部分,而是在二進(jìn)制中總是默認(rèn)為 2 。

大致了解了浮點(diǎn)數(shù)的結(jié)構(gòu),接下來通過實(shí)踐來驗(yàn)證一下。

在前面的章節(jié)中,有得出十進(jìn)制的 26.8125 的二進(jìn)制形式為 11010.1101。根據(jù)圖 3 中給出的定義,必須滿足小數(shù)點(diǎn)前有一位的形式,因此調(diào)整 11010.1101 ,使其變?yōu)?1.10101101 * 2^4 。至此,推導(dǎo)出“符號”、“階碼”和“尾數(shù)”3 個部分:

符號為 0。因?yàn)楦鶕?jù)定義,正數(shù)為 0,負(fù)數(shù)為 1。

階碼為 10000000011。因?yàn)?e = 4,根據(jù)定義,階碼 = e + 1023,階碼為 1027 的二進(jìn)制形式。

尾數(shù)為 10101101。因?yàn)?1.10101101 中需要忽略小數(shù)點(diǎn)以及前一個 d。

接下來,將計算所得的幾個數(shù)值根據(jù)雙精度浮點(diǎn)數(shù)的格式進(jìn)行處理:階碼已經(jīng)有 11 位,不需要補(bǔ)全;尾數(shù)只有 8 位,而圖 4 中標(biāo)明了位數(shù)需要 52 位,使用 44 個 0 補(bǔ)全。然后,編寫一段代碼,驗(yàn)證一下手動計算得到的結(jié)果是否可以還原為原始的 26.8125。[3]

圖 5

4. 特殊值

浮點(diǎn)數(shù)定義中指定了幾個特殊值用于表示 0、 NaN 和 Infinity :

0 為階碼全為 0 且位數(shù)全為 0;

NaN 為階碼全為 1 且尾數(shù)不全為 0;

Infinity 為階碼全為 1 且尾數(shù)全為 0。

根據(jù)定義,浮點(diǎn)數(shù)應(yīng)該存在 +Infinity、-Infinity、+NaN、-NaN、 +0、-0、等幾種形式??梢允褂萌鐖D 5 的方式一一驗(yàn)證所有的正特殊值。但由于圖 5 中的代碼在處理的中間使用了 long 作為過渡,因此 long 值如果為負(fù)數(shù)時,將會被表示為補(bǔ)碼。根據(jù)源碼補(bǔ)碼轉(zhuǎn)源碼的定義(源碼 = 補(bǔ)碼 - 1 后除符號位取反),以 -Infinity 為例,則 -Infinity 符號為 1,階碼全為 1,尾數(shù)全為 0。如果使用原碼表示,則為:

1111111111110000000000000000000000000000000000000000000000000000

如果此值為補(bǔ)碼,轉(zhuǎn)為源碼后的表示則為:

1000000000010000000000000000000000000000000000000000000000000000

下面同樣使用如圖 5 的方法進(jìn)行驗(yàn)證。

圖 6


圖 7

圖 6 和圖 7 分別驗(yàn)證了 +Infinity 和 -Infinity。由于 long 的補(bǔ)碼原因,這個方式無法驗(yàn)證 -NaN 和 -0。[4]


[1] 例如雙精度浮點(diǎn)數(shù)只有 52 位尾數(shù)用于表示這個運(yùn)算的結(jié)果,因此這個運(yùn)算是不可能無限繼續(xù)下去。

[2] Java 中的雙精度浮點(diǎn)數(shù)為 double。與雙精度浮點(diǎn)數(shù)類似的,還有單精度浮點(diǎn)數(shù)、擴(kuò)展精度浮點(diǎn)數(shù)等等。

[3] 此方法首先將指定的、表示二進(jìn)制數(shù)值的字符串轉(zhuǎn)化為一個 long 值,然后調(diào)用方法 Double.longBitsToDouble(long) 將這個 long 值轉(zhuǎn)化為一個擁有相同二進(jìn)制表示的 double 值。

[4] 這并不在這篇文章的討論范圍內(nèi),你可以運(yùn)行代碼 System.out.println(-Double.NaN== Double.NaN);System.out.println(Double.compare(-Double.NaN,? Double.NaN));System.out.println(-0.0d== 0.0d);System.out.println(Double.compare(-0.0d,? 0.0d)); 比較并思考運(yùn)行結(jié)果,或查閱其他資料。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容