IEEE 浮點表示
IEEE 浮點標准:V = (-1)s * M * 2E 表示一個浮點數:
- 符號(sign) s 決定
V
的正(s=0)或負(s=1),對於 0 后面會有說明 - 尾數(Mantissa) 二進制小數
- 階碼(Exponent) E 的作用是對浮點數加權,這個權重是 2 的 E 次冪
將浮點數的位分為 3 個部分:
- 1 位的符號位 s 表示 s
- k 位的階碼字段 exp = ek-1...e1e0 表示 E
- n 位的小數字段 frac = fn-1...f1f0 表示 M
以 C 語言為例,不同的精度下,s、exp、frac 有不同的位數:
單精度:
31 30 23 22 0
+---+---------+-----------------------------------------+
| s | exp | frac |
+---+---------+-----------------------------------------+
雙精度:
63 62 52 51 32
+---+----------------+----------------------------------+
| s | exp | frac(51:32) |
+---+----------------+----------------------------------+
31 0
+-------------------------------------------------------+
| frac(31:0) |
+-------------------------------------------------------+
- float:s 1位、exp 的 k=8位、frac=23位,合計 32位
- double:s 1位、exp 的 k=11位、frac=52位,合計 64位
分類
以 C 語言單精度為例,根據 exp 存儲的位的不同,所表示的浮點數可以分成 3 中不同的情況,而最后一種情況中情況分兩個變種:
-
規格化
+-------------------------------------------------------+ | s | exp!=0 & exp!=255 | frac | +-------------------------------------------------------+
這是最常見的情況,exp 的位模式既不為全 0,也不為全 1
規格化的值有兩點需要特別注意:-
階碼 E 包含一定的偏置 Bias,也就是說
E = exp - Bias
,exp 是無符號數,Bias = 2k-1 - 1,偏置的作用是為了在規格化取值范圍與非規格化取值范圍之間平滑過渡 -
尾數 M 的值並不是 frac 所表示的小數值,實際情況是
M = 1 + frac
通常情況下,二進制整數部分通過調整小數點(也就是修改 E)來變成 1,所以 IEEE 的表示法直接將這一位的1
省去,這樣二進制小數部分就能多存儲一位,提高了精度,也就是說這個 frac 隱含了開頭的 1舉個例子:假設 frac 有 5 位,現在要存儲一個二進制數 b,b 的值是 0.101011(2),調整一下權重:1.01011 * 2-1(2),farc 存儲的就是小數點后面的這 5 位
01011
-
-
非規格化
+-------------------------------------------------------+ | s | exp=0 | frac | +-------------------------------------------------------+
exp 位模式全為 0,E = exp - Bias,M = frac
規格化數因 frac 隱含開頭的 1,M >= 1,故而無法表示 0 這個數。
當 exp 和 frac 都是 0 時,s = 1 得到 -0.0,s = 0 得到 +0.0 -
- 無窮大(Infinity)
exp 所有位皆是 1,frac 所有位皆是 0 ,表示無窮大+-------------------------------------------------------+ | s | exp=255 | frac=0 | +-------------------------------------------------------+
s = 1 時表示負無窮大,s = 0 時表示正無窮大 - NaN(Not a Number)
exp 所有位皆是 1,frac 的位模式不全為 0 ,表示 NaN 不是一個數+-------------------------------------------------------+ | s | exp=255 | frac!=0 | +-------------------------------------------------------+
- 無窮大(Infinity)
階碼的值決定了該浮點數是規格化的、非規格化的、無窮大或者 NaN
轉換示例
舉例,將 1 個 float 數據轉換為 4Byte 的二進制數據存儲起來:
float a = 10.25F;
Decimal Binary
整數部分: 10 ====> 1010
小數部分: 0.25 ====> 0.01
科學計數法: 1.025 * 10^1 ====> 1010.01 = 1.01001 * 2^3
s = 0 ====> 0
E = 3 ====> E 包含偏置,IEEE 用 0111 1111 = 2^7 - 1 = 127 來表示 E = 0,所以當 E = 3 時,二進制表示為: 1000 0010 = 130 = 127 + 3
M = 1.01001 ====> 小數點前是 1,所以直接去掉,只保留小數部分,二進制表示為:0100 1000
s exp frac
10.25 ====> 0 | 1000 0010 | 01001000000000000000000
====> 0100 0001 0010 0100 0000 0000 0000 0000
需要注意的 3 個地方:
-
小數部分:0.25 = 1/4,即 2 的 -2 次方,也就是二進制的 1 小數點左移 2 位,所以得到的二進制表示為 0.01。需要注意的是:不是所有的十進制小數都能轉換為二進制小數,比如十進制的 0.3,所以只能取近似值,通過 2-n(n>0,n 為整數) 來接近 0.3
-
規格數:基數為 2,位數最高位為 1 的數為規格數,此時能夠表示的數據精度最高。通過階碼的大小來控制小數點的位置使得尾數變為規格數的過程,稱為規格化。在存儲時尾數只保存了小數部分。但是規格數無法表示 0。
-
階碼是有偏置的:2k-1 - 1
Java 中的應用
將給定的字節數組轉換為對應的浮點數,JDK 中 Float 和 Double 均提供了對應的方法來處理這種情況,以 Float 為例,使用的方法java.lang.Float#intBitsToFloat(int bits)
,將參數 bits 的位模式解析為浮點數,API 中的說明:
int s = ((bits >> 31) == 0) ? 1 : -1;
int e = ((bits >> 23) & 0xff);
int m = (e == 0) ?
(bits & 0x7fffff) << 1 :
(bits & 0x7fffff) | 0x800000;
浮點結果等於算術表達式 s·m·2e-150 的值
對應給定的浮點數,進行四舍五入可以使用java.math.BigDecimal#setScale(int, int)
方法,可以設定保留小數的位數,以及四舍五入的方式
class Scratch {
public static void main(String[] args) {
int intbis = 0B0100_0001_0010_0100_0000_0000_0000_0000;
System.out.println(Integer.toBinaryString(intbis));
System.out.println(Float.intBitsToFloat(intbis));
System.out.println(Integer.toBinaryString(Float.floatToIntBits(10.25F)));
}
}
參考
- 浮點數在計算機中存儲方式 - Robin Zhang - 博客園
- 深入理解計算機系統(2.7)---二進制浮點數,IEEE標准(重要) - 左瀟龍 - 博客園
- 從JDK源碼角度看Float - 掘金
- JDK API
- 計算機組成原理 第 2 版 P-229
- CSAPP 中文 第 3 版P-78
- 你應該知道的浮點數基礎知識 • cenalulu's Tech Blog update 2019-11-14 四 01:27 下午 圖畫的很好