今更理解する浮動小数点

自分なりのまとめです。間違いがありましたらすみません。

コンピュータでの数の表現の仕方

コンピュータのハードウェア(基盤のようなものを想像してみてください)はICで構成されておりこのICは電圧のONとOFFのどちらかの状態しか持つことが出来ません。
そのためこのICの集まりで何かを表現するためにはONとOFFの集まりで表現するしかありません。そこでONを数字の1、OFFを数字の0としてみなしてこの並びでデータを表現することにします。
そのような0と1の並びをどのように解釈するかで表現したいものが決まります。
101100101.....のようなデータを画像と解釈するか動画と解釈するかゲームと解釈するかそれともただの数として解釈するかは自由です。
ただ、解釈した結果があなたの望むものであれば良いのです。そしてコンピュータが数を扱う際にはこの0と1の並びをただの数として解釈します。

2進数

数ですから数えられなければいけません。しかも、0と1しか使えません。
では、りんごを数えましょう。
まずりんごが何もない状態を想像してください。これを0と1だけで表現しなければいけません。
ではりんごが何もない状態を0と表現することに勝手に決めましょう。
次にさっきの空の状態からりんごを1つ追加しました。さてこれを数えましょう。
この状態を1と表現することに勝手に決めました。
ここからさらにりんごを1つ追加しました。これを数えたいです。
では、これを2と表現しま。。。おっと人間っぽくなってしまいました。
先にお話したようにコンピュータは0と1しか使えないんでしたね。
では、0と1だけでどのように今の状態を表現しましょうか。もう0という表現を使ってしまいました。1という表現もです。
うーん、困りました。どうやら今のままではここまでしか数えられないようです。どうしましょう?
そこで思いつきました。もうひと桁増やして表現しよう。
先ほどまではひと桁でしたが増やすと使える表現が00や01や10や11と増えました。
これでもう一度最初から数えてみましょう。
空の状態を00とする。そこから1つ追加したものを01、そこからさらに1つ追加したものを10、さらに追加したものを11とします。
さっきより数えられる範囲が広がりました。このように桁を増やせばどんどん数えられる範囲が広がります。
このように数を表現する方法が2進数です。0と1しか使わない。0と1は電圧のONとOFFに対応させることができます。そのためこの表現の仕方はコンピュータにとってとても扱いやすいのです。

2進数での負の数

2進数で負の数はどのように表現するのかというといくつか方法があります。
代表的なのでいうと符号をつけるという先頭1桁目が1なら負で0なら正の数とみなすという方法や2の補数という方法など。。。
ここでは負の数も表す方法があるんだなという理解で良いです。

2進数での四則演算

すみません。大切だとは思いますが省略します。調べればすぐに出てくると思いますので。。。
ここでは2進数でも足し算もかけ算も出来るんだとご理解頂ければと思います。

2進数での小数表現

まず10進数で普通に考えてみましょう。1から0.1を得るにはどうすれば良いでしょうか?1×{\frac{1}{10}}とやれば得られますね。
この時1の桁が右に1個ずれてますね。10個で桁が増えるのが10進数ですから10倍すると桁が左にずれ、{\frac{1}{10}}すると桁が右にずれます。
同じように2進数で考えて2進数の1の桁を右に1個ずらすには1×{\frac{1}{2}}とすると1の桁が右にずれて0.1となります。
これらから10進数での0.1は10個集まると桁が上がりますので10進数では0.1です。2進数での0.1は2個集まると桁があがりますので10進数に直すと0.5になります。
このように0.1という同じ見た目でもそれが10進数での表現か2進数での表現かで実際に意味する数が変わります。
では、2進数での0.11を10進数に直した場合の数はいくらでしょうか?これは2進数での0.1と2進数での0.01の組み合わせです。
先ほどのことから2進数での0.1は10進数での0.5でした。では2進数での0.01は10進数ではいくらでしょうか?2進数の0.01を2個集めると桁があがって2進数での0.1になるのです。
つまり 2進数での0.1は10進数での0.5なので10進数での計算に直すと0.5 = 2 × ? です。?は0.25です。つまり2進数の0.01を10進数に直すと0.25です。
このことから2進数での0.11を10進数に直すと0.5+0.25=0.75であることが分かります。
さて、10進数の0.5を2進数で表すと0.1と表せることは分かりました。しかし0.1という表現をそのままコンピュータで使うことは出来ません。はじめに言った通りコンピュータは0と1の並びでしか表現できません。小数点(.)なんてありませんから。ではどのようにコンピュータで表すのでしょうか?

固定小数点表記

小数を表す一つのやり方は小数点の位置を決めておく方法で固定小数点表記と呼ばれます。
例えば、先頭から1桁を整数部分、次の2桁目を小数部分と決めておきます。つまり小数点(.)は1桁目と2桁目の間だと分かります。すると01という0と1だけの表現を0.1であると判断できます。
ただこの方法は精度に問題があります。10進数で表すと例えば、先頭から5桁を整数部分、次の5桁目を小数部分と決めていた場合に3.14159265という小数は0埋めされ00003.141592となってしまい精度が失われてしまいます。そのためあまり精度がいらない数値計算では用いられるという感じでしょうか。

浮動小数点表記

浮動小数点表記の考え方は小数を整数部分が1桁(ただし0ではない)になるように小数点を移動し、x.yyyyy... × 2 ^zという形式で表そうというものです。その時にどうやってこの表現を0と1だけで表そうかと考えた時に3つの部分に分けて表記しようとなりました。
3つとは、符号部、小数部(仮数部とも言います)、指数部です。
符号部というのは2進数での負の数のところで述べた符号をつけるというものです。先頭1桁を0なら正の数、1なら負の数とみなします。つまり浮動小数点表記は負の小数も扱えます。
小数部というのは先ほどの2進数の0.11では浮動小数点の考え方では1.1×2^-1となるので小数部は1です。
指数部はこの場合は2^-1なので-1ですね。
固定小数点表記とは違い小数点の位置は指数部によって後から決まります。

IEEE 754 浮動小数点規格

符号部、小数部、指数部という3つの部分に分けて表現するのは分かったのですが、その3つの部分の並び順は?それぞれの部分は何ビット(0か1の一桁のことを1ビットと言います)になるの?と疑問に思うのですがそれを決めた規格の1つで広く普及しているのがIEEE 754 浮動小数点規格です。単精度浮動小数点規格と倍精度浮動小数点規格の2つの形式が定められています。
ここでは2進数の0.011を単精度浮動小数点規格で表現してみたいと思います。
単精度浮動小数点規格は小数を32ビットで表現します。つまり0か1のどちらかが32個並びます。
符号に先頭1ビット、次の8ビットが指数、次の23ビットが小数部です。
まず正の小数なので先頭1ビットは0ですね。
指数部は少し複雑で「-2」なので「-2」にバイアスの「127」を加算します。指数部にバイアスを加算することで、負の数を含む表現が正の数だけで表現できます。
(-2)+127 = 125 この125を2進数で表して01111101になります。
0.011を正規化すると1.1×2^-2です。小数部には小数点以下の値が入ります。小数第1位から順に「上位ビット→下位ビット」の順で並べるだけです。足りない部分は0で埋めます。
よって以下の画像のようになります。
00111110110000000000000000000000
https://medium-company.com/wp-content/uploads/2021/08/ieee754_3.png
このような形式で2進数の0.011、10進数に直すと0.375という小数は画像のような0と1の並びで表現されます。
倍精度浮動小数点規格は32ビットではなく64ビットで表します。

誤差

10進数での0.1は有限小数だが10進数での0.1を2進数の小数で表そうとすると無限小数になってしまいます。無限はまずいので32ビットや64ビットで収まるようにどこかで切って丸めるしかありません。そのため誤差が出ることが多いです。
IEEE 754 浮動小数点規格では10進数の小数を2進数の小数で表して正規化(1.1×2^-2のような形)した時の小数部の23桁目と24桁目が00 → 00、01 → 00、10 → 10、11 → 100として丸めます。これを最近接偶数方向丸めといいます。