🔢

ざっくりと、しかしある程度理解しておく、浮動小数点数のビット表現

2024/05/04に公開

この記事について

浮動小数点数(IEEE 754)のビット表現について調べる機会があり、半分くらい自身の整理のためにまとめたものとなります。
厳密には適切ではない表現などもあるかもしれませんが、マサカリはお手柔らかにお願いします🙇‍♂️

対象読者

  • 情報処理試験や大学学部授業レベルではなんとなく浮動小数点数について知っている
    • 指数部とか仮数部とかはなんとなく知っている
    • 全くの初見の場合は巷の他の記事に参考にしてください
  • 同じく、負の数を表現するための2の補数表現は知っている
  • 具体的にある浮動小数点数がどういったビット表現になるかとか、-0, ∞, NaNなどの例外値についての理解は怪しい
    • が、もう少し詳しく知っておきたいと思っている

私自身が上記のような状態だったため、その中で疑問に思った点を中心に順序立ててまとめています。
網羅的/体系的なものは他を参考にしてください。
また、極力数式は省いて具体的な数値を元に簡潔に解説することを心掛けました。

IEEE 754について

浮動小数点数とは、実数(無限個)をコンピュータ上のビット表現(有限個)で近似的に表現するための方式の一つです。
その他の方式としては、有理数や固定小数点数がありますが本記事では省略します。

浮動小数点数の中でも古くは色々なフォーマットがあったようですが、現代のデファクトはIEEE 754です。
ほとんどのプログラミング言語で、明示的にも暗黙的にもIEEE 754を準拠していると思います。

フォーマット

IEEE 754の中でも、精度や何進数かなどによっていくつかのフォーマットが定義されています。
今回はbinary32という、一般に単精度と呼ばれ、C言語ではfloat型として定義されているフォーマットに限って話を進めていきます。

binary~の他にもdecimal~というフォーマットも定義されているようで、これは後から拡張された十進法のフォーマットのようです。
全ての形式で成り立つように話を進めるためには、抽象的な数式を中心に話をしないといけなくなるため、今回はbinary32(32bit=4byte、二進数)の形式を前提とします。


https://ja.wikipedia.org/wiki/IEEE_754 より引用

以降では、仮数部(上図 fraction)、指数部(上図 exponent)という単語が頻繁に登場しますので、スッとイメージできるようにしておいてください。

符号部

まずは先頭の1ビットです。
これは簡単ですね。
0だったら正の数、1だったら負の数です。

仮数部

2^E(指数部) * Mの、Mの部分です。
binary32では23ビット分表現できます。

具体的に、今回は2.5(十進数)という数値を元に考えてみます。

2.5(十進数)は二進数では、10.1です。
これを2^E(指数部) * Mの形式にすると、2^1 * 1.01とも表すことができます。
このように仮数部を1.xの形にすることを正規化と言います。

では、23ビットの表現は、以降を0で埋めた、101 0000 0000 0000 0000 0000でしょうか。
これは異なります。
正規化して表現すること前提のため、先頭の1ビットは暗黙的に必ず想定されていて、23ビットはそれ以降のビットを表現します。
つまり、(1)010 0000 0000 0000 0000 0000となります。()の部分が暗黙的な部分です。
扱えるビット数が1つ増えて24ビット分の表現ができるようになっています。
ちょっとした工夫で1ビットお得になりました。

0を表現する

しかし、0を表現しようと思ったとき問題が発生します。

仮数部を全て0ビットで埋めて000 0000 0000 0000 0000 0000としても、暗黙に(1)000 0000 0000 0000 0000 0000を意味してしまいます。
2^E(仮数部、ここを限界まで小さくする) * 1.0としても、0に近しい数値は表現できますが、数直線上は原点からズレてしまいます。
これは結構問題ですね。
いったんこの問題を頭に入れつつ、指数部の話に移ります。

指数部

2^E * M(仮数部)の、Eの部分です。
binary32では、指数部は8ビット分表現できます。

仮数部が101(ビット表現は暗黙の1ビットがあるので01)として、Eを変えていくつか例を挙げてみます。

E 二進数 十進数
0 1.01 1.25
1 10.1 2.5
2 101.0 5
-1 0.101 0.625

では、それぞれの指数部のビット表現は、
0000 0000
0000 0001
0000 0010
1111 1111
と二の補数表現で表現すればいいでしょうか。
これは異なります。

バイアス

実は8ビット表現を非負数で解釈して、バイアス値を引いた上で実際の指数を決めます。
binary32において、バイアス値は127です。

E バイアスを引く前の値 指数部のビット表現
0 127 0111 1111
1 128 1000 0000
2 129 1000 0001
-1 126 0111 1110

なぜ、このようなバイアスをわざわざ設けているのでしょうか。
一つにはソートアルゴリズムなどの効率化の観点があります。
指数部は0000 0000から1111 1111までビットが増加すればするほど大きくなります。
符号部が0の正の数同士であれば、以降の31ビットを単純にビット単位で比較するだけで大小関係が判定できます。

例外

では、binary32において、指数部の最小値と最大値は
0000 0000 -> 00 - 127-127
1111 1111 -> 255255 - 127128
となることから、-127128でしょうか。
しかし、これも違います。

実は指数部のビットが全て0, 全て1は特殊な扱いを受けます。

全て1ビット

指数部が全て1ビットの場合は、-∞NaNを表現します。
仮数部が0の場合は、符号部によってそれぞれ, -∞となります。
仮数部が0以外の場合は、NaNになります。
NaNは例えば0除算の結果などの不正な値です。
0以外の表現は沢山あるので、ビット表現は一意に定まりません。
NaNの中でも、signaling NaN、quiet NaNという概念があるようですが、処理系に依存することや、私も詳しくは把握できていないため省略します。

ビット表現 符号部 指数部 仮数部
0111 1111 1000 0000 0000 0000 0000 0000 +(0) 1111 1111 0
-∞ 1111 1111 1000 0000 0000 0000 0000 0000 -(1) 1111 1111 0
NaN X111 1111 1XXX XXXX XXXX XXXX XXXX XXXX 任意 1111 1111 0以外任意

全てが0ビット

指数部が全て0ビットの場合は、0, -0, 非正規化数という値を表現します。

0, -0

ただし、前述の通り、全て0ビットにしたところで仮数部には暗黙の先頭1ビットがあるため、0にはなりません。

ここで、指数部が全て0ビットの場合、暗黙の先頭1ビットを想定せず、0.MのM部分を指数部の23ビットが表現するという例外が適用されます。
指数部と仮数部が共に0の場合、0を表現できるようになりました。

ただし、符号部は+と-があるためIEEE 754の仕様ではそれぞれ0-0が存在します。

ビット表現 符号部 指数部 仮数部
0 0000 0000 0000 0000 0000 0000 0000 0000 +(0) 0(0000 0000) 0
-0 1000 0000 0000 0000 0000 0000 0000 0000 -(1) 0(0000 0000) 0
非正規化数

0を表現することはできましたが、指数部が0で、仮数部が0以外の場合はどうなるでしょうか。

実は、指数部が0の場合は、前述の暗黙の先頭1ビットを想定しない以外にも、指数部を0 - 127 = -127ではなく、さらに1を引いて-126として考えるという例外ルールがさらに存在します。

上記二つの例外ルールを適用した場合と、例外が仮にどちらもないと想定した場合に表現可能な領域を数直線上でみてみましょう。

まず、例外がない場合です。
0が表せないだけでなく、-2^(-127) ~ 0 ~ 2^(-127)の範囲を全く表現することができなくなります。

次に例外を設けた場合です。
0だけでなく、0と-2^(-127),2^(-127)との間の値も表現が可能になりました。

この間の値を非正規化数と呼びます。
1.x形式にすることを正規化というためです。

まとめ

今回はbinary32形式を具体例に沿って、表現できる値とそのビット表現をまとめてみました。

2^E * Mで表す形式ということは知っている方も多いと思いますが、仮数部の暗黙の先頭1ビットや指数部のバイアス、指数部が0だった場合の例外規則、などについては詳細を認識してない方もいたのではないでしょうか。

今回はビット表現に絞って解説しましたが、IEEE 754では、その他のフォーマットや丸め方法なども標準化しているため、気になった方は深掘りしてみてください。

References

https://ushiostarfish.hatenablog.com/entry/2019/08/12/210023

GitHubで編集を提案

Discussion