IEEE浮点数标准

**IEEE 二進位浮點數算術標準**（**IEEE 754**）是 20 世紀 80 年代以來最廣泛使用的浮點數運算標準，為許多 CPU 與浮點運算器所採用。這個標準定義了表示浮點數的格式（包括負零 - 0）與反常值（denormal number），一些特殊數值（（無窮（Inf）與非數值（NaN）），以及這些數值的「浮點數運算子」；它也指明了四種數值修約規則和五種例外狀況（包括例外發生的時機與處理方式）。

IEEE 754 規定了四種表示浮點數值的方式：單精確度（32 位元）、雙精確度（64 位元）、延伸單精確度（43 位元以上，很少使用）與延伸雙精確度（79 位元以上，通常以 80 位元實做）。只有 32 位元模式有強制要求，其他都是選擇性的。大部分程式語言都提供了 IEEE 浮點數格式與算術，但有些將其列為非必需的。例如，IEEE 754 問世之前就有的 C 語言，現在包括了 IEEE 算術，但不算作強制要求（C 語言的 float 通常是指 IEEE 單精確度，而 double 是指雙精確度）。

該標準的全稱為 **IEEE 二進位浮點數算術標準（ANSI/IEEE Std 754-1985）** ，又稱 **IEC 60559:1989，微處理器系統的二進位浮點數算術**（本來的編號是 IEC 559:1989）。後來還有「與基數無關的浮點數」的「IEEE 854-1987 標準」，有規定基數為 2 跟 10 的狀況。現在最新標準是「ISO/IEC/IEEE FDIS 60559:2020」。

在六、七十年代，各家電腦公司的各個型號的電腦，有著千差萬別的浮點數表示，卻沒有一個業界通用的標準。這給資料交換、電腦協同工作造成了極大不便。IEEE 的浮點數專業小組於七十年代末期開始醞釀浮點數的標準。在 1980 年，英特爾公司就推出了單片的 8087 浮點數協處理器，其浮點數表示法及定義的運算具有足夠的合理性、先進性，被 IEEE 採用作為浮點數的標準，於 1985 年發布。而在此前，這一標準的內容已在八十年代初期被各電腦公司廣泛採用，成了事實上的業界工業標準。加州大學伯克利分校的數值計算與電腦科學教授威廉・卡韓被譽為「浮點數之父」。

## 浮點數剖析

一個浮點數 (Value) 的表示其實可以這樣表示：

$$
{\texttt {Value}}={\texttt {sign}}\times {\texttt {exponent}}\times {\texttt {fraction}}
$$

也就是浮點數的**實際值**，等於符號位（sign bit）乘以**指數偏移值**(exponent bias) 再乘以**分數值**(fraction)。

以下內容是 IEEE 754 對浮點數格式的描述。

### 本文表示位元的約定

把 W 個位元（bit）的資料，從記憶體位址低階到高端，以 0 到 W−1 編碼。通常將記憶體位址低階的位元寫在最右邊，稱作最低有效位（Least Significant Bit, LSB），代表最小的位元，改變時對整體數值影響最小的位元。聲明這一點的必要性在於 X86 體系架構是小端序的資料儲存。

對於十進位整數 N，必要時表示為 N10 以與二進位的數的表示 N2 相區分。

對於一個數，其二進位科學計數法表示下的指數的值，下文稱之為**指數的實際值**；而根據 IEEE 754 標準對指數部分的編碼的值，稱之為浮點數表示法**指數域的編碼值**。

### 整體呈現

二進位浮點數是以符號數值表示法的格式儲存 —— 最高有效位被指定為符號位（sign bit）；「指數部份」，即次高有效的 e 個位元，儲存指數部分；最後剩下的 f 個低有效位的位元，儲存「有效數」（significand）的小數部份（在非規約形式下整數部份默認為 0，其他情況下一律默認為 1）。

#### 指數偏移值

指數偏移值（exponent bias），即浮點數表示法中指數域的編碼值，等於指數的實際值加上某個固定的值，IEEE 754 標準規定該固定值為 $2^{e-1}-1$ ，其中的 $e$ 為儲存指數的位元的長度。

以單精度浮點數為例，它的指數域是 8 個位元，固定偏移值是 $2^{8-1}-1=128-1=127$ 。此為有號數的表示方式，單精度浮點數的指數部分實際取值是從 - 126 到 127（-127 和 128 被用作特殊值處理，見下方「非正規形式的浮點數」和「特殊值」）。例如指數實際值為 $17_{10}$ ，在單精度浮點數中的指數域編碼值為 $144_{10}$ ，即 $144_{10}=17_{10}+127_{10}$ 。

採用指數的實際值加上固定的偏移值的辦法表示浮點數的指數，好處是可以用長度為 $e$ 個位元的無符號整數來表示所有的指數取值，這使得兩個浮點數的指數大小的比較更為容易，實際上可以按照字典次序比較兩個浮點表示的大小。

這種**移碼**表示的指數部分，中文稱作**階碼**。

#### 正規形式的浮點數

如果浮點數中指數部分的編碼值在 $0<{\textrm {exponent}}\leqslant 2^{e}-2$ 之間，且在**科學表示法**的表示方式下，分數 (fraction) 部分**最高有效位**（即整數位）是 $1$ ，那麼這個浮點數將被稱為**正規形式的浮點數**。「規約」是指用唯一確定的浮點形式去表示一個值。

由於這種表示下的尾數有一位隱含的二進位有效數字，為了與二進位科學計數法的尾數（mantissa）相區別，IEEE754 稱之為**有效數**（significant）。

舉例來說，雙精度 (64-bit) 的規約形式浮點數在指數偏移值的值域為 $00000000001$ (11-bit) 到 $11111111110$ ，在分數部分則是 $000.....000$ 到 $111.....111$ (52-bit)

#### 非正規形式的浮點數

如果浮點數的指數部分的編碼值是 0，分數部分非零，那麼這個浮點數將被稱為**非正規形式的浮點數**。一般是某個數字**相當**接近零時才會使用非規約型式來表示。
IEEE 754 標準規定：**非正規形式的浮點數的指數偏移值比正規形式的浮點數的指數偏移值小 1**。例如，最小的正規形式的單精度浮點數的指數部分編碼值為 1，指數的實際值為 - 126；而非規約的單精度浮點數的指數域編碼值為 0，對應的指數實際值也是 - 126 而不是 - 127。實際上非正規形式的浮點數仍然是有效可以使用的，只是它們的絕對值已經小於所有的規約浮點數的絕對值；即所有的非規約浮點數比規約浮點數更接近 0。規約浮點數的尾數大於等於 1 且小於 2，而非規約浮點數的尾數小於 1 且大於 0。

除了規約浮點數，IEEE754-1985 標準採用非規約浮點數，用來解決填補絕對值意義下最小規格數與零的距離。（舉例說，正數下，最大的非規格數等於最小的規格數。而一個浮點數編碼中，如果 exponent=0，且尾數部分不為零，那麼就按照非規約浮點數來解析）非規約浮點數源於 70 年代末 IEEE 浮點數標準化專業技術委員會醞釀浮點數二進位標準時，Intel 公司對**漸進式下溢位**（gradual underflow）的力薦。當時十分流行的 DEC VAX 機的浮點數表示採用了**突然式下溢位**（abrupt underflow）。如果沒有漸進式下溢位，那麼 0 與絕對值最小的浮點數之間的距離（gap）將大於相鄰的小浮點數之間的距離。例如單精度浮點數的絕對值最小的規約浮點數是 $1.0\times 2^{-126}$ , 它與絕對值次小的規約浮點數之間的距離為 $2^{-126}\times 2^{-23}=2^{-149}$ 。如果不採用漸進式下溢位，那麼絕對值最小的規約浮點數與 0 的距離是相鄰的小浮點數之間距離的 $2^{23}$ 倍，可以說是非常突然的下溢位到 0。這種情況的一種糟糕後果是：兩個不等的小浮點數 X 與 Y 相減，結果將是 0。訓練有素的數值分析人員可能會適應這種限制情況，但對於普通的程式設計師就很容易陷入錯誤了。採用了漸進式下溢位後將不會出現這種情況。例如對於單精度浮點數，指數部分實際最小值是（-126），對應的尾數部分從 $1.1111\ldots 11$ , $1.1111\ldots 10$ 一直到 $0.0000\ldots 10$ , $0.0000\ldots 01$ ， $0.0000\ldots 00$ 相鄰兩小浮點數之間的距離（gap）都是 $2^{-126}\times 2^{-23}=2^{-149}$ ；而與 0 最近的浮點數（即最小的非正規數）也是 $2^{-126}\times 2^{-23}=2^{-149}$ 。

#### 特殊值

這里有三個特殊值需要指出：

1. 如果**指數**是 0 並且尾數的**小數部分**是 0，這個數 ±0（和符號位相關）
2. 如果**指數** = $2^{e}-1$ 並且尾數的**小數部分**是 0，這個數是 ±∞（同樣和符號位相關）
3. 如果**指數** = $2^{e}-1$ 並且尾數的**小數部分**非 0，這個數表示為非數（NaN）。

以上規則，總結如下：

#### 32 位元單精度

單精度二進制小數，使用 32 個位元儲存。

S 為符號位，Exp 為指數位，Fraction 為有效數位。
指數部分即使用所謂的**偏正值**形式表示，偏正值為實際的指數大小與一個固定值（32 位元的情況是 127）的和。採用這種方式表示的目的是簡化比較。因為，指數的值可能為正也可能為負，如果採用二補數表示的話，全體符號位 S 和 Exp 自身的符號位將導致不能簡單的進行大小比較。正因為如此，指數部分通常採用一個無符號的正數值儲存。單精度的指數部分是−126～+127 加上偏移值 127，指數值的大小從 1～254（0 和 255 是特殊值）。浮點小數計算時，指數值減去偏正值將是實際的指數大小。

單精度浮點數各種極值情況：

#### 64 位元雙精度

雙精度二進制小數，使用 64 個位元儲存。

S 為符號位，Exp 為指數位，Fraction 為有效數位。指數部分即使用所謂的偏正值形式表示，偏正值為實際的指數大小與一個固定值（64 位元的情況是 1023）的和。採用這種方式表示的目的是簡化比較。因為，指數的值可能為正也可能為負，如果採用二補數表示的話，全體符號位 S 和 Exp 自身的符號位將導致不能簡單的進行大小比較。正因為如此，指數部分通常採用一個無符號的正數值儲存。雙精度的指數部分是−1022～+1023 加上 1023，指數值的大小從 1～2046（0（2 進位全為 0）和 2047（2 進位全為 1）是特殊值）。浮點小數計算時，指數值減去偏正值將是實際的指數大小。

### 浮點數的比較

浮點數基本上可以按照符號位、指數域、尾數域的順序作字典比較。顯然，所有正數大於負數；正負號相同時，指數的二進位表示法更大的其浮點數值更大。

### 浮點數的捨入

任何有效數上的運算結果，通常都存放在較長的暫存器中，當結果被放回浮點格式時，必須將多出來的位元丟棄。
有多種方法可以用來執行捨入作業，實際上 IEEE 標準列出 4 種不同的方法：

* **捨入到最接近**：修約到最接近，在一樣接近的情況下偶數優先（Ties To Even，這是預設的修約方式）：會將結果修約為最接近且可以表示的值，但是當存在兩個數一樣接近的時候，則取其中的偶數（在二進位中是以 0 結尾的）。
* **朝 +∞方向捨入**：會將結果朝正無限大的方向捨入。
* **朝 -∞方向捨入**：會將結果朝負無限大的方向捨入。
* **朝 0 方向捨入**：會將結果朝 0 的方向捨入。

### 浮點數的運算與函式

#### 標準運算

下述函式必須提供:

* 加減乘除（Add、subtract、multiply、divide）。在加減運算中負零與零相等： $-0.0=0.0$
* 平方根（Square root）： ${\sqrt {x}}\geq 0(x\geq 0)$ ，另規定 ${\sqrt {-0.0}}=-0.0$
* 浮點餘數。返回值 $x-(round(x/y)*y)$ 。
* 近似到最近的整數 $round(x)$ 。如果恰好在兩個相鄰整數之間，則近似到偶數。
* 比較運算. -Inf < 負的規約浮點數數 < 負的非規約浮點數 < -0.0 = 0.0 < 正的非規約浮點數 < 正的規約浮點數 < Inf；
* 特殊比較： -Inf = -Inf, Inf = Inf, NaN 與任何浮點數（包括自身）的比較結果都為假，即 (NaN ≠ x) = false.

### 建議的函式與謂詞

* `copysign(x, y)`: `copysign(x, y)` 返回的值由 x 的不帶符號的部份和 y 的符號組成。因此 `abs(x)` 等於 `copysign(x, 1.0)`。`copysign` 可以對 NaN 正確操作，這是少有的幾個可以對 NaN 像普通算術一樣操作有效的函式之一。C99 新增了 `copysign` 函式。
* −x: 從涵義上指將 x 的符號反轉。當 x 是 ±0 或者 NaN 時，其涵義可能不同於 0-x.
* `scalb(y, N)`: 計算 _y_×2N（N 是整數），無需再計算 2N。C99 中對應的函式名是 `scalbn`.
* `logb(x)`: 計算 _x_ = 1._a_×2n（x ≠ 0, a ∈[0, 1)）中的 n. C99 新增了 `logb` 和 `ilogb` 函式。
* `nextafter(x,y)`: 沿 y 方向找最鄰近 x 的可表達浮點數。比如 `nextafter(0, 1)` 得到的是最小可表達的正數。C99 新增了 `nextafter` 函式。
* `finite(x)`: 判斷 x 是否有限，即−Inf < x < Inf. C99 新增了 `isfinite` 函式。
* `isnan(x)`: 判斷 x 是否是一個 NaN，這等價於 "x ≠ x". C99 新增了 `isnan` 函式。
* `x <> y`: 僅當 x <y 或者 x> y 時才為 True，其涵義是 NOT（x = y）。注意這不同於 "x ≠ y"。
* `unordered(x, y)`: 當 x 與 y 無法比較大小時為 True，比如說 x 或者 y 是一個 NaN. C99 中對應的函式名是 `isunordered`.
* `class(x)`: 區分 x 的浮點數類屬：發信 NaN、靜默 NaN、-Inf、負的規約浮點數，負的非規約浮點數，-0.0,0.0，正的非規約浮點數，正的規約浮點數，Inf。

## 精度

在二進制，第一個有效數字必定是「1」，因此這個「1」並不會儲存。

### 討論一

單精和雙精浮點數的有效數字分別是有儲存的 23 和 52 個位，加上最左手邊沒有儲存的第 1 個位，即是 24 和 53 個位。

　
: $\log 2^{24}=7.22$

　
: $\log 2^{53}=15.95$

由以上的計算，單精和雙精浮點數可以保證 7 位和 15 位十進制有效數字。

### 討論二

C++ 語言標準定義的浮點數的**十進位精度**(decimal precision)：十進位數字的位數，可被 (浮點數) 表示而值不發生變化。C 語言標準定義的浮點數的十進位精度為：十進位數字的位數 q，使得任何具有 q 位十進位數字的浮點數可近似表示為 b 進位的 p 位數字並且能近似回十進位表示而不改變這 q 位十進位數字

但由於相對近似誤差不均勻，有的 7 位十進位浮點數不能保證近似轉化為 32 位元浮點再近似轉化回 7 位十進位浮點後保持值不變：例如 8.589973e9 將變成 8.589974e9。這種近似誤差不會超過 1 位元的表示能力，因此 (24-1)\*std::log10 (2) 等於 6.92，下取整為 6，成為 std::numeric\_limits<float>::digits10 以及 FLT\_DIG 的值。std::numeric\_limits<float>::max\_digits10 的值為 9，含義是必須 9 位十進位數字才能區分 float 的所有值；也即 float 的最大表示區分度。

類似的，std::numeric\_limits<double>::digits10 或 DBL\_DIG 是 15， std::numeric\_limits<double>::max\_digits10 是 17

### 例子

以下的 C++ 程式，概略地展示了單精和雙精浮點數的精度。

## 相關條目

* 浮點數
* 單精度浮點數
* 雙精度浮點數
* MIPS
* TOP500

## 外部連結

* IEEE 754 references
* Let's Get To The (Floating) Point by Chris Hecker
* What Every Computer Scientist Should Know About Floating-Point Arithmetic by David Goldberg（頁面存檔備份，存於網際網路檔案館） - a good introduction and explanation.
* IEEE 854-1987（頁面存檔備份，存於網際網路檔案館） History and minutes
* Converter
* Another Converter
* Converter as MS-Windows program
* Comparing doubles in C++
* An Interview with the Old Man of Floating-Point（頁面存檔備份，存於網際網路檔案館）
* Coprocessor.info : x87 FPU pictures, development and manufacturer information

## 參考文獻