浮動小数点数¶

計算機で扱える数「整数」と「浮動小数点数」について解説する。

そもそも数値計算とは?¶

  • 数値計算(数値解析)とは…数学の問題を有限桁の浮動小数点数を使って「数値的」に解く.数式処理とは違う
  • 数値計算でできること,できないこと
  • 数値計算は誤差との戦い
  • 整数(integer)と浮動小数点数(binary32/binary64)

整数型¶

Juliaで整数は

型 機械内表現
Int8 8bit整数
Int16 16bit整数
Int32 32bit整数
Int64 64bit整数
Int128 128bit整数
BigInt 任意精度

のような数値がある.

In [1]:
versioninfo()
Julia Version 1.9.0
Commit 8e630552924 (2023-05-07 11:25 UTC)
Platform Info:
  OS: macOS (arm64-apple-darwin22.4.0)
  CPU: 8 × Apple M2
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, apple-m1)
  Threads: 2 on 4 virtual cores
In [2]:
a = Int32(10)
Out[2]:
10
In [3]:
b = Int32(2147483647) + Int32(1)
Out[3]:
-2147483648
In [4]:
c = typemax(Int32)
Out[4]:
2147483647

これからInt32の最大値が2147483647であることがわかり $2^{31}-1=2147483647$ である.

説明のために32bit整数(Int32)を考えると

ビットパターン 数値
01111111111111111111111111111111 2147483647
00000000000000000000000000000010 2
00000000000000000000000000000001 1
00000000000000000000000000000000 0
11111111111111111111111111111111 -1
11111111111111111111111111111110 -2
10000000000000000000000000000000 -2147483648

このような負の数の表現形式を「2の補数形式」と呼ぶ. 32個の各bitが次のような重みをもっていると考えられる.

$$\fbox{$-2^{31}$}\fbox{$2^{30}$}\fbox{$2^{29}$}\,\cdots\fbox{$2^{1}$}\fbox{$2^{0}$}$$

2の補数形式の場合,$n$ビットで $-2^{n-1}$〜$2^{n-1}-1$の範囲の数を表現できる.つまり

型 表現範囲
Int8 -128〜127
Int16 -32768〜32767
Int32 -2147483648〜2147483647
Int64 -9223372036854775808〜9223372036854775807
Int128 -170141183460469231731687303715884105728〜170141183460469231731687303715884105727

の範囲の整数が表せる.

In [5]:
2147483647+1
Out[5]:
2147483648
In [6]:
9223372036854775807+1
Out[6]:
-9223372036854775808

浮動小数点数¶

浮動小数点数(Float64/double, Float32/float)は,「浮動小数点形式」と呼ばれる形式で表現できる.

例えば,「$1234.5$」を「$1.2345\times 10^3$」のように, 小数点の位置を1番左の数値と左から2番目の数値の間に移動(「正規化」と呼ぶ)し,それに指数を掛けた形式で数を表現する. この「$1.2345$」の部分を「仮数部」,「$10^3$」の部分(厳密には$~^3$)を「指数部」という.

浮動小数点数は仮数部の長さ,指数部の長さ,基数が2,10,16など,多様な規格が考えられる. そこで1985年にWilliam Kahanが中心となって

IEEE 754: Standard for Binary Floating-Point Arithmetic

という標準規格が制定された.最近では世に出るハードウェアのほぼ全てがこの規格に従っている.

倍精度 (Float64/double, binary64)¶

倍精度は,符号($\pm$)に1ビット.指数部に11ビット,仮数部に 52ビットを使う. 全部で64ビット=8バイトである.

$\fbox{1(符号)}\fbox{11(指数部)}\fbox{52(仮数部)}$

  • 符号は,0なら正,1なら負
  • 指数部は「$\times 2^{\mbox{指数}}$」の指数の部分に$1023$を加えたものが11ビット符号無しの整数の形で格納されている
  • 仮数部は,実際の仮数部の先頭の「1」を取り除いた残りが格納されている.仮数部の先頭は必ず1にしてメモリに格納しないことで1ビット分精度を稼いでいる

数値$x$は

$$x=\pm\; 1.d_1d_2\cdots d_{52}\times 2^{m}=\pm\left(\frac{1}{2^0}+\frac{d_1}{2^1}+\frac{d_2}{2^2}+\cdots+\frac{d_{52}}{2^{52}}\right)2^{e}_{(10)}$$

と書ける($-1022\le e\le 1023$,$m$:$e+1023$の2進表現).

例えば,5.25は2進数で書くと $$ 101.01_{(2)} = \left(\frac{1}{2^0}+\frac{0}{2^1}+\frac{1}{2^2}+\frac{0}{2^{3}}+\frac{1}{2^4}\right)\times 2^2_{(10)} $$ であるから,計算機内では

$\fbox{0}\fbox{10000000001}\fbox{0101000000000000000000000000000000000000000000000000}$

のように格納されている。指数部の「1000000001」は、「2+1023=1025」 を2進数にしたもの.

これは正規化数と呼ばれる数の範囲.

実際にJuliaのbitstring関数を使って浮動小数点数を見てみよう.

In [7]:
x = 5.25
println(bitstring(x))
println(bitstring(5.24))
0100000000010101000000000000000000000000000000000000000000000000
0100000000010100111101011100001010001111010111000010100011110110
In [8]:
binx = bitstring(x) 
sign = binx[1]; println(sign)
exp  = binx[2:12]; println(exp)
frac = binx[13:end]; println(frac)
0
10000000001
0101000000000000000000000000000000000000000000000000

符号はsign = 0で正の数, 指数部はexp-1023=1025-1023=2,
仮数部はfrac=(101000000000000000000000000000000000000000000000000)$_2$で2進数表示され,実際の値は1.3125,
元の浮動小数点数に戻すと

$$(-1)^{\mathrm{sign}}*\mathrm{frac2}*2^{\mathrm{exp}-1023}=5.25$$

で次のように確かに元の数に戻ることがわかる.

In [9]:
aa = [parse(Int, f) for f in frac];
bb = 2. .^(1:52);
dfrac = 1 + sum(aa ./ bb); println(dfrac)
cc = [parse(Int, f) for f in exp]
dexp = sum(cc .* (2 .^(10:-1:0))) - 1023; println(dexp)
x = (-1)^(parse(Int,sign))*dfrac*2^(dexp); println(x)
1.3125
2
5.25

次に$e$の範囲$-1022\le e\le 1023$に注目する.

$e+1023$が11ビット符号無しの整数なら範囲は$0$〜$2047$であるが,$e$範囲から $e+1023=0$のときと$e+1023=2047$のときが使われていない.

これらは特殊な数を表すのに使われる.それらは

  • 零
  • 無限大 (Inf)
  • NaN (Not a Number)
  • 非正規化数

と呼ばれる.

零¶

指数部が$e+1023=0$かつ仮数部が0のとき.

$$\pm\; 0.00\cdots 0\times 2^{0}={\pm\left(\frac{0}{2^0}+\frac{0}{2^1}+\frac{0}{2^2}+\cdots+\frac{0}{2^{52}}\right)2^{-1023}}_{(10)}.$$
In [10]:
x = 0.0; println(x), println(bitstring(x))
x = -0.0; println(x), println(bitstring(x))
0.0
0000000000000000000000000000000000000000000000000000000000000000
-0.0
1000000000000000000000000000000000000000000000000000000000000000
Out[10]:
(nothing, nothing)

無限大¶

$e+1023=2047$かつ仮数部が0のとき,$\pm\infty$を表す.

$$\pm\; 1.00\cdots 0\times 2^{m}={\pm\left(\frac{1}{2^0}+\frac{0}{2^1}+\frac{0}{2^2}+\cdots+\frac{0}{2^{52}}\right)2^{1024}}_{(10)},$$

$m$: 2047の2進表現. このときオーバーフローが起こるという.

In [11]:
x = Inf; println(x), println(bitstring(x))
println(-x), println(bitstring(-x))
Inf
0111111111110000000000000000000000000000000000000000000000000000
-Inf
1111111111110000000000000000000000000000000000000000000000000000
Out[11]:
(nothing, nothing)

NaN (Not a Number)¶

$e+1023=2047$かつ仮数部が0でないとき.

負数の平方根など,不可能な演算の結果を表すのに使われる.

In [12]:
x = Inf; println(bitstring(x))
x = NaN; println(x), println(bitstring(x))
x = Inf - Inf; println(x), println(bitstring(x))
x = Inf / Inf; println(x), println(bitstring(x))
0111111111110000000000000000000000000000000000000000000000000000
NaN
0111111111111000000000000000000000000000000000000000000000000000
NaN
0111111111111000000000000000000000000000000000000000000000000000
NaN
0111111111111000000000000000000000000000000000000000000000000000
Out[12]:
(nothing, nothing)

正規化数の最大最小¶

正の正規化数の最大の数は,$e+1023=2046$, かつ仮数部のビットが全て1のとき.

$${\left(\frac{{1}}{2^0}+\frac{1}{2^1}+\frac{1}{2^2}+\cdots+\frac{1}{2^{52}}\right)2^{{1023}}}_{(10)}= 2^{1024}-2^{971}\approx10^{308.25}$$

であり,これを1ビットでも超えたら無限大になる.これをオーバーフローという.

In [13]:
x = (2. ^ 53 - 1) * 2. ^ 971
println(x)
println(bitstring(x))
x = (2. ^ 53) * 2. ^ 971
println(x)
println(bitstring(x))
1.7976931348623157e308
0111111111101111111111111111111111111111111111111111111111111111
Inf
0111111111110000000000000000000000000000000000000000000000000000

正の正規化数の最小の数は,$e+1023=1$, かつ仮数部のビットが全て0のとき.

$${\left(\frac{{1}}{2^0}+\frac{0}{2^1}+\frac{0}{2^2}+\cdots+\frac{0}{2^{52}}\right)2^{-1022}}_{(10)}= 2^{-1022}\approx10^{-307.65}$$

であり,これを下回るとアンダーフローというが,IEEE 754 では,ここでアンダーフローさせないで仮数部を使った「悪あがき」をする.次の例を見てみよう.

In [14]:
x = 2. ^ (-1022)
println(x)
println(bitstring(x))
y = x-2^(-1074);
println(y)
println(bitstring(y))
2.2250738585072014e-308
0000000000010000000000000000000000000000000000000000000000000000
2.225073858507201e-308
0000000000001111111111111111111111111111111111111111111111111111

非正規化数¶

指数部が$e+1023=0$かつ仮数部が0でないとき,

仮数部の最初の桁を$0$にして

$$\pm\; 0.d_1d_2\cdots d_{52}\times 2^{0}={\pm\left(\frac{\color{red}0}{2^0}+\frac{d_1}{2^1}+\frac{d_2}{2^2}+\cdots+\frac{d_{52}}{2^{52}}\right)2^{{\color{red}{-1022}}}}_{(10)}.$$

という数の表現をする.つまり指数部が$e+1023=1$よりも小さくなったら、正規化しないで指数部は$e+1023=1$のままにする. 代わりに仮数部の最初の桁を$0$にする. このような決まりによって, 非正規化数は文字通り「正規化していない」数となる.

漸近アンダーフロー¶

上の例のように正規化数の最小数の最終bitを1だけ減らすと

$${\left(\frac{{0}}{2^0}+\frac{1}{2^1}+\frac{1}{2^2}+\cdots+\frac{1}{2^{52}}\right)2^{-1022}}_{(10)}$$

となり,これを正規化すると

$${\left(\frac{{1}}{2^0}+\frac{1}{2^1}+\frac{1}{2^2}+\cdots+\frac{0}{2^{52}}\right)2^{-1023}}_{(10)}$$

となって,指数部の下限$-1022\le e$を超えてしまう. そこで,「$2^{-1022}$を下回ったら正規化をやめて指数部を$2^{-1022}$に固定して仮数部の最初の桁を0としてみて格納する」ルールが発動し, 非正規化数での数の表現が始まる. 以下, 1ビットずつ減らしていくと

$${\left(\frac{{0}}{2^0}+\frac{1}{2^1}+\frac{1}{2^2}+\cdots+\frac{1}{2^{52}}\right)2^{-1022}}_{(10)}$$$${\left(\frac{{0}}{2^0}+\frac{1}{2^1}+\frac{1}{2^2}+\cdots+\frac{0}{2^{52}}\right)2^{-1022}}_{(10)}$$$$\vdots$$$$\left(\frac{{0}}{2^0}+\frac{1}{2^1}+\frac{0}{2^2}+\cdots+\frac{0}{2^{52}}\right)2^{-1022}$$$${\left(\frac{{0}}{2^0}+\frac{0}{2^1}+\frac{1}{2^2}+\cdots+\frac{1}{2^{52}}\right)2^{-1022}}_{(10)}$$$$\vdots$$$${\left(\frac{{0}}{2^0}+\frac{0}{2^1}+\frac{0}{2^2}+\cdots+\frac{1}{2^{52}}\right)2^{-1022}}_{(10)}=2^{-1074}\approx10^{-323.31}$$

のような数が表現できる.ただし,$2^{-1022}$と$2^{-1074}$の間の数は,本来53ビットあるべき仮数部の長さが52ビット〜1ビットまで減ってしまっており,精度が低下していることに注意が必要である.

In [15]:
x = 2. ^ (-1074)
println(x)
println(bitstring(x))

x = (2. ^ (-1074)) / 2
println(x)
println(bitstring(x))
5.0e-324
0000000000000000000000000000000000000000000000000000000000000001
0.0
0000000000000000000000000000000000000000000000000000000000000000

浮動小数点数まとめ¶

倍精度(binary64, Float64/double)は

仮数部が0 仮数部が0でない
$e+1023=0$ $\pm0$ 非正規化数
$1\le e+1023\le 2046$ 正規化数 正規化数
$e+1023=2047$ $\pm\infty$ NaN

単精度 (binary32, Float32/float) は

仮数部が0 仮数部が0でない
$e+127=0$ $\pm0$ 非正規化数
$1\le e+127\le 254$ 正規化数 正規化数
$e+127=255$ $\pm\infty$ NaN

今後,浮動小数点数全体の集合を$\mathbb{F}$と表すことにする.特に断りがなければ,浮動小数点数は倍精度浮動小数点数(64bit)とする.IEEE754ではbinary64とも呼ばれている.

謝辞¶

本資料は筆者が学生の頃に精度保証付き数値計算を教えて下さった柏木雅英先生の「数値解析特論」の講義資料が基になっています. また, 以下のような文献・Web ページ等を参考にこの文章は書いています.

参考文献¶

  1. 伊理正夫, 藤野和建, 数値計算の常識, 共立出版, 1985.

(Twitterとかでも度々話題に上がる名著. IEEE754 の制定の年にすでに浮動小数点数に対する注意が詰まっている書籍が出版されている)

  1. 齊藤宣一, 数値解析入門, 東京大学出版会, 2012.

(数値解析学の現在最も詳しい教科書. 浮動小数点数についても1章に詳しく記述がある.)

  1. 大石進一編著, 精度保証付き数値計算の基礎, コロナ社, 2018.

(精度保証付き数値計算の教科書. 浮動小数点数および区間演算に詳しい. この1章が読めたら大したもの)

  1. ushiostarfish, IEEE 754 浮動小数点入門.

(IEEE 754 浮動小数点数を細かく紹介し, 丸め誤差の詳細, および区間演算について触れている)

  1. Nick Higham, What Is Floating-Point Arithmetic/IEEE Standard Arithmetic.

(数値解析の超有名人によるブログ記事, (IEEE754/854)浮動小数点数について端的にまとめられている)

高安亮紀,2020年7月17日(最終更新:2023年5月20日)