← ブログに戻る記数法

IEEE 754 浮動小数点:ビットから数値へ

IEEE 754 の binary32 と binary64 を解説します。符号、バイアス付き指数、後続仮数部、特殊値、丸め、そして非結合な演算を読み解きます。

DennyDenny
読了まで約12分
Chalkboard feature illustration for “IEEE 754 浮動小数点:ビットから数値へ”

要約: 正規化された 2 進値に対して、IEEE 754 は (−1)s(1+f)2e−bias(-1)^s(1+f)2^{e-bias} を使います。すなわち符号ビット、バイアス付き指数、暗黙の先頭 1 を伴う後続仮数部です。予約された指数パターンは符号付きゼロ、サブノーマル、無限大、NaN を表します。ほとんどの厳密な実数演算結果は有限の形式に丸めなければならないため、浮動小数点の加算と乗算は一般に結合的ではありません。

範囲とスケールが分かっている場合、固定小数点の 2 進表現は有用です。しかし、1 回の計算で何桁もの大きさの差がある値を扱うと不便になります。たとえば小数部 16 ビットの符号付き 32 ビット固定小数点値は、刻み幅が 2−162^{-16}、範囲が約 −32,768-32{,}768 から 32,767.9999832{,}767.99998 です。

浮動小数点では、一部のビットを指数に割り当て、基数点を値の大きさに応じて移動させます。IEEE 754 は 1985 年に初めて発行され、現在有効な改訂版は IEEE 754-2019 です。この規格は 2 進および 10 進形式、演算、丸め、例外、交換時の動作を定義します。実装はハードウェア、ソフトウェア、またはその組み合わせでよいため、規格準拠であっても、すべての CPU があらゆる形式をシリコン上に備えるとは限りません。

なぜ浮動小数点が必要なのか?

科学的記数法は紙の上で同じ問題を解決します。6.674×10−116.674 \times 10^{-11} は小さな有効数字と大きな指数を分けて表します。IEEE 754 は、各部分に何ビットを与え、どのように符号化するかを厳密に定めた 2 進の科学的記数法です。

この形式は、二つの設計上の要請によって形作られています。

  1. 規定された符号化と演算。 数学上の値とビットパターンは一対一ではありません。ゼロには二つの符号があり、NaN には多数のペイロード符号化があります。規格は、演算と比較がそれらをどう扱うかを定めます。
  2. 実装可能な算術。 指数の比較と仮数の桁合わせは、丸めと例外の動作が規定された、有限のハードウェアまたはソフトウェアのアルゴリズムに対応しなければなりません。

両方を実現するためのトレードオフ、すなわちバイアス付き指数、暗黙の先頭ビット、特殊用途の指数コードが、IEEE 754 特有の形を生み出しています。

単精度のレイアウト(32 ビット)

| 31 | 30 ............ 23 | 22 ........................... 0 |
|  s |       e (8 bits)   |          m (23 bits)             |
| 1  |                    |                                  |
  • 符号ビット ss:1 ビット。0 は非負、1 は負を意味します。
  • 指数 ee:8 ビット。バイアス 127 を持つ符号なし整数として格納されます。
  • 後続仮数部 mm:23 ビット。正規数では、暗黙の先頭 1 の後ろに続く小数部を格納します。

正規化された数(一般的な場合)では、値は次のとおりです。

v=(−1)s⋅(1+m223)⋅2e−127v = (-1)^s \cdot \left(1 + \frac{m}{2^{23}}\right) \cdot 2^{e - 127}

仮数中の「1.1.」は暗黙です。すべての正規化 2 進数は、指数を選ぶことで先頭を 1 として書けるため、この形式では 1 ビットを節約できます。

倍精度のレイアウト(64 ビット)

構造は同じですが、フィールドが広くなります。

| 63 | 62 ........... 52 | 51 ............................. 0 |
|  s |     e (11 bits)   |          m (52 bits)              |
  • 符号ビット ss:1 ビット。
  • 指数 ee:11 ビット、バイアスは 1023。
  • 後続仮数部 mm:52 ビット。

値(正規化時):

v=(−1)s⋅(1+m252)⋅2e−1023v = (-1)^s \cdot \left(1 + \frac{m}{2^{52}}\right) \cdot 2^{e - 1023}

範囲はおよそ ±1.8×10308\pm 1.8 \times 10^{308}、有効な 10 進桁数は約 15~17 桁です。

指数にバイアスをかける理由

指数を 2 の補数ではなく e−biase - bias として格納することには、微妙ながら重要な利点があります。浮動小数点の比較を、ビットパターンに対する整数比較(符号ビットを反転して)に帰着できるのです。二つの非負浮動小数点数を 32 ビット符号なし整数として比較すると、大きい浮動小数点数が大きい整数になります。2 の補数の指数では、正と負の指数の境界でこの単調な関係が崩れます。

指数フィールドがすべてゼロのパターンはゼロとサブノーマル用に予約され、すべて 1 のパターンは無限大と NaN 用に予約されます。binary32 では、通常の指数フィールド 1~254 は、バイアスを除いた指数 −126-126~+127+127 に対応します。

特殊な場合

指数ビット後続仮数部ビット意味
すべてゼロすべてゼロ±0\pm 0(符号ビットがどちらかを決める)
すべてゼロゼロ以外サブノーマル
中間の任意の値任意の値正規化数
すべて 1すべてゼロ±∞\pm \infty
すべて 1ゼロ以外NaN(非数)

ゼロには二つの符号化(+0+0 と −0-0)があります。IEEE の比較では等しいとみなされますが、一部の演算では符号の異なる結果を生みます(例:1/+0=+∞1 / +0 = +\infty、1/−0=−∞1 / -0 = -\infty)。

サブノーマル(以前はデノーマルとも呼ばれました)は、ゼロと最小の正規数の間の隙間を埋めます。暗黙の先頭 1 は 0 になり、binary32 では実効指数として −126-126 を使います。これにより段階的アンダーフローが得られます。サブノーマル範囲では絶対的な刻み幅が一定に保たれる一方、ゼロに近づくほど相対精度は低下します。

無限大はオーバーフローまたはゼロ除算で生じます。算術では直感的な形で伝播します。たとえば、∞+1=∞\infty + 1 = \infty、∞×0=NaN\infty \times 0 = \text{NaN} です。

NaNは、−1\sqrt{-1}、0/00 / 0、∞−∞\infty - \infty のような無効な演算を示します。NaN は伝播性を持ち、NaN を含む算術演算の結果は NaN です。重要なのは、IEEE 754 の比較では NaN≠NaN\text{NaN} \neq \text{NaN} となることであり、テストスイートが == ではなく isnan() を使う理由です。

演習例:0x40490FDB を復号する

有名な 32 ビット定数です。2 進数では次のようになります。

0x40490FDB = 0100 0000 0100 1001 0000 1111 1101 1011

フィールドに分割すると次のとおりです。

  • 符号:0 — 正
  • 指数:1000 0000 = 12810128_{10} — 実際の指数は 128−127=1128 - 127 = 1
  • 後続仮数部:100 1001 0000 1111 1101 1011

仮数は 1.(trailing bits)21.\text{(trailing bits)}_2、すなわち次の値です。

1.1001001000011111101101121.10010010000111111011011_2

格納された小数部を計算します。

mfrac=0b10010010000111111011011223=4,788,1878,388,608≈0.5707963m_{frac} = \frac{0b10010010000111111011011}{2^{23}} = \frac{4{,}788{,}187}{8{,}388{,}608} \approx 0.5707963

したがって仮数は 1.57079631.5707963 です。これに 212^1 を掛けます。

v≈3.1415927v \approx 3.1415927

これは単精度精度での π\pi です。定数 0x40490FDB は π\pi の標準的な IEEE 754 単精度符号化であり、世界中の数値計算ライブラリ、GPU シェーダー、試験問題に登場します。

演習例:5.75 を符号化する

5.755.75 を単精度 IEEE 754 に変換します。

手順 1:2 進数に変換します。 5=10125 = 101_2。0.75=0.5+0.25=0.1120.75 = 0.5 + 0.25 = 0.11_2。合わせると、5.75=101.1125.75 = 101.11_2 です。

手順 2:正規化します。 左側にちょうど 1 桁だけ残るまで 2 進小数点を左へ移動します。

101.112=1.01112×22101.11_2 = 1.0111_2 \times 2^2

したがって実際の指数は 22 です。

手順 3:各フィールドを符号化します。

  • 符号:正なので s=0s = 0。
  • 指数:実際の 22 + バイアス 127127 = 129129 = 1000 0001。
  • 後続仮数部:先頭の 1 を除き、次の 23 ビットを格納します。すなわち 0111 の後に 19 個のゼロ、または 01110000 00000000 0000000 です。

手順 4:組み立てます。

0 | 10000001 | 01110000000000000000000

16 進数では 0x40B80000 です。このパターンを復号手順で逆にたどれば、5.75 を正確に復元できることを確認できます。

5.75 には正確な 2 進表現がある点に注意してください。その小数部は 2 の負のべき乗の和(2−1+2−22^{-1} + 2^{-2})です。しかしほとんどの 10 進小数はそうではありません。単精度での 0.10.1 は 0x3DCCCCCD で、復号すると 0.1000000014901...0.1000000014901...、約 10−910^{-9} の誤差があります。この小さな誤差が、初学者が遭遇する「0.1+0.2≠0.30.1 + 0.2 \neq 0.3」という驚きの根本原因です。

丸め方向

IEEE 754-2019 は、五つの丸め方向属性を定めています。

  • 最近接丸め、偶数へのタイ。(既定値)表現可能な値のうち近い方に丸め、ちょうど中間なら最下位仮数ビットが 0 となる値を選びます。
  • 最近接丸め、ゼロから遠ざかる方向へのタイ。 この属性は特に 10 進演算と結び付いており、すべての 2 進浮動小数点環境で公開されているわけではありません。
  • ゼロ方向への丸め。(切り捨て)
  • +∞+\infty 方向への丸め。(切り上げ)
  • −∞-\infty 方向への丸め。(切り下げ)

既定のモードは統計的な偏りを最小化します。単純な「五入」では多数の値を累積したときにわずかな上方バイアスが生じますが、偶数丸めは平均するとそれを相殺します。

アプリケーションが選択できる方向は、言語、ランタイム、コンパイラ、ハードウェア環境に依存します。再現性も、選択した丸め方向だけでなく、評価順序、中間精度、融合演算、ライブラリ関数、サブノーマルの扱いに左右されます。

浮動小数点演算が結合的でない理由

IEEE 754 単精度の三つの値を考えます。a=1020,b=−1020,c=1a = 10^{20}, b = -10^{20}, c = 1 です。

(a+b)+c(a + b) + c:まず a+b=0a + b = 0 となります(相殺は正確です)。次に 0+c=10 + c = 1。最終結果は 11 です。

a+(b+c)a + (b + c):まず b+cb + c を計算します。bb の指数は約 67、cc の指数は 0 です。仮数を桁合わせすると、cc は binary32 の精度をはるかに超えた位置へ移動します。丸め後は b+c=bb+c=b です。次に a+b=0a+b=0 となります。

二つの括弧付けで異なる答えになるため、浮動小数点の加算は結合的ではありません。コンパイラが式を再結合してよいかは、言語規則、実装、最適化フラグ、浮動小数点環境に依存します。再現性が重要なコードでは、あるコンパイラフラグを普遍的な保証とみなすのではなく、その選択を文書化し、実際のツールチェーンで検証すべきです。

同じ性質は、減算(相殺のケース)、乗算(極めて小さな積がゼロへアンダーフローする可能性)、除算(極めて小さな数での除算が無限大へオーバーフローする可能性)にも当てはまります。

ハードウェア:FPU

ハードウェアの浮動小数点加算器は一般に次のような段階を実行します。FPU を持たないプロセッサでも、同じ意味論をソフトウェアで実装できます。

  1. 両オペランドのフィールドを復号します。
  2. 指数を比較し、指数の小さい方の仮数を差分だけ右へシフトします。丸めのためにガード、ラウンド、スティッキービットを保持します。
  3. 桁合わせした仮数を加算または減算します(幅広の キャリールックアヘッド型加算器 を使用)。
  4. 先頭の 1 が現れるまで左シフトして結果を正規化し、それに応じて指数を調整します。
  5. 有効な丸めモードに従って丸めます。
  6. 結果を IEEE 754 形式に符号化します。

データパスには、指数比較、仮数の桁合わせ、加算/減算ロジック、先頭ゼロ検出、正規化、丸め、特殊ケース処理、例外報告が必要です。RISC-V の F 拡張は binary32 命令を定義し、D は binary64 を追加します。どちらも任意であり、標準形式であることと必須ハードウェアであることの違いを示しています。

仮数の経路は 2 進演算の技法を再利用しますが、FPU は単に余分な配線を備えた整数 ALU ではありません。丸め、段階的アンダーフロー、NaN、無限大、符号付きゼロ、例外は、そのアーキテクチャ上の動作の一部です。

より広い形式

単精度と倍精度以外に、IEEE 754 は次を定義しています。

  • 半精度(binary16):符号 1 + 指数 5 + 後続仮数部 10 ビット。グラフィックスと低精度の数値ワークロードで使われます。
  • 四倍精度(128 ビット):1 + 15 + 112。まれですが、数値解析や一部の金融計算で使われます。
  • bfloat16(IEEE 754 の基本交換形式ではありません):1 + 8 + 7。binary32 に似た指数範囲を、はるかに低い精度で保持し、機械学習で広く使われています。

それぞれの種類は、対象ワークロードに合わせて範囲と精度をトレードオフします。

よくある落とし穴

  • 誤差モデルなしに == を使うこと。 一部の不変条件や離散値には厳密比較が正しい一方、近似結果には領域固有の絶対/相対許容差または最終桁単位の方針が必要です。
  • NaN != NaN を忘れること。 NaN の検出には isnan() を使います。
  • 結合性を仮定すること。 和の順序を変えると答えが変わることがあります。コンパイラフラグは安全でない再結合を有効にし得るため、使う前に理解してください。
  • 10 進と 2 進の直感を混同すること。 0.10.1 は有限の厳密な 2 進表現を持ちません。10 進の業務規則では、スケーリングした整数、10 進浮動小数点、または BCD を検討し、それでも必要な各境界で丸めを定義します。
  • サブノーマルの性能を想定すること。 サブノーマルをフルスピードで処理するプロセッサもあれば、ペナルティがあるものやゼロフラッシュモードをサポートするものもあります。対象を測定し、正しさに必要な場合は段階的アンダーフローを維持してください。

より広い CPU アーキテクチャとのつながり

レジスタ構成は ISA 固有です。整数用と浮動小数点用のレジスタファイルを分けるアーキテクチャもあれば、状態の一部を重複または統合するものもあります。ロード、ストア、変換、移動、呼び出し規約がその境界を定義します。

より幅の広い演算では、整数側は、対象に合わせて拡大・最適化された 8 ビット加算器 に関連する構造に依存します。浮動小数点側には、桁合わせ、正規化、丸め、例外、特殊値の処理が加わります。

次に読むもの

固定幅の符号付き整数については 2 の補数の解説 を、桁指向の 10 進符号化については BCD:2 進化 10 進数の基礎 を続けて読んでください。

ビットレベルで実験するには、仮数演算向けの ALU 風データパスを使い、桁合わせ、正規化、ガード/ラウンド/スティッキービット、特殊ケースを個別の段としてモデル化します。

規範となる参照資料は、現在有効な 浮動小数点演算に関する IEEE 754-2019 規格 です。

アイデアを実際に動く回路にしましょう。