跳轉到

1989 - Kahan 的 IEEE 754(浮點數的統一標準)#

案件摘要#

1985 年(標準公布;Kahan 1989 年獲圖靈獎),IEEE 754 浮點數標準誕生—— William Kahan(1933–) 是主要的設計者:

\[ \boxed{\text{浮點數 = 符號 × 指數 × 尾數(二進位的科學記號)}} \]
\[ (-1)^s \times 1.f \times 2^{e - 127} \quad\text{(32 位元單精度)} \]

Kahan 的三大貢獻: 1. 統一標準——在此之前,每家電腦的浮點數格式互不相容; 2. 正確的捨入(rounding)——最近偶數捨入(round-to-nearest-even); 3. 特殊值: \(\pm\infty\) 、NaN(非數)、非正規數(subnormal)。 1989 年,Kahan 獲得圖靈獎—— 今天的所有電腦(CPU、GPU、手機)都用 IEEE 754。

前因 -- 為什麼會有這個案子#

  • 浮點數的混亂(1950s–1980s): 每家電腦廠商有自己的浮點數格式:
\[ \text{IBM} \ne \text{DEC} \ne \text{Cray} \ne \text{CDC} \]
  • 同一個程式在不同機器上算出不同結果;
  • 有些機器的除法會「吃掉」有效位(Cray 的除法誤差)。
\[ \text{沒有標準} \Rightarrow \text{科學計算不可信}. \]
  • Kahan 的背景(1960s–1980s): Kahan 是加州大學柏克萊分校的數學教授(數值分析), 他對浮點數的誤差深惡痛絕:
\[ \text{浮點數的誤差} = \text{「隱藏的災難」(累積、放大)} \]

著名案例:1980 年代的愛國者飛彈(1991)因浮點累積誤差攔截失敗(28 人死亡); 1996 年 Ariane 5 火箭因數值溢位爆炸(5 億美元損失)。 - Intel 8087 的機會(1980): Intel 開發 8087 浮點協同處理器(1978–1980), 邀請 Kahan 擔任顧問—— Kahan 把他的「理想浮點數」設計進了 8087, 8087 的設計後來成為 IEEE 754。 - 標準化的過程(1980–1985): - Kahan 與 Intel 的工程師(包括 John Palmer)起草; - 與 DEC 的競爭:DEC 的 VAX 也有提案—— Kahan 的版本在「例外處理」上更完整; - 1985 年:IEEE 754 正式公布。

線索與推理 -- 數學式、程式、理論#

第一條線索:浮點數的格式(IEEE 754)#

32 位元單精度: | 位元 | 31 | 30–23 | 22–0 | |------|----|----|------| | 內容 | 符號 \(s\) | 指數 \(e\) (偏移 127) | 尾數 \(f\) (23 位) |

\[ \text{值} = (-1)^s \times 1.f \times 2^{e-127} \]

64 位元雙精度:1 + 11 + 52 位。 特殊值: | 指數 | 尾數 | 意義 | |------|------|------| | 全 0 | 0 | \(\pm 0\) (有符號零!) | | 全 0 | ≠ 0 | 非正規數(subnormal,漸進下溢) | | 全 1 | 0 | \(\pm\infty\) | | 全 1 | ≠ 0 | NaN(Not a Number) |

\[ \boxed{\text{非正規數:} 0.f \times 2^{-126} \quad\text{(避免「突然下溢」)}} \]

第二條線索:捨入的數學(Kahan 的關鍵設計)#

最近偶數捨入(round-to-nearest-even): - 正好在中間(如 1.5)時,捨入到偶數(2 而非 1); - 為什麼?無偏性——統計上捨入誤差會互相抵消(不累積偏差)。 例子: - 1.5 → 2(偶數);2.5 → 2(偶數!);3.5 → 4(偶數)。

\[ \boxed{\text{最近偶數捨入 = 統計無偏(誤差不累積)}} \]

對照「四捨五入」:永遠進位——統計上偏大(累積偏差)。

第三條線索:浮點數誤差的災難(案例)#

浮點數不能精確表示:

\[ 0.1 + 0.2 \ne 0.3 \quad\text{(二進位的 0.1 是無限循環)}. \]

災難案例: | 案例 | 年份 | 誤差 | 後果 | |------|------|------|------| | 愛國者飛彈 | 1991 | 0.1 秒的累積誤差 | 攔截失敗,28 人死亡 | | Ariane 5 火箭 | 1996 | 64 位元轉 16 位元溢位 | 爆炸,5 億美元 | | 溫哥華證交所 | 1982 | 捨入誤差累積 | 指數錯 50%! |

\[ \text{浮點誤差} = \text{隱藏的災難(累積、放大)}. \]

Python:IEEE 754 的數值驗證#

import numpy as np
import struct

# 1) 浮點數不能精確表示(二進位的悲劇)
print("浮點數的表示(二進位):")
print(f"  0.1 + 0.2 = {0.1 + 0.2!r}")
print(f"  0.1 + 0.2 == 0.3?{0.1 + 0.2 == 0.3}")
print(f"  0.1 + 0.2 - 0.3 = {0.1 + 0.2 - 0.3!r}(誤差殘留)\n")

# 2) IEEE 754 的位元結構(32 位元單精度)
def float_bits(x):
    """顯示 IEEE 754 的位元(32 位元)"""
    [b] = struct.unpack('>I', struct.pack('>f', x))
    sign = (b >> 31) & 1
    exp = (b >> 23) & 0xFF
    frac = b & 0x7FFFFF
    return sign, exp, frac

print("IEEE 754 位元結構(32 位元單精度):")
for x in [1.0, 2.0, 0.5, -1.0, 0.1]:
    s, e, f = float_bits(x)
    print(f"  {x}: 符號 = {s}, 指數 = {e} (127+{e-127}), 尾數 = {f:023b}")
print()

# 3) 特殊值:∞、NaN、非正規數
print("特殊值(Kahan 的設計):")
inf = np.float64('inf'); nan = np.float64('nan')
print(f"  1.0 / 0.0 = {np.float64(1.0) / np.float64(0.0)}(+∞)")
print(f"  -1.0 / 0.0 = {np.float64(-1.0) / np.float64(0.0)}(−∞)")
print(f"  0.0 / 0.0 = {np.float64(0.0) / np.float64(0.0)}(NaN)")
print(f"  ∞ - ∞ = {inf - inf}(NaN)")
print(f"  NaN == NaN?{nan == nan}(NaN 不等於自己!)")
# 非正規數
smallest_normal = np.finfo(np.float64).tiny
smallest_subnormal = np.nextafter(0.0, 1.0)
print(f"  最小正規數 = {smallest_normal!r}")
print(f"  最小非正規數 = {smallest_subnormal!r}(漸進下溢)\n")

# 4) 最近偶數捨入(Kahan 的關鍵設計)
print("最近偶數捨入(round-to-nearest-even):")
# 2.5 應捨入到 2(偶數),不是 3
import decimal
from decimal import Decimal, ROUND_HALF_EVEN
for x in ['1.5', '2.5', '3.5', '0.5']:
    d = Decimal(x).quantize(Decimal('1'), rounding=ROUND_HALF_EVEN)
    print(f"  {x} → {d}(捨入到偶數)")
print("  → 2.5 → 2(不是 3!):統計無偏(誤差不累積)\n")

# 5) 捨入誤差的累積(災難的模擬)
print("捨入誤差的累積(Kahan 總和法 vs 天真總和):")
def naive_sum(arr):
    """天真總和(誤差累積)"""
    s = 0.0
    for x in arr:
        s += x
    return s

def kahan_sum(arr):
    """Kahan 總和法(補償求和,誤差極小)"""
    s = 0.0
    c = 0.0   # 補償項
    for x in arr:
        y = x - c
        t = s + y
        c = (t - s) - y   # 捕捉丟失的位元
        s = t
    return s

# 大量小數的總和
n = 10**7
arr = [0.1] * n   # 0.1 不能精確表示
naive = naive_sum(arr)
kahan = kahan_sum(arr)
print(f"  {n} 個 0.1 的總和:")
print(f"  天真總和 = {naive!r}")
print(f"  Kahan 總和 = {kahan!r}")
print(f"  理論值 = {n*0.1!r}")
print(f"  天真誤差 = {abs(naive - n*0.1):.2e}")
print(f"  Kahan 誤差 = {abs(kahan - n*0.1):.2e}")
print("  → Kahan 總和法把誤差降到最低(1989 圖靈獎的方法)")
輸出:
浮點數的表示(二進位):
  0.1 + 0.2 = 0.30000000000000004
  0.1 + 0.2 == 0.3?False
  0.1 + 0.2 - 0.3 = 5.551115123125783e-17(誤差殘留)

IEEE 754 位元結構(32 位元單精度):
  1.0: 符號 = 0, 指數 = 127 (127+0), 尾數 = 00000000000000000000000
  2.0: 符號 = 0, 指數 = 128 (127+1), 尾數 = 00000000000000000000000
  0.5: 符號 = 0, 指數 = 126 (127+-1), 尾數 = 00000000000000000000000
  -1.0: 符號 = 1, 指數 = 127 (127+0), 尾數 = 00000000000000000000000
  0.1: 符號 = 0, 指數 = 123 (127+-4), 尾數 = 10011001100110011001101

特殊值(Kahan 的設計):
  1.0 / 0.0 = inf(+∞)
  -1.0 / 0.0 = -inf(−∞)
  0.0 / 0.0 = nan(NaN)
  ∞ - ∞ = nan(NaN)
  NaN == NaN?False(NaN 不等於自己!)
  最小正規數 = 2.2250738585072014e-308
  最小非正規數 = 5e-324(漸進下溢)

最近偶數捨入(round-to-nearest-even):
  1.5 → 2(捨入到偶數)
  2.5 → 2(捨入到偶數)
  3.5 → 4(捨入到偶數)
  0.5 → 0(捨入到偶數)
  → 2.5 → 2(不是 3!):統計無偏(誤差不累積)

捨入誤差的累積(Kahan 總和法 vs 天真總和):
  10000000 個 0.1 的總和:
  天真總和 = 999999.9998389754
  Kahan 總和 = 1000000.0
  理論值 = 1000000.0
  天真誤差 = 1.61e-04
  Kahan 誤差 = 0.00e+00
  → Kahan 總和法把誤差降到最低(1989 圖靈獎的方法)

結案 -- 後果與影響#

  • 統一標準(1985):
\[ \boxed{\text{IEEE 754 = 浮點數的「世界語」}} \]
  • 所有 CPU(x86、ARM、RISC-V、PowerPC)用 IEEE 754;
  • 所有 GPU(NVIDIA、AMD)用 IEEE 754(部分擴展);
  • 所有語言(C、Python、Java、JS)用 IEEE 754。
\[ \text{浮點的混亂(1980s)} \xrightarrow{\text{IEEE 754(1985)}} \text{統一} \xrightarrow{} \text{科學計算可信}. \]
  • 浮點數的「2008 修訂」(IEEE 754-2008):
  • 半精度(16 位元):AI 訓練的加速(Tensor Core 用半精度);
  • 四精度(128 位元):高精度科學計算;
  • 十進位浮點(decimal):金融計算(避免二進位的 0.1 問題)。
\[ \text{IEEE 754(1985)} \xrightarrow{\text{2008 修訂}} \text{AI 的半精度 + 金融的十進位}. \]
  • Kahan 總和法的應用(1965–):
\[ \text{Kahan 總和法(補償求和)} \xrightarrow{} \text{科學計算、統計、機器學習} \]
  • NumPy 的 pairwise summation(同思想的變種);
  • BLAS 函式庫:矩陣運算的誤差控制;
  • 金融:大額資金的計算(十進位 + Kahan)。
  • 災難的教訓(1991/1996):
\[ \boxed{\text{浮點誤差 = 隱藏的災難}} \]
  • 愛國者飛彈(1991):0.1 秒的累積誤差 → 28 人死亡;
  • Ariane 5(1996):64→16 位元溢位 → 5 億美元;
  • 現代:靜態分析工具(Polyspace、Astrée)檢查浮點誤差—— 安全關鍵系統(航空、醫療)的標準流程。
  • AI 時代的浮點(2010s–):
  • 半精度(FP16)與 bfloat16:深度學習的訓練加速—— Tensor Core 用 FP16/BF16,速度 2–8 倍;
  • 混合精度訓練(mixed precision):前向用半精度、累積用全精度—— Kahan 的「誤差控制」思想;
  • FP8(2022,NVIDIA):更激進的壓縮。
\[ \text{IEEE 754(1985)} \xrightarrow{\text{FP16/BF16(2017)}} \text{AI 的訓練加速} \xrightarrow{} \text{FP8(2022)}. \]
  • Kahan 的其他貢獻(1989 圖靈獎):
  • iNaN(indeterminate):不確定值的處理;
  • Berkeley 的數值分析課程——Kahan 教了數十年的「浮點數的陷阱」;
  • Kahan 的名言: > 「浮點數的算術是『巫術』——除非你懂它的誤差模型。」
  • 歷史定位:Kahan 是「浮點數的守護者」——
\[ \text{混亂(1980s)} \xrightarrow{\text{Kahan + IEEE 754(1985)}} \text{統一} \xrightarrow{} \text{AI 的半精度(2017)}. \]

一句話:沒有 IEEE 754,就沒有可信的科學計算與 AI。

關鍵人物與文獻#

  • W. Kahan:IEEE 754 的主要設計者;Kahan 總和法;1989 圖靈獎。
  • IEEE:IEEE Standard 754-1985;754-2008(修訂)。
  • D. Goldberg:What Every Computer Scientist Should Know About Floating-Point Arithmetic(1991)——浮點數的經典文章。
  • J. Palmer:Intel 8087 的工程師——IEEE 754 的推手。
  • 相關案件:1948-香農資訊理論.md、1973-UNIX作業系統.md、2018-深度學習三巨頭.md。