浮點數運算

在電腦科學中，浮點數運算（Floating-point arithmetic）是指以「浮點」（floating point，縮寫為 FP）格式來表示實數並進行運算的一種方式。浮點是一種實數的近似數值表示法，由一個有效數字（即尾數）乘以特定基數的整數次指數（冪數）構成。以這種表示法表示的數值稱為浮點數（floating-point number）。由於電腦無法精確表示所有實數，浮點數運算通常會伴隨著近似值處理或捨入。

電腦採用浮點數運算的主因在於其底層的二進位制運算邏輯。例如：4 ÷ 2 = 2，轉換為二進位即 100(2) 變成 010(2)，相當於將數值退一位數。同理，1.0 ÷ 2 = 0.5，二進位表示為 0.1(2)，也就是 $\frac{1}{2}$。以此類推，二進位的 0.01(2) 即為十進位的 $\frac{1}{2^{2}}$ = $\frac{1}{4}$ = 0.25。由於十進位制中許多小數（如 0.1）無法精確轉換為有限位數的二進位小數，因此系統只能以近似值的方式來表達。

這種表示法類似於基數為 10 的科學記數法。在電腦上，通常使用 2 為基數，一個浮點數 a 可由尾數 m 與指數 e 表示為：a = m × be。在任意一個這樣的系統中，可選擇一個基數 b 與精度 p（即使用多少位元來儲存）。m（即尾數）是格式如 ±d.ddd...ddd 的 p 位數（每一位數字介於 0 到 b-1 之間，包含 0 與 b-1）。若 m 的首位非 0，則稱該浮點數已正規化（Normalized）。實務上通常會使用獨立的符號位元（s 代表正或負）來表示正負號，此時 m 預設為正值。

這種設計基於對數值「表示範圍」與「精密度」之間的取捨：可以在某個固定長度的儲存空間內表示出某個實數的近似值。例如：一個指數範圍為 ±4 的 4 位十進位浮點數可以表示 43210、4.321 或 0.0004321，但沒有足夠的精確度來表示 432.123 和 43212.3（必須捨入近似為 432.1 和 43210）。當然，現代電腦實際使用的位數通常遠大於 4。

此外，浮點數表示法通常還包含一些特殊數值：+∞ 和 −∞（正負無窮大）以及 NaN（「Not a Number」，非數）。無窮大用於表示數值大到無法表示的狀況，NaN 則用來指示非法操作或無法定義的運算結果。

在記憶體中，無窮大（inf）的階碼（指數）為全 1，尾數為全 0；而 NaN 的階碼為全 1，尾數則不全為 0。

## 電腦的浮點數

浮點指的是帶有小數的數值，浮點運算即是小數的四則運算，常被用來測量電腦的運算速度。大部分電腦採用二進位（b=2）的表示方法。位元（bit）是衡量浮點數所需儲存空間的單位，通常為 32 位元或 64 位元，分別稱為單精度和雙精度。部分系統提供更大的浮點數，例如英特爾的浮點運算單元 Intel 8087協處理器（及其後續整合進 x86 處理器的產品）提供了 80 位元長的浮點數，用於儲存浮點運算的最後或中間結果。此外，也有一些系統提供 128 位元的浮點數（通常以軟體實現）。

## 浮點數的標準

電腦中使用的浮點數標準已被電氣電子工程師協會（IEEE）規範化為 IEEE 754。

## 舉例

π 的值可以表示為 π = 3.1415926...10（十進位）。當在一個支援 17 位尾數的電腦系統中表示時，它會近似為 0.11001001000011111 × 22。

## 浮點數運算

為了方便閱讀與呈現，以下的例子將採用十進位、有效位數 7 位數的浮點數（類似 IEEE 754 decimal32 格式），其底層原理不會隨進位制或有效位數而改變。此處的 s 表示尾數（有效數字），e 表示指數。

### 加減法

處理浮點數加法的簡單作法是將兩個浮點數調整至具有相同的指數。在以下例子中，第二個數的小數點左移了三位，使兩者的指數相同，隨後即可進行一般的加法運算：

```
  123456.7 = 1.234567 × 10^5
  101.7654 = 1.017654 × 10^2 = 0.001017654 × 10^5

  因此
  123456.7 + 101.7654 = (1.234567 × 10^5) + (1.017654 × 10^2)
                      = (1.234567 × 10^5) + (0.001017654 × 10^5)
                      = (1.234567 + 0.001017654) × 10^5
                      =  1.235584654 × 10^5
```

若用 e 和 s 來表示：

```
  e=5;  s=1.234567     (123456.7)
+ e=2;  s=1.017654     (101.7654)

  e=5;  s=1.234567
+ e=5;  s=0.001017654  (對齊移位後)

  e=5;  s=1.235584654  (實際的和：123558.4654)
```

這是未經修約的真實結果。隨後系統會將其四捨五入到 7 位有效數字，並在需要時進行正規化，最終結果為：

```
  e=5;  s=1.235585    (最後答案：123558.5)
```

加數的最低三位數（654）在結果中被丟棄了，這種現象稱為捨入誤差。在一些極端的例子中，兩個浮點數相加的和，可能會與其中較大的一方完全相等：

```
  e=5;  s=1.234567
+ e=−3; s=9.876543

  e=5;  s=1.234567
+ e=5;  s=0.00000009876543 (對齊移位後)

  e=5;  s=1.23456709876543 (真實的和)
  e=5;  s=1.234567         (四捨五入及正規化後)
```

在上述例子中，當兩數指數差距極大時，需要極高的精度才能獲得準確的結果。不過在二進位的加減法硬體設計中，只要利用保護位元（Guard bit）、捨入位元（Rounding bit）以及額外的黏滯位元（Sticky bit），即可確保捨入結果的正確性。

另一種嚴重失去有效數字的情形發生在兩個幾乎相等的數字相減時。在以下例子中，e = 5; s = 1.234571 和 e = 5; s = 1.234567 分別是有理數 123457.1467 和 123456.659 的近似值：

```
  e=5;  s=1.234571
− e=5;  s=1.234567

  e=5;  s=0.000004
  e=−1; s=4.000000 (四捨五入及正規化後)
```

如同Sterbenz 引理所證明的，即使因為漸進式下溢而出現下溢，浮點數的差仍可被精確計算。然而，原始兩數的真實差值約為 e = −1; s = 4.877000，這與浮點數運算得到的 e = −1; s = 4.000000 之間存在超過 20% 的誤差。在極端情況下，甚至所有的有效數字都可能完全消失。這種稱為災難性抵消的現象顯示，若草率假設計算結果的每一位數字都是可靠的，將面臨極大風險。這類誤差的處理與修正是數值分析領域的核心課題之一。

### 乘除法

若要進行乘法，將有效數字相乘，指數相加，再進行四捨五入及正規化即可：

```
  e=3;  s=4.734612
× e=5;  s=5.417242

  e=8;  s=25.648538980104 (真實乘積)
  e=8;  s=25.64854        (四捨五入後)
  e=9;  s=2.564854        (正規化)
```

除法則會將被除數與除數的有效數字相除，兩者的指數相減，接著再進行四捨五入及正規化。

乘除法運算通常不會面臨災難性抵消或大數吃小數的問題，但依然會產生捨入誤差。如果進行連續運算，這些誤差可能會逐步累積放大。實務上，要在硬體層面實現這類運算的數位邏輯相當複雜（例如布斯乘法算法與除法器設計）。

## 準確性

由於浮點數無法完美表達所有實數，浮點運算的結果與理論上的數學運算會有所差異，有時此差異甚至極為顯著。

例如，二進位浮點數無法精確表達 0.1 和 0.01。0.1 的平方既不是精確的 0.01，也不會是最接近 0.01 的可表達數值。單精度（24 位元有效數字）浮點數表示 0.1 的結果為 $e=-4$, $s=110011001100110011001101_{(2)}$，其十進位真值為：

0.100000001490116119384765625

此數值的平方為：

0.010000000298023226097399174250313080847263336181640625

然而，在該系統中最接近 0.01 的可表達數值實際上是：

0.009999999776482582092285156250

此外，浮點數也無法精確表達圓周率 $\pi$，因此在電腦上計算 $\tan\frac{\pi}{2}$ 並不會得到正無窮大，也不會觸發溢位。以下方的 C 語言程式碼為例：

```c
double pi = 3.1415926535897932384626433832795;
double z = tan(pi/2.0);
```

該計算結果為 16331239353195370.0；若改用單精度浮點數計算，結果則為 −22877332.0。同理，$\sin \pi \neq 0$。

由於浮點數計算過程中不可避免地會流失精度，浮點運算的代數性質與純數學運算並不相同，例如浮點數的加法與乘法皆不符合結合律與分配律。

### 歷史事故

早期 P5 世代的 60-100MHz 奔騰處理器在浮點運算單元（FPU）存在瑕疵。在極少數情況下，會導致除法運算的精確度大幅降低。這個硬體缺陷於 1994 年被發現，成為廣為人知的奔騰浮點除錯誤（Pentium FDIV bug）。此事件讓英特爾面臨巨大的公關危機，最終不得不啟動全面回收計畫來更換有缺陷的處理器。