浮動小数点数

14.2.2. 浮動小数点数

10の何十乗というような桁の大きな数0や、と1の間の細かい数を統一的に表現するために浮動小数点を導入します。

2進小数 #

整数の2進表現 ( 12.3. bit列と2進数 ) で、 右端 (=小数点) から \(k\) 左の桁は \(2^{k-1}\) を表しました。 これを小数にも拡張し、 小数点から \(k\) の桁は \(2^{-k}\) を表すとします。 つまり、小数点から右に 1/2の桁、1/4の桁、1/8の桁…と続きます。

10進 2進
0 0
0.5 0.1
0.25 0.01
0.125 0.001
0.0625 0.0001
0.75 0.11

数を \(2^k\) 倍することは 2進表記での小数点を \(k\) 右にずらすこと、 \((1/2)^k\) 倍することは \(k\) 左にずらすことに対応します。 2の冪乗倍が小数点位置の移動だけで簡単にできるので、 たとえば64bit 整数表現を \([0,1)\) の範囲を \(2^{-64}\) 刻みで等間隔に表現していると扱うこともできます。より広い範囲を表現するためには、刻み幅を可変にした指数表記を導入します。

0.1 は、2進数では循環小数になります。
2進表記と10進表記とで、循環小数になる数が変わることに注意してください。 ほとんどの環境で、コンピュータに 0.1 を10回足させても 1.0とは別の数になります。

指数表記 #

とても大きな数ととても0に近い数の両方を表したいときに、表現範囲を整数より増やせるでしょうか?
アイデアは、 物理や化学で用いられる Avogadro 定数を \(6.022\ldots \cdot 10^{23} [\text{mol}^{-1}]\) と表記をするような、指数表記です。たとえば10進数5桁分の表記場所 abcde があるとして、これをそのまま10進数に使うと表現範囲は \([0,99999]\) ですが、 \(\texttt{abc}\cdot 10^{\texttt{de}}\) と読むことにすれば、 \([0,999\cdot 10^{99}]\) と最大値が一気に増えます。指数に使ったdeの部分に負の数を表現できるようにすれば \([0,1]\) の間の数を表現することもできます。

今後、 6.022 や abc の有効数字を表現する部分を 仮数、23や de など桁を表現する部分を 指数 と呼びます。

代償として、刻み幅が1より大きくなることに注意してください。 また、指数表記の指数部分の数を変えることは小数点の位置が移動していると見なせることから、 このような表記を浮動小数点 とも言います。

浮動小数点数 #

コンピュータが使う浮動小数点数の代表として、IEEE 754 倍精度 (64bit) を紹介します。 数 \(x\) を3つの数 \(\langle s,m,e \rangle\) に分解して表現し、以下の式で対応させます。

\[x \approx ({-1})^s \underbrace{(1+m \cdot 2^{-52})}_{\text{仮数}} \cdot 2^{\overbrace{(e-1023)}^{\text{指数}}}\]
符号部 \(s\) 指数部 \(e\) 仮数部 \(m\)
符号長 1 ビット 11 ビット 52 ビット

\(\langle s,m,e \rangle\) は、それぞれ符号無し2進整数です。合計で64bitを使います。

符号部
\(s\) は1bitで0または1です。このとき \(({-1})^s\) はそれぞれ \(1,\,{-1}\) となるので、 \(s\) は符号に相当します
指数部
\(e\) は11bitで \([0,2047]\) の整数を表現可能です。 あとで紹介するように 0 と 2047 は特別扱いするので、総合して、 \(2^{(e-1023)}\in[2^{-1022},2^{1023}]\) となります。
仮数部
\(m\) は52bitで、 \((1+m \cdot 2^{-52})\) を仮数として使います。 指数表記では、 \(123=1.23\cdot 10^2\) のように、仮数の整数部が1桁になるよう桁を調整します。この操作で、0でない2進数は必ず1桁目が1になります。このことから 1.011 のような2進の仮数なら 1 は省略して .011 の部分だけ表現しようという工夫がこの主旨です。1bit だけ得をするので、ケチ表現と呼ぶこともあるようです

実数 \( x \) をすべて表せるわけではなく、対応する2進数の組 \(\langle s,m,e \rangle\) が存在するごく一部だけが表現可能です。 それ以外は、近い数で近似的に表現されます。このときの誤差を、表現誤差 と言います。

0.1 は、IEEE 754 でも循環小数となり、表現誤差を伴います。
大事なことなので、しつこく何度か書きます。
仮数部が 53 bit あることから、53bit 以内の整数は誤差なく表現可能です。

単精度・半精度 浮動小数点数
他に 単精度 (32bit) や 半精度 (16bit) の規格も定められています。それらは精度が落ちるのでHWBでは割愛しますが、 GPU計算が効率よくできることから、精度より速度が重要な状況で使われることがあります。
64bitより正確な浮動小数点数
他にも 128 ビットで表現する四倍精度(binary128、仮数部 112 ビット、指数部 15 ビット)や、環境によっては、80 ビットを使った表現が利用できることもあります。本ページでは省略します。

Colab #

浮動小数の考え方を Colab で確認してみましょう。 math.frexp という関数は、仮数が [0,1] となるように指数を調整した結果を示します。

準備として、math モジュールを利用可能にします。
import math

math.frexp(数) という式を、数の部分を変えながら試してみましょう。 2進数の表記で考えると、小数点を移動させていることが見やすいですね。

\[\underbrace{0.09375}_{\frac{1}{16}+\frac{1}{32}} = \underbrace{0.75}_{\frac{1}{2}+\frac{1}{4}} \cdot 2^{-3}\]
math.frexp(0.09375)
(0.75, -3)
\[\underbrace{123.0}_{2^6+2^5+2^4+2^3+2^1+2^0} = \underbrace{0.9609375}_{\frac{1}{2}+\frac{1}{2^2}+\frac{1}{2^3}+\frac{1}{2^4}+\frac{1}{2^6}+\frac{1}{2^7}} \cdot 2^7\]
math.frexp(123.0)
(0.9609375, 7)
符号付き整数 浮動小数点数 精度と誤差