単精度浮動小数点数(IEEE754) の作り方
書いたこと
単精度浮動小数点数形式とデータ構造
2進数をそのままでは単精度浮動小数点数データ構造に変換することはできない。 一旦、単精度浮動小数点数形式に正規化を行う必要がある。
単精度浮動小数点数形式と正規化
浮動小数点数データ構造に変換する前段階として、2進数を以下のように最上位ビットを1にして、位取りを2の指数で表すようにする。
この形式に変換することを正規化という。
例 3.1415の正規化
正規化方法
1. 3.1415を2進数に変換する
0b11.00100100001110010101100
2. 最上位ビットが1になるように位を調整する
0b1.10010010000111001010110 x 2^1
単精度浮動小数点数データ構造への変換
正規化した2進数とIEEE754で定義された単精度浮動小数点数データ構造は以下の関係を持つ。
符号部
1ビットで構成され、正規化した際に正数のときは0、負数のときは1。
指数部
8ビットで構成され、指数部の値は、正規化した際の指数値の値に+ 7F(127)
足した値となる。
これによって0 ~ 7Eまでの値に負数(-127 ~ -1)を割当て、80 ~ FFまでの値に正数(1 ~ 127)を割りてている。
仮引数部
23ビットで構成され、正規化した値に対して小数点以下上位23ビット取得する。
例 3.1415
2進数に変換
0b11.00100100001110010101100
正規化した値
0b1.100100100001110010101100
正規化した値をデータ構造に変換
正規化した値 | データ構造に割当てられる値 |
---|---|
符号: 正数 | 符号部: 0 |
指数: 1 | 指数部: 128 |
小数点以下上位24桁 | 仮引数部 |
100100100001110010101100 | 10010010000111001010110 |
2. 精度と誤差
単精度浮動小数点数は、位取りを整えることが出来るため、値の大小に関わらず常に精度は2進数で小数点以下23桁が保証される
例: 円周率が3.1415として、直径が250と0.25における精度を10進数で考える。(1の位を最上位に正規化)
有効桁数 | 250 | 0.25 |
---|---|---|
小数点以下4桁 | 7.8535 x 102 | 7.8375 x 10^-1 |
小数点以下3桁 | 7.854 x 102 | 7.854 x 10^-1 |
小数点以下2桁 | 7.85 x 102 | 7.85 x 10^-1 |
小数点以下1桁 | 7.9 x 102 | 7.9 x 10^-1 |
値の大小によって精度は変わらないが、有効桁数によって、本来の値と誤差が発生している。
代表的な誤差
- 丸め誤差: 23桁をオーバーフローした時
- 情報落ち: 巨大な数字と小さな数字を足した時
- (有効数字の)桁落ち: 極めて近い値同士、引き算した時
丸め誤差 (打切り誤差)
単精度浮動小数点数に変換した際に、小数点数以下23桁を超える範囲は、24桁目で丸められる。 (Swiftの場合、1ビットの数が偶数の場合は、桁上りし、奇数の場合、切り捨てられていると思われる)
例1: 16777215 (0b1.1111111111111111111111) より大きい数
16777215より大きい数は2進数に変換し、正規化すると24桁を超える
例2: 0.1
0.75は0.5(1/2)と0.25(1/4)の和で表せるが、0.1は表せないため、2進数に変換すると無限小数となり、23桁で打ち切られる。
0.1を2進数に変換
0b0.000110011001100110011001100110
浮動小数点数形式に正規化
0b1.10011001100110011001101 \* 2^(-4)
0b1.10011001100110011001101 = 1.6000000238418579
1.60000002384185791016 \* 2^(-4) = 0.10000000149
したがって、0.1は単精度浮動小数点数では、0.10000000149 となる。
例: Swiftでの確認 0.1を10回足しても1.0にならない
var f = 0.1 as Float for _ in 0..<10 { f += 0.1 } print(f == 1.0) // false
情報落ち
浮動小数点数の和を計算する際、指数部がそろえられるため、巨大な数字との和をとると、小さい方の値が落ちてしまう。
例: Swiftによる確認 1千万と0.1との和
var f = 10_000_000 as Float for _ in 0..<1000 { f += 0.1 } print(f == 10_000_000) // true
桁落ち
極めて近い値同士で引き算をすると、有効数字が小さくなり、0埋めされることによって誤差が発生する。
例: sqrt(113) - sqrt(112)
それぞれ2進数で表すと
sqrt(113) = 0b1010.10100001010100010100 sqrt(112) = 0b1010.10010101001111111101
浮動小数点数形式へ正規化
sqrt(113) = 0b1.01010100001010100010100 x 2^(3) sqrt(112) = 0b1.01010010101001111111101 x 2^(3)
したがって
sqrt(113) - sqrt(112) = 0b0.00000001100000100010111 x 2^(3)
正規化すると0埋めが発生する
sqrt(113) - sqrt(112) = 0b1.10000010001011100000000 x 2^(-5)