讀了一些 IEEE 754 實現的浮點數運算相關的文章
- IEEE 754 (IEEE 754-2019)
- Floating-point arithmetic
- Significand
- JavaScript 浮點數陷阱及解法
- 基礎野:細說浮點數
- 浮點數的深入分析
- What is the difference between quiet NaN and signaling NaN?
- JavaScript 里最大的安全的整數為什么是2的53次方減一?
- How numbers are encoded in JavaScript
- IEEE754規范的舍入方案怎么理解呢? - 海楓的回答 - 知乎
- ECMA 262
- IEEE 754 浮點數的表示精度探討
- Lecture Notes on the Status of IEEE Standard 754 for Binary Floating-Point Arithmetic - Kahan
- What Every Computer Scientist Should Know About Floating-Point Arithmetic
- 你應該知道的浮點數基礎知識
寫一寫讀后感 (以下如無特殊說明, 所有表達時中使用的符號均為相應英文的首字母;浮點數也專指二進制浮點數; 所有內容基於 IEEE 754-2019)
名稱 | radix | Significand bits (包括1位隱含的整數位) | Decimal digits (精度 = lg2^Significand bits) | 指數位 | 固定偏移值 | E min | E max |
---|---|---|---|---|---|---|---|
binary16 半精度浮點數 | 2 | 1 + 10 = 11 | lg2^11 ≈ 3.31 | 5 | 2^(5-1) - 1 = 15 | -14 = 1 - +15 | 2^(5-1) - 1 = +15 |
binary32 單精度浮點數 | 2 | 24 | 7.22 | 8 | 127 | −126 | +127 |
binary64 雙精度浮點數 | 2 | 53 | 15.95 | 11 | 1023 | −1022 | +1023 |
binary128 四精度浮點數 | 2 | 113 | 34.02 | 15 | 16383 | −16382 | +16383 |
binary256 八精度浮點數 | 2 | 237 | 71.34 | 19 | 262143 | -262142 | +262143 |
31
|
| 30 23 22 0
| | | | |
type -+-+------+-+---------------------+ value
特殊值 * 00000000 00000000000000000000000 ±0.0
min subnormal number * 00000000 00000000000000000000001 ±2^−23 × 2^−126 = ±2−149 ≈ ±1.4×10^-45
max subnormal number * 00000000 11111111111111111111111 ±(1−2^−23) × 2^−126 ≈ ±1.18×10^-38
min normal number * 00000001 00000000000000000000000 ±2^−126 ≈ ±1.18×10^-38
±1.0 * 01111111 00000000000000000000000 ±1.0
max normal number * 11111110 11111111111111111111111 ±(2−2^-23) × 2^127 ≈ ±3.4×10^38
特殊值 * 11111111 00000000000000000000000 ±∞
特殊值 0 11111111 10000000000000000000000 qNaN
特殊值 0 11111111 01000000000000000000000 sNaN
-----+-+------+-+---------------------+
| | | | |
| +------+-+---------------------+
| | | |
| | v |
| |the implicit bit|
| v v
| exponent fraction
v
sign
32 位單精度浮點數
浮點數存儲結構由三部分組成
- s符號位 sign
- 0 為正
- 1 為負
- e指數位 exponent
- 指數位 (偏移指數, 也稱階碼) 使用無符號整數表示, 范圍:
[0, 2^e - 1]
(偏移指數 = 實際指數 + 固定偏移值. 固定偏移值 =2^(e-1) - 1
)-
偏移指數:
0
表示非規約形式的浮點數或特殊值 ±0- 如果尾數的小數部分非 0, 表示非規約的浮點數
- 如果尾數的小數部分是 0, 表示特殊值 ±0 (和符號位相關)
-
偏移指數:
(0, 2^(e-1) - 1)
表示負指數 -
偏移指數:
2^(e-1) - 1
表示 ±0 指數 -
偏移指數:
(2^(e-1) - 1, 2^e - 1)
表示正指數 -
偏移指數:
2^e - 1
表示特殊值 ±∞ 或 特殊值NaN- 如果尾數的小數部分是 0, 表示特殊值 ±∞ (和符號位相關)
- 如果尾數的小數部分非 0, 表示特殊值 NaN
- qNaN (quiet NaN) 尾數的小數部分最高位為 1
- 將該最高位更改為 0 時, 可能得到特殊值 ±∞ (和符號位相關)
- sNaN (signaling NaN) 尾數的小數部分最高位為 0
- 將該最高位更改為 1 時, 得到 qNaN
- 通常 qNaN 用於使運算正常進行, sNaN 用於引發異常 (是否引發異常取決於 floating-point unit FPU 的狀態), 具體見 qNaN 與 sNaN 的區別
- qNaN (quiet NaN) 尾數的小數部分最高位為 1
-
- 使用偏移指數的優點: 可以用長度為 e 個單位的無符號整數來表示所有的實際指數, 這使得兩個浮點數的指數大小的比較更為容易
- 指數位 (偏移指數, 也稱階碼) 使用無符號整數表示, 范圍:
- m尾數位 mantissa / 有效數 significand (significand 也被叫做 mantissa, 它等於 the implicit bit + fraction)
- 規約與非規約浮點數
- 偏移指數: (0, 2^e - 1), 也即 [1, 2^e - 2], 表示規約形式的浮點數. 規約形式的浮點數隱含整數位為 1
- 偏移指數為 0 且尾數的小數部分非 0, 表示非規約形式的浮點數. 非規約形式的浮點數隱含整數位為 0
- 非規約形式的浮點數的偏移指數比規約形式的浮點數的偏移指數小 1
- 例如: 最小規約形式的單精度 (32位 = 1s + 8e + 23f) 浮點數的偏移指數為 1: (-126 + 127), 實際指數為 -126; 而非規約的單精度浮點數的偏移指數為 0: (-126 + 127 - 1), 對應的實際指數也是 -126 而不是 -127
- 使用隱含整數位的優點: 增加了 1 位浮點數的有效數長度
- 使用非規約形式的浮點數的優點 (漸進式下溢出 gradual underflow 的優點): 避免了突然式下溢出 abrupt underflow, 使得每個浮點數之間的距離 gap 一致 =
2^(-f + (1 - (2^(e-1) - 1)))
- 規約與非規約浮點數
浮點數的特點
-
只能精確表示可由二進制科學計數法
(-1)^s*m*2^e
表示的數值, m 超出精度的部分自動進一舍零這也是 0.1, 1.1 等浮點數無法被精確存儲的原因
// 以下使用 JavaScript 實現的雙精度浮點數, 精度為 15.95, 約 16 位有效數字 有效數字是指在一個數中,從該數的第一個非零數字算起的所有數字的長度 (0.1).toPrecision(16); // "0.1000000000000000" 對於0.1, 有效數為16位 (0.1).toPrecision(17); // "0.10000000000000001" 對於0.1, 有效數為17位 (0.1).toPrecision(18); // "0.100000000000000006" (0.1).toPrecision(22); // "0.1000000000000000055511" (1.1).toPrecision(16); // "1.100000000000000" 對於1.1, 有效數為16位 (1.1).toPrecision(17); // "1.1000000000000001" 對於1.1, 有效數為17位 (1.1).toPrecision(18); // "1.10000000000000009" (1.1).toPrecision(22); // "1.100000000000000088818" 1.000000000000001; // 1.000000000000001 有效位數為16位 1.0000000000000001; // 1 第17位的1被舍去了
-
規約形式浮點數的最大值:
±(1 + (2^-1 + 2^-2 + ... + 2^-f)) * 2^(2^(e-1) - 1)
<=>±(2 - 2^-f) * 2^(2^(e-1) - 1)
.對於雙精度浮點數來說, 其規約最大值為:
±(2- 2^-52) * 2^1023 === ±1.7976931348623157e+308
,1.7976931348623157e+308
也是 JavaScript 中Number
對象靜態屬性MAX_VALUE
的值 (注意它不是一個安全整數), 大於該值即表示 ∞ (Number.MAX_VALUE * 1.000000000000001 === Infinity; Number.MAX_VALUE + 1e+292 === Infinity
) -
非規約形式浮點數的最小值:
±2^(-f + (1 - (2^(e-1) - 1)))
.對於雙精度浮點數來說, 其非規約最小值為:
±2^(-52-1022) === ±5e-324
,5e-324
也是 JavaScript 中Number
對象靜態屬性MIN_VALUE
的值, 小於該值即表示 0 -
浮點數的安全整數范圍 (安全整數范圍指浮點數與整數可以一對一):
[-(2^m - 1), 2^m - 1]
. 對於雙精度浮點數來說, 安全整數為:±2^53 - 1 === ±9007199254740991
, 共有 16 位有效數字. 非安全整數的特點是: 一個浮點數對應多個實數, 如下圖所示:這也是 JavaScript 中
Number
對象靜態屬性MAX_SAFE_INTEGER
和MIN_SAFE_INTEGER
的值2^53 + 1
用二進制表示為:1000...0001
(共 54 位, 兩個一分別是 2^53 和 2^0), 轉為二進制科學表示法為:1.000...0001 * 2^53
(尾數的小數部分共 53 位), 由於雙精度浮點數的尾數最多能保存 52 位二進制, 因此最后的 1 注定被舍去.2^53 + 1
與2^53
存儲一致, 也即2^53 === 2^53 + 1
, 2^53 不是一個安全整數 -
浮點數可以准確表示的整數 (除了安全整數范圍內的數), 以雙精度浮點數為例: 由於尾數的小數部分最多只能存儲 52 位, 因此大於浮點數的安全整數范圍並且還要精確表示的整數有兩類
-
一類是在指數的范圍內增加指數的大小, 且保持尾數始終為
1.0
的數:2^54
,2^55
,2^56
, ...,2^1023
, 這些都是精確的數 -
另一類是指數與尾數同時更改的數: 對於 [2^53, 2^54) 之間的數, 因為其尾數的小數部分共有 53 位, 第 53 位注定會被舍去, 那我們只要保證數的第 53 位為 0, 那么該數即可精確保證, 也即在 [2^53, 2^54) 之間的偶數才能保證第 53 位為 0, 才能精確表示;
同理, [2^54, 2^55) 之間的數, 第 53 位和第 54 位注定被舍去, 那我們只要保證數的第 53 位和第 54 位都為 0, 那么該數即可精確保證, 也即在 [2^54, 2^55) 之間, 間距變為 4 的倍數, 這樣才能保證第 53 位和第 54 位都為 0, 才能精確表示, 以此類推
-
浮點數的比較
- 浮點數基本上按照符號位, 指數域, 尾數域的順序作比較. 顯然, 所有正數大於負數; 正負號相同時, 指數的二進制表示法更大的其浮點數值更大; 符號位和指數位相同的, 尾數更大的其浮點數值更大
浮點數的五種舍入方式 (對於二進制浮點數來說是四種舍入方式)
-
舍入到最近的值
-
舍入到最近的值, roundTiesToEven: 會將結果舍入為最接近且可以表示的值. 如果一樣接近, 選擇最低有效位為偶數的 (尾數的最低位二進制為 0); 如果最低有效位也相同 (比如 10 進制浮點數 9.5, 9 和 1*e^1 的最低有效位都為奇數), 則選擇量級更大的 (對於正數, 越大量級越大; 對於負數, 越小量級越大), 這通常是二進制浮點數的默認舍入方式, 也是十進制浮點數推薦的舍入方式
// 單精度浮點數(尾數的小數部分23位) Round to nearest, ties to even 示例 // 9.5 表示為二進制科學計數法的浮點數, 舍入一位 9.5 => 1001.1 => 1.0011 * 2^3 // 離它最近的兩個數分別為 10 和 9 10 => 1010 => 1.010 * 2^3 9 => 1001 => 1.001 * 2^3 // 10 與 9 離 9.5 的距離分別為 1.010 * 2^3 - 1.0011 * 2^3 = 0.0001 * 2^3 // 0.1 1.0011 * 2^3 - 1.001 * 2^3 = 0.0001 * 2^3 // 0.1 // 距離一樣接近, 比較其最低有效位 // 1.010 * 2^3 的最低有效位為 even // 1.001 * 2^3 的最低有效位為 odd // 因此, 9.5 舍入一位后是 10 而不是 9 // 0.95 表示為二進制科學計數法的浮點數, 舍入一位 0.95 => 0.11 1100 1100 1100 1100 1100 1 // 離他最近的兩個數分別為 1 和 0.9 1 => 1.00 0000 0000 0000 0000 0000 0 0.9 => 0.11 1001 1001 1001 1001 1001 1 // 1 與 0.9 離 0.95 的距離分別為 1.00 0000 0000 0000 0000 0000 0 - 0.11 1100 1100 1100 1100 1100 1 = 0.00 0011 0011 0011 0011 0011 1 0.11 1100 1100 1100 1100 1100 1 - 0.11 1001 1001 1001 1001 1001 1 = 0.00 0011 0011 0011 0011 0011 0 0.00 0011 0011 0011 0011 0011 1 > 0.00 0011 0011 0011 0011 0011 0 // 0.9 離 0.95 更近一點, 因此 0.95 舍入一位后是 0.9 而不是 1
-
舍入到最近的值, roundTiesToAway: 會將結果舍入為最接近且可以表示的值. 如果一樣接近, 選擇量級更大的 (對於正數, 越大量級越大; 對於負數, 越小量級越大), 二進制浮點數不需要該舍入方式, 而十進制浮點數應該提供該舍入方式供用戶選擇
-
-
定向舍入
- 朝 +∞ 方向舍入, roundTowardPositive, 也稱為向上取整 ceil: 會將結果朝正無窮大的方向舍入
- 朝 -∞ 方向舍入, roundTowardNegative, 也稱為向下取整 floor: 會將結果朝負無窮大的方向舍入
- 朝 0 方向舍入, roundTowardZero, 也稱為截斷 truncation: 會將結果朝 0 的方向舍入
-
JavaScript 中
Math.round(x)
靜態方法的舍入方式- 返回最接近 x 的整數. 如果兩個整數相等的接近, 那么返回更接近 +∞ 的; 如果已經是整數了, 那么返回它自身
二進制浮點數的異常處理
- 無效運算, Invalid operation. 數學上未定義的運算, 例如 0/0, sqrt(-1.0) 等, 默認返回 qNaN
- 被零除, Division by zero. 除數為零,被除數為有限的非零數字, 默認返回 ±∞
- 上溢, Overflow. 運算產生的結果超出指數能表達的范圍 E max, 默認返回 ±∞
- 下溢, Underflow. 運算產生的結果超出了規約浮點數 normal numbers 的范圍, 默認返回非規約浮點數 subnormal numbers 或 0 (遵循舍入規則)
- 不精確, Inexact. 運算產生的結果無法精確表示, 默認返回精確結果的舍入值 (遵循舍入規則)
在線轉換 (二進制與十進制) 浮點數的鏈接
- IEEE-754 Floating Point Converter - Single precision 32-bit
- IEEE754 Single precision 32-bit
- IEEE754 Double precision 64-bit