単精度浮動小数点数(IEEE754) の作り方

書いたこと

  1. 単精度浮動小数点数形式とデータ構造
  2. 10進数から単精度浮動小数点数データ構造への変換
  3. 精度と誤差

単精度浮動小数点数形式とデータ構造

2進数をそのままでは単精度浮動小数点数データ構造に変換することはできない。 一旦、単精度浮動小数点数形式に正規化を行う必要がある。

単精度浮動小数点数形式と正規化

浮動小数点数データ構造に変換する前段階として、2進数を以下のように最上位ビットを1にして、位取りを2の指数で表すようにする。

f:id:yossan2:20201228232141p:plain
単精度浮動小数点数形式

この形式に変換することを正規化という。

例 3.1415の正規化

f:id:yossan2:20201231145808p:plain
3.1415の正規化

正規化方法

1. 3.1415を2進数に変換する

0b11.00100100001110010101100

2. 最上位ビットが1になるように位を調整する

0b1.10010010000111001010110 x 2^1

単精度浮動小数点数データ構造への変換

正規化した2進数とIEEE754で定義された単精度浮動小数点数データ構造は以下の関係を持つ。

f:id:yossan2:20201228220851p:plain
単精度浮動小数点数(IEEE754)データ構造

符号部

1ビットで構成され、正規化した際に正数のときは0、負数のときは1。

指数部

8ビットで構成され、指数部の値は、正規化した際の指数値の値に+ 7F(127)足した値となる。 これによって0 ~ 7Eまでの値に負数(-127 ~ -1)を割当て、80 ~ FFまでの値に正数(1 ~ 127)を割りてている。

f:id:yossan2:20201228234832p:plain
指数値と指数部の関係

仮引数部

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桁を超える

f:id:yossan2:20210102000304p:plain

例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)