JavaScriptやPythonなどで小数を扱っていると、こんな結果を見たことがあると思います。
0.1 + 0.2
期待する結果は 0.3 ですが、実際には環境によって次のような値になります。
0.30000000000000004
これはバグではなく、コンピュータが小数を表現するときに使う「IEEE 754」という規格が関係しています。
今回は、IEEE 754の基本と、Float32・Float64の値が内部でどのように表現されているのかを見ていきます。
IEEE 754とは
IEEE 754は、浮動小数点数をコンピュータ上で表現するための標準規格です。
よく使われる形式には、
- Float32:32ビット
- Float64:64ビット
があります。
Float32では32個のビットを、次の3つに分けて使います。
Sign | Exponent | Fraction
それぞれ、
- Sign:正負
- Exponent:指数
- Fraction:有効数字
を表します。
単純に「小数を2進数にしたもの」ではなく、指数表現に近い仕組みで保存されているのがポイントです。
12.5をFloat32にするとどうなる?
12.5をFloat32として表現すると、HEXでは次の値になります。
41480000
2進数では、
01000001 01001000 00000000 00000000
です。
32ビットを分解すると、
0 | 10000010 | 10010000000000000000000
となります。
先頭の 0 がSign、その次がExponent、残りがFractionです。
こうして見ると、普段使っている「12.5」という値も、メモリ上ではまったく違う形で保存されていることが分かります。
なぜ0.1は正確に表せない?
10進数では 0.1 は簡単な有限小数です。
しかし2進数では、0.1を有限桁で正確に表現できません。
そのためコンピュータでは、最も近い値へ丸めて保存します。
Float32で0.1を表すと、HEXは次のようになります。
3DCCCCCD
Float64では、
3FB999999999999A
となります。
つまり、プログラム内部では「完全な0.1」ではなく、0.1に非常に近い値が保存されています。
これが、
0.1 + 0.2 !== 0.3
となる原因の一つです。
Float32とFloat64の違い
Float32とFloat64の大きな違いは、使用するビット数と精度です。
Float32は32ビットなので、データ量を抑えられる一方、表現できる精度はFloat64より低くなります。
Float64は64ビットを使うため、より細かい値を保持できます。
たとえば12.5の場合、
Float32
41480000
Float64
4029000000000000
となります。
同じ12.5でも内部表現の長さが異なります。
ゲーム、画像処理、組み込み機器などではFloat32が使われることも多く、一般的な数値計算ではFloat64が使われる場面も多くあります。
-0という値も存在する
IEEE 754では、通常の 0 とは別に -0 も存在します。
Float32では、
+0 = 00000000
-0 = 80000000
です。
10進数で見るとどちらも0に見えますが、Signビットが異なります。
これは、極限値や数値計算などで正負の方向を保持するために使われることがあります。
Infinityもビットパターンで表現される
IEEE 754では無限大も特別な値として定義されています。
Float32では、
Infinity
7F800000
-Infinity
FF800000
となります。
JavaScriptでも、
1 / 0
は、
Infinity
になります。
単なるエラーではなく、IEEE 754上で定義された特殊値です。
NaNとは
NaNは「Not a Number」の略です。
数値として扱えない計算結果を表します。
たとえばJavaScriptでは、
0 / 0
の結果が、
NaN
になります。
Float32の代表的なNaNのHEX表現の一つは、
7FC00000
です。
NaNも通常の数値とは異なる特別なビットパターンを持っています。
HEXを見るとデバッグしやすいことがある
通常のアプリ開発では、IEEE 754のHEXを直接確認する機会は多くありません。
しかし、
- バイナリファイル解析
- 通信データ解析
- PLCやセンサーとの通信
- 組み込み開発
- Modbusなどのデータ確認
- Float32の誤差調査
では、HEX表現を確認すると原因を追いやすくなることがあります。
たとえば仕様書に、
41 48 00 00
と書かれていても、それだけでは直感的に値が分かりません。
これをFloat32として解釈すると、
12.5
になります。
10進数・HEX・Binaryを相互に確認する
IEEE 754は手作業でも計算できますが、32ビットや64ビットを毎回分解するのはかなり面倒です。
10進数、HEX、2進数を相互に確認したい場合は、IEEE 754変換ツールを使うと、Float32・Float64のビットパターンとSign・Exponent・Fractionをまとめて確認できます。
たとえば12.5を入力すれば、
Decimal
12.5
Float32 HEX
41480000
のように変換結果を確認できます。
まとめ
IEEE 754を知っておくと、
「なぜ小数計算で誤差が出るのか」
「HEXで保存されたFloat値は何を意味しているのか」
が理解しやすくなります。
特に重要なのは、浮動小数点数が単純な10進小数として保存されているわけではなく、
Sign
Exponent
Fraction
という構造で表現されていることです。
Float32やFloat64を扱うときは、見えている10進数だけではなく、その内部にあるビット表現を確認すると、数値の挙動をより深く理解できます。
