← SEARCHABLE ARCHIVE
Computer Science · 2026-08-08

IEEE 754 如何儲存實數?

理解 binary32、binary64、捨入、NaN 與累加誤差

INTERACTIVE READING MODE →

IEEE 754 如何儲存實數?

本文以教學為目的。硬體、編譯選項與語言實作可能改變細節,重要行為請在自己的環境中確認。

初學者常對 0.1 + 0.2 不完全等於 0.3 感到意外。電腦並不是不會加法,而是必須把無限多個實數放進有限的位元模式,因此大多數值只能以鄰近值表示。

IEEE 754 不只規定位元排列,也定義運算、捨入、無限大、NaN、漸進式下溢、轉換與例外狀態。本文會拆解 binary32 與 binary64,再整理比較、累加、金額與序列化時的實務原則。

定點數與浮點數

定點數事先固定小數點位置。若整數 12345 永遠代表兩位小數,實際值就是 123.45。它適合固定尺度,但同一格式難以同時涵蓋極小與極大的數。

浮點數類似科學記號。

6.25 = 1.5625 × 2²
0.15625 = 1.25 × 2⁻³

有效數字與指數分開儲存,範圍因此很寬,但相鄰值之間的距離並不固定。

IEEE 754 涵蓋的內容

IEEE 754-2019 規範二進位與十進位格式、運算、轉換、捨入、例外條件及預設處理。實作可以使用硬體、軟體或兩者混合。

名稱常見語言型別總位元符號指數儲存 fraction精度
binary32通常是 float32182324 位元
binary64通常是 double641115253 位元

「通常」很重要。C 與 C++ 的型別名稱本身不保證所有平台都使用這些格式。C++ 可透過 std::numeric_limits<T>::is_iec559 等屬性檢查實作。

binary32 的三個欄位

1 位元符號 | 8 位元指數 | 23 位元 fraction

一般有限值的公式是:

(-1)^s × (1.fraction)₂ × 2^(E - 127)

指數使用 127 的 bias。正規化二進位有效數字的開頭必定為 1,因此不必真的存下來。23 位元 fraction 因而提供 24 位元精度。binary64 的 bias 是 1023,52 位元 fraction 提供 53 位元精度。

手動編碼 6.25

6.25₁₀ = 110.01₂ = 1.1001₂ × 2²

符號為 0。儲存指數是 2 + 127 = 129,也就是 10000001₂。省略開頭的 1 後,fraction 是 1001000...0。

0 10000001 10010000000000000000000

十六進位為 0x40C80000。

float value = 6.25f;
std::uint32_t bits = std::bit_cast<std::uint32_t>(value);

特殊指數模式

指數fraction意義
全為 0全為 0帶符號的 0
全為 0非 0subnormal 數
1 到 254任意正規化有限數
全為 1全為 0正或負無限大
全為 1非 0NaN

因此 binary32 的正規化實際指數介於 -126 與 127。

正零與負零

指數和 fraction 都為 0 時仍保留符號,所以同時存在 +0 與 -0。一般比較認為兩者相等,但某些運算會保留方向。

double p = 0.0;
double n = -0.0;

std::cout << (p == n) << '\n';
std::cout << std::signbit(n) << '\n';
std::cout << 1.0 / p << '\n';
std::cout << 1.0 / n << '\n';

一般 IEEE 行為下,最後兩個結果分別為正無限大與負無限大。

subnormal 與漸進式下溢

如果靠近 0 時仍要求開頭為 1,最小正規化數與 0 之間會留下空隙。儲存指數為 0 且 fraction 非 0 時改用:

(-1)^s × (0.fraction)₂ × 2^-126

subnormal 數以逐步失去精度的方式接近 0。binary32 最小正規化正數約為 1.17549435 × 10^-38,最小 subnormal 正數約為 1.40129846 × 10^-45。

部分 SIMD 或 GPU 環境為了效能會把 subnormal 直接視為 0,數值程式必須確認此模式。

無限大與 NaN

指數全為 1 且 fraction 為 0 時表示無限大。溢位或非零有限值除以 0 可能在預設處理下產生無限大。

fraction 非 0 時表示 NaN,Not a Number。0.0 / 0.0 或負數的實數平方根都可能產生 NaN。

NaN 沒有一般順序,甚至與自己比較也不相等。

double nan = std::numeric_limits<double>::quiet_NaN();
std::cout << (nan == nan) << '\n';
std::cout << std::isnan(nan) << '\n';

應使用 std::isnan 判斷。quiet NaN、signaling NaN 與 payload 的支援依語言及硬體而異。

為什麼 0.1 不能精確表示

0.1 等於 1 / 10。最簡分數要成為有限二進位小數,分母的質因數只能有 2。10 還含有 5,因此二進位表示會循環。

0.1₁₀ = 0.000110011001100110011...₂

binary64 只保留 53 位元有效數字,其餘部分必須捨入。

std::cout << std::setprecision(20) << 0.1 << '\n';

常見結果為 0.10000000000000000555。0.1 + 0.2 的差異來自輸入與加法結果各自的捨入。

數值間距會隨大小增加

浮點數並未均勻分布。binary32 有 24 位元精度,因此到 16,777,216 為止的整數都能精確表示。再大的區域中,相鄰值開始相差 2 或更多。

float a = 16'777'216.0f;
float b = a + 1.0f;
std::cout << (a == b) << '\n';

一般 binary32 會輸出真,因為該大小附近沒有獨立的 a + 1。

epsilon 不是萬用容許值

std::numeric_limits<double>::epsilon() 是 1 與下一個可表示 double 的距離,不適合直接套用到所有大小。實務上常結合接近 0 時的絕對誤差與其他範圍的相對誤差。

bool almostEqual(double a, double b,
                 double absoluteTolerance = 1e-12,
                 double relativeTolerance = 1e-9)
{
    double difference = std::abs(a - b);
    if (difference <= absoluteTolerance)
    {
        return true;
    }
    double scale = std::max(std::abs(a), std::abs(b));
    return difference <= scale * relativeTolerance;
}

容許值必須根據座標、時間、物理量等問題領域決定。

捨入模式與五種例外狀態

精確結果落在兩個可表示值之間時,捨入方向決定儲存結果。常見預設是取最近值,完全居中時選擇最後一位為偶數的一側。

C 與 C++ 透過 <fenv.h> 和 <cfenv> 存取實作支援的捨入方向與狀態旗標。

五種 IEEE 例外狀態是 invalid operation、division by zero、overflow、underflow、inexact。這通常不是 C++ throw,而是產生預設結果並記錄旗標。

std::feclearexcept(FE_ALL_EXCEPT);
volatile double one = 1.0;
volatile double zero = 0.0;
volatile double result = one / zero;

if (std::fetestexcept(FE_DIVBYZERO))
{
    std::cout << "division by zero flag\n";
}

運算順序與累加誤差

每個中間結果都會捨入,所以有限精度加法不一定符合結合律。

double large = 1e16;
double small = 1.0;

double first = (large + small) - large;
double second = small + (large - large);

第一個式子可能失去 small。大量累加時可考慮 Kahan summation、相近大小配對相加或使用更寬的累加型別。

金額與精確資料

二進位浮點數很適合圖形、座標與許多科學計算,但通常不適合需要精確十進位捨入的金額。

  • 用整數儲存元、分等最小單位。
  • 使用 decimal 或定點型別。
  • 明確規定捨入的時機與方向。

識別碼和必須精確的計數器也應使用整數。

序列化與實作確認

跨越檔案或網路邊界時應明定 binary32 或 binary64、協定位元組順序、是否保留帶符號 0 與 NaN payload。文字往返可用 C++ max_digits10 決定足夠的位數。

std::numeric_limits 可檢查 is_iec559、radix、digits、min()、denorm_min() 與 max()。浮點型別的 min() 是最小正規化正值;最負的有限值應查 lowest()。

參考資料

總結

IEEE 754 使用符號、有效數字與指數,在有限位元內涵蓋極大範圍。代價是近似、依數值大小改變的間距、特殊值,以及每一步可能發生的捨入。理解同一套模型後,0.1 + 0.2、NaN 比較、大座標抖動與累加誤差就不再是互不相關的怪現象。