固定小数点数(私たちが普段使っているような、小数点の位置が決まっている形)を、コンピュータが扱う「浮動小数点形式」に変換するプロセスは、実は「10進数の数値を、科学的表記法($1.23 \times 10^{2}$ など)の2進数版に書き換える作業」そのものです。
具体的に、$9.625$ という数値を、32ビットの浮動小数点数(float)に変換する手順を追ってみましょう。
ステップ1:2進数の「固定小数点」に直す
まずは、整数部と小数部をそれぞれ2進数に変換します。
-
整数部 ($9$): $9 = 8 + 1$ なので、
1001 -
小数部 ($0.625$):
- $0.625 \times 2 = 1.25$ → 1
- $0.25 \times 2 = 0.5$ → 0
- $0.5 \times 2 = 1.0$ → 1
- よって、
.101
繋げると、固定小数点表示の2進数 1001.101 が完成します。
ステップ2:正規化(小数点を移動させる)
ここが「浮動」の由来です。小数点を動かして、「$1. \dots$」 の形にします。
-
1001.101の小数点を左に 3つ 動かします。 - $1.001101 \times 2^{3}$
これで、浮動小数点数の「種」となるパーツが揃いました。
- 符号: プラス
-
仮数(数字の並び):
001101(先頭の $1$ は省略するのがルール) -
指数(動かした数):
3
ステップ3:各パーツをビットに詰め込む
IEEE 754形式(float)の32ビットに当てはめていきます。
① 符号部 (1bit)
- プラスなので
0です。
② 指数部 (8bit)
- ここには「バイアス(下駄)」という仕組みがあります。
floatの場合は 127 を足します。 - $3 + 127 = 130$
- $130$ を2進数にすると
10000010です。なぜ足すの?: マイナスの指数($0.001$ など)を扱う際、ビット列を負の数として扱うより、正の数にオフセットさせた方がコンピュータが比較計算しやすいためです。
③ 仮数部 (23bit)
- 先ほどの
1.001101の小数部分だけを使います。 001101- 残りのスペースはすべて
0で埋めます。 00110100000000000000000
ステップ4:完成
これらをガッチャンコすると、メモリ上のビット列が出来上がります。
| 符号 (1bit) | 指数部 (8bit) | 仮数部 (23bit) |
|---|---|---|
0 |
10000010 |
00110100000000000000000 |
これを 16進数で表すと 0x411A0000 となり、これがコンピュータが理解する「$9.625$」の正体です。
まとめ:変換のエッセンス
- 2進数にする: 普通の2進数の小数にする。
- 小数点を滑らせる: 「$1.xxxx$」の形にして、何回動かしたか(指数)を記録する。
-
詰め込む:
- 先頭の $1$ は「当たり前」なので捨てて、後ろの $xxxx$ だけ保存する(仮数部)。
- 動かした回数に 127 を足して保存する(指数部)。
■ステップ1の計算根拠
ステップ1で行った「整数部は2進数に変換し、小数部は2倍して1を引く(あるいは2倍して整数部を取り出す)」という作業の根拠は、2進数の位(重み)の定義そのものにあります。
私たちが使う10進数と、コンピュータが使う2進数で、それぞれの桁が持つ「価値」を比較すると分かりやすくなります。
1. 整数部の根拠(割り算の逆)
整数部は、2の累乗 ($2^0, 2^1, 2^2, \dots$) の組み合わせでできています。
$9$ を $1001$ にするのは、「$9$ の中に $2$ が何個含まれているか」を順に剥ぎ取っていく作業です。
- $9 = \mathbf{1} \times 2^3 + \mathbf{0} \times 2^2 + \mathbf{0} \times 2^1 + \mathbf{1} \times 2^0$
- つまり「左に1桁ずらす(2倍する)」ことの逆(2で割る)を繰り返して、余りを確認しています。
2. 小数部の根拠(2倍して「溢れ」を見る)
小数部の各桁は、2の負の累乗 ($2^{-1}, 2^{-2}, 2^{-3}, \dots$) すなわち $1/2, 1/4, 1/8, \dots$ の重みを持っています。
$0.625$ を2進数にするプロセスを、数式で分解するとこうなります。
なぜ「2倍」するのか?
2進数の小数 $0.abc \dots$ を10進数で書くとこうなります:
$$\text{値} = a \times 2^{-1} + b \times 2^{-2} + c \times 2^{-3} \dots$$
この全体を 2倍 してみましょう。
$$\text{値} \times 2 = a \times 2^{0} + b \times 2^{-1} + c \times 2^{-2} \dots$$
- ここで、もともと小数点以下第1位だった $a$ が、整数部($2^0$ の位)へ放り出されました。
- つまり、「2倍したときの結果が1以上になるか?」を見れば、その桁が 1 か 0 かが判明するのです。
0.625での実演
-
$0.625 \times 2 = \mathbf{1}.25$
- 2倍したら「1」が整数部に出た。→ 第1位は 1 ($0.5$ の成分がある)。
- 残った $0.25$ について次を調べる。
-
$0.25 \times 2 = \mathbf{0}.5$
- 2倍しても「0」のまま。→ 第2位は 0 ($0.25$ の成分はない)。
- 残った $0.5$ について次を調べる。
-
$0.5 \times 2 = \mathbf{1}.0$
- 2倍したら「1」が出た。→ 第3位は 1 ($0.125$ の成分がある)。
- 残りが $0$ になったので終了。
結果: $0.101$ ($1/2 + 0/4 + 1/8 = 0.5 + 0.125 = 0.625$)
まとめ:なぜこの方法が確実なのか
- 整数部は、「2の塊」が何個あるかを知るために 2で割っていく。
- 小数部は、「半分(1/2)の塊」が含まれているかを知るために 2倍して整数部に押し出す。
この「逆の操作」を行うことで、10進数の世界から2進数の世界へ、値を1ビットずつ正確に「翻訳」しているのがステップ1の根拠です。
■2の累乗形式浮動小数点数から10の累乗形式浮動小数点数への変換
$2^n$(2の累乗)形式で表された数値を、$10^n$(10の累乗)形式へ変換する作業は、数学的には「対数(Logarithm)」を使って桁数を計算するプロセスになります。
なぜこれが必要かというと、コンピュータが内部で「$2^{32}$」と管理している巨大な値が、10進数で何桁になるかを一瞬で判断するためです。
1. 数学的な変換原理
ある数値 $X$ が $2^a$ であるとき、これを $10^x$ に書き換えたい場合、以下の式が成り立ちます。
$$10^x = 2^a$$
両辺の常用対数($\log_{10}$)をとると:
$$x = a \times \log_{10} 2$$
ここで、$\log_{10} 2 \approx 0.3010$ であるため、以下の変換公式が導き出されます。
$10$の指数 $\approx 2$の指数 $\times 0.3010$
2. 具体的な変換例
例えば、$2^{16}$ を $10^n$ 形式に直してみましょう。
- 指数を計算: $16 \times 0.3010 = 4.816$
- $10^n$ 形式にする: $10^{4.816}$
- 整数と小数に分ける: $10^{0.816} \times 10^4$
- 値を出す: $10^{0.816}$ は計算機で叩くと約 $6.546$ なので…
- 完成: $6.546 \times 10^4$ (= $65460$。実際の $65536$ に近い値になります)
3. エンジニアが覚えている「暗算テクニック」
開発現場では、厳密な対数計算をする代わりに、以下の近似(マジックナンバー)をよく使います。
$2^{10} \approx 10^3$
$2^{10} = 1024$、 $10^3 = 1000$ なので、非常に誤差が少ない近似です。これを使うと、巨大なビット数も一瞬で10進数の桁数に見当がつきます。
- $2^{20}$: $(2^{10})^2 \approx (10^3)^2 = 10^6$ (約100万 / 1 Mega)
- $2^{30}$: $(2^{10})^3 \approx (10^3)^3 = 10^9$ (約10億 / 1 Giga)
- $2^{32}$: $2^2 \times 2^{30} = 4 \times 10^9$ (約42億。IPv4のアドレス数ですね)