float 型の詳細
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ JavaScript で float 型のバイナリ値を取得して指数部や仮数部の表示
◆ 内部の計算方法の表示
◆ 内部の計算方法の表示
普段なんとなく使ってる数値型の値ですが ふと内部での扱いが気になったので調べました
JavaScript では整数でも小数と同じ number 型で 中身は 64bit の浮動小数点型です
整数型なら単純なのですが 浮動小数点型だと複雑です
指数部と仮数部に分かれていてーとかはなんとなくは知っているもののそれらを具体的にどうやって計算してるのかがあまりわかってません
普通の number 型では中のバイナリ値まで見れないので TypedArray を使います
64bit だと長くなるので Float64Array の代わりに 32bit の Float32Array を使います
桁数が違うだけで考え方は同じです
数値から 符号・指数・仮数部に分割して取得する関数と それらの数値から float 型に戻す関数です
floatTo の結果を floatFrom に入れて元通りになってます
float 型で正確に表現できない 0.1 では少し誤差が出ていますが単純に 0.1 を表示してもこうなります
分解できたので 自分で計算してみます
計算過程を表示する関数を用意しました
符号は最初の 0/1 の違いだけで 指数・仮数部は同じなので省略しています
いくつかの値を入れてみます
もとの値になってますね
0 の場合は 2 ** -127 になって 5.877471754111438e-39 という結果です
この値は 32bit float 型で表現できる最小よりも小さいので 0 になるのですが JavaScript の数値型の 64bit として計算してるので 5.877471754111438e-39 となってます
この辺は低レイヤー言語ならともかく JavaScript みたいなスクリプト言語だと基本意識することがないところなので こういう部分はやっぱり複雑だなーと感じます
JavaScript では整数でも小数と同じ number 型で 中身は 64bit の浮動小数点型です
整数型なら単純なのですが 浮動小数点型だと複雑です
指数部と仮数部に分かれていてーとかはなんとなくは知っているもののそれらを具体的にどうやって計算してるのかがあまりわかってません
普通の number 型では中のバイナリ値まで見れないので TypedArray を使います
64bit だと長くなるので Float64Array の代わりに 32bit の Float32Array を使います
桁数が違うだけで考え方は同じです
数値から 符号・指数・仮数部に分割して取得する関数と それらの数値から float 型に戻す関数です
const floatTo = (f) => {
const f32 = new Float32Array(1)
f32[0] = f
const bits = Array.from(
new Uint8Array(f32.buffer),
x => x.toString(2).padStart(8, "0")
).reverse().join("")
return [bits.slice(0, 1), bits.slice(1, 9), bits.slice(9)]
}
const floatFrom = (sign, exp, frac) => {
const bits = sign + exp + frac
if (bits.length !== 32) throw new Error("Invalid arguments")
return new Float32Array(
Uint8Array.from(
bits.split(/(\d{8})/g).filter(x => x).reverse().map(x => parseInt(x, 2))
).buffer
)[0]
}
floatTo(12345)
// (3) ['0', '10001100', '10000001110010000000000']
floatFrom("0", "10001100", "10000001110010000000000")
// 12345
floatFrom(...floatTo(1))
// 1
floatFrom(...floatTo(2))
// 2
floatFrom(...floatTo(3))
// 3
floatFrom(...floatTo(10))
// 10
floatFrom(...floatTo(0.5))
// 0.5
floatFrom(...floatTo(0.1))
// 0.10000000149011612
floatTo の結果を floatFrom に入れて元通りになってます
float 型で正確に表現できない 0.1 では少し誤差が出ていますが単純に 0.1 を表示してもこうなります
Float32Array.of(0.1)[0]
// 0.10000000149011612
分解できたので 自分で計算してみます
計算過程を表示する関数を用意しました
符号は最初の 0/1 の違いだけで 指数・仮数部は同じなので省略しています
const floatInfo = (f) => {
const [sign, exp, frac] = floatTo(f)
console.log({ sign, exp, frac })
const E = parseInt(exp, 2) - 127
const fraction = "1" + frac
const values = []
for (const [offset, bit] of Object.entries(fraction)) {
console.log(`(2 ** ${E - offset}) * ${bit}`)
if (+bit) {
values.push(2 ** (E - offset))
}
}
console.log(values.join(" + "))
console.log("= " + values.reduce((a, b) => a + b, 0))
}
いくつかの値を入れてみます
> floatInfo(1)
{ sign: '0', exp: '01111111', frac: '00000000000000000000000' }
(2 ** 0) * 1
(2 ** -1) * 0
(2 ** -2) * 0
(2 ** -3) * 0
(2 ** -4) * 0
(2 ** -5) * 0
(2 ** -6) * 0
(2 ** -7) * 0
(2 ** -8) * 0
(2 ** -9) * 0
(2 ** -10) * 0
(2 ** -11) * 0
(2 ** -12) * 0
(2 ** -13) * 0
(2 ** -14) * 0
(2 ** -15) * 0
(2 ** -16) * 0
(2 ** -17) * 0
(2 ** -18) * 0
(2 ** -19) * 0
(2 ** -20) * 0
(2 ** -21) * 0
(2 ** -22) * 0
(2 ** -23) * 0
1
= 1
> floatInfo(3)
{ sign: '0', exp: '10000000', frac: '10000000000000000000000' }
(2 ** 1) * 1
(2 ** 0) * 1
(2 ** -1) * 0
(2 ** -2) * 0
(2 ** -3) * 0
(2 ** -4) * 0
(2 ** -5) * 0
(2 ** -6) * 0
(2 ** -7) * 0
(2 ** -8) * 0
(2 ** -9) * 0
(2 ** -10) * 0
(2 ** -11) * 0
(2 ** -12) * 0
(2 ** -13) * 0
(2 ** -14) * 0
(2 ** -15) * 0
(2 ** -16) * 0
(2 ** -17) * 0
(2 ** -18) * 0
(2 ** -19) * 0
(2 ** -20) * 0
(2 ** -21) * 0
(2 ** -22) * 0
2 + 1
= 3
> floatInfo(255)
{ sign: '0', exp: '10000110', frac: '11111110000000000000000' }
(2 ** 7) * 1
(2 ** 6) * 1
(2 ** 5) * 1
(2 ** 4) * 1
(2 ** 3) * 1
(2 ** 2) * 1
(2 ** 1) * 1
(2 ** 0) * 1
(2 ** -1) * 0
(2 ** -2) * 0
(2 ** -3) * 0
(2 ** -4) * 0
(2 ** -5) * 0
(2 ** -6) * 0
(2 ** -7) * 0
(2 ** -8) * 0
(2 ** -9) * 0
(2 ** -10) * 0
(2 ** -11) * 0
(2 ** -12) * 0
(2 ** -13) * 0
(2 ** -14) * 0
(2 ** -15) * 0
(2 ** -16) * 0
128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
= 255
> floatInfo(0.5)
{ sign: '0', exp: '01111110', frac: '00000000000000000000000' }
(2 ** -1) * 1
(2 ** -2) * 0
(2 ** -3) * 0
(2 ** -4) * 0
(2 ** -5) * 0
(2 ** -6) * 0
(2 ** -7) * 0
(2 ** -8) * 0
(2 ** -9) * 0
(2 ** -10) * 0
(2 ** -11) * 0
(2 ** -12) * 0
(2 ** -13) * 0
(2 ** -14) * 0
(2 ** -15) * 0
(2 ** -16) * 0
(2 ** -17) * 0
(2 ** -18) * 0
(2 ** -19) * 0
(2 ** -20) * 0
(2 ** -21) * 0
(2 ** -22) * 0
(2 ** -23) * 0
(2 ** -24) * 0
0.5
= 0.5
> floatInfo(0.1)
{ sign: '0', exp: '01111011', frac: '10011001100110011001101' }
(2 ** -4) * 1
(2 ** -5) * 1
(2 ** -6) * 0
(2 ** -7) * 0
(2 ** -8) * 1
(2 ** -9) * 1
(2 ** -10) * 0
(2 ** -11) * 0
(2 ** -12) * 1
(2 ** -13) * 1
(2 ** -14) * 0
(2 ** -15) * 0
(2 ** -16) * 1
(2 ** -17) * 1
(2 ** -18) * 0
(2 ** -19) * 0
(2 ** -20) * 1
(2 ** -21) * 1
(2 ** -22) * 0
(2 ** -23) * 0
(2 ** -24) * 1
(2 ** -25) * 1
(2 ** -26) * 0
(2 ** -27) * 1
0.0625 + 0.03125 + 0.00390625 + 0.001953125 + 0.000244140625 + 0.0001220703125 +
0.0000152587890625 + 0.00000762939453125 + 9.5367431640625e-7 + 4.76837158203125e-7 +
5.960464477539063e-8 + 2.9802322387695312e-8 + 7.450580596923828e-9
= 0.10000000149011612
もとの値になってますね
0 の場合は 2 ** -127 になって 5.877471754111438e-39 という結果です
この値は 32bit float 型で表現できる最小よりも小さいので 0 になるのですが JavaScript の数値型の 64bit として計算してるので 5.877471754111438e-39 となってます
この辺は低レイヤー言語ならともかく JavaScript みたいなスクリプト言語だと基本意識することがないところなので こういう部分はやっぱり複雑だなーと感じます