単精度電卓 数値誤差
電卓やコンピュータの計算には数値誤差(numerical error)があります。
「なんだかよく分からんけど、コンピュータが計算した数値なんだから間違いはない」と思い込んではいませんか?
数値誤差を実感してもらうために、あえて精度が悪い単精度の電卓を作りました。
「小数」や「割り算」を扱った瞬間に数値誤差が発生することを実感してください。
また、代表的な数値誤差の「打切り誤差(丸め誤差)」や「情報落ち」「桁落ち」について説明します。
この JavaScript は Internet Exploer では動作しません。Edge か Chrome か Safari を使ってください。
小学校の時に習った小数点の付け方は固定小数点数(fixed-point number)という形式でした。
一般のコンピュータでは小数データは浮動小数点数(floating point number)という形式で扱われます。
浮動小数点では以下のように数を仮数(mantissa)と指数(exponent)で表現します。
\begin{align*}
\mbox{数} = \mbox{仮数} \times 10^{\mbox{指数}}
\end{align*}
また、仮数が $10^0$ の位、つまり1の位から始まるように、指数の値を調整するのですが、このように調整することを正規化(normalization)といいます。
正規化した浮動小数点数では、「仮数の絶対値は$1$以上で$10$未満」になります。
\begin{align*}
1\le \left| \mbox{仮数}\right| \lt 10
\end{align*}
以下の枠内の式の左辺は10進法の固定小数表現です。右辺が正規化した浮動小数表現です。
何度か手を動かして式を書いて、指数と仮数の感覚をつかでください。
\begin{alignat*}{3}
0.002345 &= 2.345\times 10^{-3} &\qquad& \mbox{「$2.345$」が仮数} &\quad& \mbox{「$-3$」が指数}\\
45678 &= 4.5678\times 10^4 & & \mbox{「$4.5678$」が仮数} & & \mbox{「$4$」が指数}\\
-765.45 &= -7.6545\times 10^2 & & \mbox{「$-7.6454$」が仮数} & & \mbox{「$2$」が指数}
\end{alignat*}
コンピュータの内部では小数も、10進法ではなく、2進法で表現しています。
例えば10進法の「0.15625」は2進法の「0.00101」に対応します。
\begin{align*}
\mbox{(2進法)}\; 0.00101
&\rightarrow 0\cdot 2^0 + 0\cdot 2^{-1} + 0\cdot 2^{-2} + 1\cdot 2^{-3} + 0\cdot 2^{-4} + 1\cdot 2^{-5} \\
&= 2^{-3} + 2^{-5} = \frac{1}{8} + \frac{1}{32} = 0.125 + 0.03125 \\
&= 0.15625 \;\mbox{(10進法)}
\end{align*}
2進法の「0.00101」を正規化した浮動小数点形式で表現すると下の式のようになります。左辺が固定小数表現、右辺が正規化浮動小数表現です。(右辺の基数「10」は「10進法のジュウ」ではなく「2進法のイチ・ゼロ(つまり10進法の2)」です。また指数の「11」は「2進法のイチ・イチ(つまり10進法の3)」です。注意してください。)
仮数は「1.01」に、指数は「-11」になります。
\begin{align*}
\mbox{(2進法の固定小数)}\; 0.00101=1.01\times 10^{-11}\;\mbox{(2進法の正規化浮動小数)}
\end{align*}
2進法に関しては(こちら)で詳しく説明してます。
また、浮動小数点については(こちら)で詳しく説明してます。
もうちょっと詳しくなりたい人は参考にしてください。
メモリなどにデータを格納するときには、符号(sign)と、指数部の値と、仮数部の値、を格納します。
各部に何ビットの領域をどのような表現で割り当てるのか、によって様々な規格があります。
国際規格のIEEE 754では以下の浮動小数点数(floating-point number)を定めています。
- 単精度(single-precision)binary32
±の符号:1ビット、指数部:8ビット、仮数部:23ビット
計:32ビット(4バイト)
- 倍精度(double-precision)binary64
±の符号:1ビット、指数部:11ビット、仮数部:52ビット
計:64ビット(8バイト)
- 四倍精度(quadruple-precision)binary128
±の符号:1ビット、指数部:15ビット、仮数部:112ビット
計:128ビット(16バイト)
この他にも半精度や八倍精度や拡張精度などがあります。
単精度は16ビットや32ビットのバスが主流だった時代に使われていたもので、現在は殆ど使われていません。
現在の主流は倍精度になります。
一部のプログラミング言語では四倍精度が扱えます。
専用機の中には四倍精度に特化したハードウェア設計のものがあります。
単精度では仮数部に23ビットの領域を割り当てています。
これは仮数部として2進法で24桁の精度を確保しているということになります。
2進法の正規化した浮動小数点数では仮数の最初の一桁は必ず1になるので、その部分にはメモリを割り当てなくて済みます。
(10進法では仮数の最初の一桁は1~9のどの数値になるか分かりません。)
2進法の24桁が10進法の何桁にあたるのか計算すると、$\log_{10}2^{24}\fallingdotseq 7.22$ となります。
2進法の24桁は10進法の7桁の精度にあたると考えればよいです。
単精度
ビット番号 | 031 | 030 | 029 | 028 | 027 | 026 | 025 | 024 | 023 | 022 | 021 | 020 | 019 | 018 | 017 | 016 | 015 | 014 | 013 | 012 | 011 | 010 | 009 | 008 | 007 | 006 | 005 | 004 | 003 | 002 | 001 | 000 |
ビット数カウント | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
内訳 | 符号 | 指数部 8 ビット | 仮数部 23 ビット |
倍精度では仮数部に52ビットの領域を割り当てています。
これは仮数部として2進法で53桁の精度を確保しているということになります。
2進法の53桁が10進法の何桁にあたるのか計算すると、$\log_{10}2^{53}\fallingdotseq 15.95$ となります。
2進法の53桁は10進法の15桁の精度にあたると考えればよいです。
倍精度
ビット番号 | 063 | 062 | 061 | 060 | 059 | 058 | 057 | 056 | 055 | 054 | 053 | 052 | 051 | 050 | 049 | 048 | 047 | 046 | 045 | 044 | 043 | 042 | 041 | 040 | 039 | 038 | 037 | 036 | 035 | 034 | 033 | 032 | 031 | 030 | 029 | 028 | 027 | 026 | 025 | 024 | 023 | 022 | 021 | 020 | 019 | 018 | 017 | 016 | 015 | 014 | 013 | 012 | 011 | 010 | 009 | 008 | 007 | 006 | 005 | 004 | 003 | 002 | 001 | 000 |
ビット数カウント | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
内訳 | 符号 | 指数部 11 ビット | 仮数部 52 ビット |
四倍精度では仮数部に112ビットの領域を割り当てています。
これは仮数部として2進法で113桁の精度を確保しているということになります。
2進法の113桁が10進法の何桁にあたるのか計算すると、$\log_{10}2^{113}\fallingdotseq 34.02$ となります。
2進法の113桁は10進法の34桁の精度にあたると考えればよいです。
四倍精度
ビット番号 | 127 | 126 | 125 | 124 | 123 | 122 | 121 | 120 | 119 | 118 | 117 | 116 | 115 | 114 | 113 | 112 | 111 | 110 | 109 | 108 | 107 | 106 | 105 | 104 | 103 | 102 | 101 | 100 | 099 | 098 | 097 | 096 | 095 | 094 | 093 | 092 | 091 | 090 | 089 | 088 | 087 | 086 | 085 | 084 | 083 | 082 | 081 | 080 | 079 | 078 | 077 | 076 | 075 | 074 | 073 | 072 | 071 | 070 | 069 | 068 | 067 | 066 | 065 | 064 | 063 | 062 | 061 | 060 | 059 | 058 | 057 | 056 | 055 | 054 | 053 | 052 | 051 | 050 | 049 | 048 | 047 | 046 | 045 | 044 | 043 | 042 | 041 | 040 | 039 | 038 | 037 | 036 | 035 | 034 | 033 | 032 | 031 | 030 | 029 | 028 | 027 | 026 | 025 | 024 | 023 | 022 | 021 | 020 | 019 | 018 | 017 | 016 | 015 | 014 | 013 | 012 | 011 | 010 | 009 | 008 | 007 | 006 | 005 | 004 | 003 | 002 | 001 | 000 |
ビット数カウント | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
内訳 | 符号 | 指数部 15 ビット | 仮数部 112 ビット |
内部表現とは「コンピュータ内部で実際にどのように数値を表現しているか」「メモリ上にどのように数値を格納しているか」という意味です。
「データをどのように符号化するか」という意味で、エンコーディング(encoding)ともいいます。
IEEE 754 規格の単精度では、符号部に1ビット、指数部に8ビット、仮数部に23ビット、計32ビットを割り当てています。
単精度実数には、通常の浮動小数点数を表す正規化数と、有効数字を犠牲にした非正規化数があります。
この点に関しては、後の節で説明する有効数字の概念を理解してからもう一度見直すと、よりよく理解できると思います。
符号部(sign)
符号部は正負の符号を表す部分です。
- 正符号、つまり + の時は「0」
- 負符号、つまり - の時は「1」
指数部(exponent)
指数部にはオフセットバイナリ(offset binary)という方式を採用しています。
単精度ではバイアス値127(2進法で 01111111 に相当)を加えます。
指数部の8ビットで256種類の情報を扱えますが、バイアスを加えて数値をずらす(オフセットする)ことで、-126から+127までの254種類の指数の数値を、1~254(2進法で 00000001~11111110)に変換して表現します。
オフセットバイナリを用いることで、負の指数を符号ビットを使わずに表現しているわけです。
具体的には以下のような操作を行います。
- 指数部が「100(10進法で4)」の時は 「 01111111 + 00000100 = 10000011 (10進法では 127 + 4 = 131)」を計算し、「10000011」 で指数部を表現します。
- 指数部が「-100(10進法で-4)」の時は 「 01111111 - 00000100 = 01111011 (10進法では 127 - 4 = 125)」を計算し、「01111011」 で指数部を表現します。
指数部のビットを「00000000」のように全て「0」で埋めたものは、ゼロや非正規化数という数の為に用います。
また、指数部のビットを「11111111」のように全て「1」で埋めたものは、無限大などの特殊な用途に用います。
仮数部(fraction)
仮数部は一番左の「1.」を取り除いて表現します。
ゼロだけは例外なのですが、2進法の正規化した浮動小数点数では、仮数部の1の位は必ず1になります。
この1の位の「1.」を取り除くことで1ビット稼いでいるわけです。
この表現をケチ表現(hidden bit)といいます。
- 仮数部が「1.10011001100110011001101」の時は「1.」を取り除いた「10011001100110011001101」で仮数部を表現します。
- 仮数部が「1.00011001100110011001101」の時は「1.」を取り除いた「00011001100110011001101」で仮数部を表現します。
指数部が「00000000」の非正規化数では、指数部を -126 に固定します。
そしてこの場合、仮数部には一番左の「0.」を取り除いたものが入ります。
そのため、非正規化数では有効数字の桁数は24桁保てなくなってしまいます。
- 非正規化数で仮数部が「0.00000001100110011001101」の時は「0.」を取り除いた「00000001100110011001101」で仮数部を表現します。
この場合の有効数字の桁数は16桁になります。
- 非正規化数で仮数部が「0.00000000000110011001100」の時は「0.」を取り除いた「00000000000110011001100」で仮数部を表現します。
この場合の有効数字の桁数は12桁になります。
[0][・][1]と打ち込んで「0.1」を表示させた状態で[=]を打ち込んでください。
表示されている10進法の「0.1」が単精度に変換されて内部メモリに格納されます。
内部メモリに格納された値を再度10進法表記に戻した値を見ると小数点以下9桁目あたりで「0.1」からズレてきます。
このズレが打切り誤差というものです。
右上に表示されている記号は一つ前に打ち込んだ演算記号を記憶している蘭になります。
右上の表示に「=」記号が表示されてる状態で[=]を打ち込むと、内部メモリを「0」にクリアします。
[0][・][2][=]など様々な値を打ち込んで打切り誤差の状況を見てみましょう。
[0][・][9][-][0][・][8][=]と打ち込めば。「0.9-0.8」を計算します。
「-」ボタンを打ち込んだ時に「0.9」を単精度に変換して内部メモリに格納します。
最後の「=」ボタンを打ち込んだ時に「0.8」を単精度に変換したものと、内部メモリに格納してある単精度の引き算を実行して、結果を内部メモリに格納します。
「0.1」を10回足してみて、どれくらいの計算誤差が累積されるのか確認してみましょう。
打切り誤差(truncation error)とは「原理的には無限回行わなければならない計算を、有限回で打ち切ったために生じる誤差」のことです。
割り切れない割り算を途中で打ち切った際にでてくる誤差のことです。
打ち切る際に四捨五入のような数値を丸める操作を行うので丸め誤差(round-off error)とも言います。
また、打切り誤差の原因には10進法と2進法の相性の問題もあります。
10進法では有限小数で表されている数でも、2進法では無限小数になる例が多々あります。
(こちら)で詳しく解説していますので参考にしてください。
例えば10進法の「0.1」を2進法に変換すると途中から循環節「0011」が出てきて永遠に循環節を繰り返します。
\begin{align*}
\mbox{(10進法小数)}\; 0.1\rightarrow
0.0\;0011\;0011\;0011\;0011\;0011\;0011\cdots\;\mbox{(2進法小数)}
\end{align*}
メモリに格納するために単精度浮動小数点数に変換します。
仮数部を24桁に零捨一入(四捨五入の2進法版)して、浮動小数点表示にします。
\begin{align*}
0.00011001100110011001100110\cancelto{1}{0}\cancel{1100\cdots}\fallingdotseq 0.000110011001100110011001101
\end{align*}
\begin{align*}
0.000110011001100110011001101=1.10011001100110011001101\times 10^{-100}
\end{align*}
有限桁で打ち切る場合に、切り捨てるのか切り上げるのか、は演算装置やコンパイラ等の処理系の仕様に依ります。ここでは零捨一入しましたが、処理の手軽さから切り捨て処理で実装している場合があります。
仮数部を24桁の有限な桁数に打ち切ったために元の「0.1」からずれてしまいました。
ちなみに再度10進法に戻してみると次のようになり、小数第9位以降は真の値からずれてることが分かります。
\begin{align*}
&\left(2^0+2^{-1}+2^{-4}+2^{-5}+2^{-8}+2^{-9}+2^{-12}+2^{-13}+2^{-16}+2^{-17}+2^{-20}+2^{-21}+2^{-23}\right)\cdot 2^{-4} \\
&=\left(2^{23}+2^{22}+2^{19}+2^{18}+2^{15}+2^{14}+2^{11}+2^{10}+2^7+2^6+2^3+2^2+2^0\right)\cdot 2^{-27} \\
&=\left(2^{23}+2^{22}+2^{19}+2^{18}+2^{15}+2^{14}+2^{11}+2^{10}+2^7+2^6+2^3+2^2+2^0\right)\cdot 5^{27}\cdot \left(5\cdot 2\right)^{-27} \\
&=\left(5^4\cdot 10^{23}+5^5\cdot 10^{22}+5^8\cdot 10^{19}+5^9\cdot 10^{18}+5^{12}\cdot 10^{15}+5^{13}\cdot 10^{14}+5^{16}\cdot 10^{11}+5^{17}\cdot 10^{10}+5^{20}\cdot 10^7+5^{21}\cdot 10^6+ 5^{24}\cdot 10^3+5^{25}\cdot 10^2+5^{27}\cdot 10^0\right)\cdot 10^{-27} \\
&=\left(625\cdot 10^{23}+3125\cdot10^{22}+390625\cdot 10^{19}+1953125\cdot 10^{18}+244140625\cdot 10^{15}+1220703125\cdot 10^{14}+152587890625\cdot 10^{11}+762939453125\cdot 10^{10}+95367431640625\cdot 10^7+476837158203125\cdot 10^6+59604644775390625\cdot 10^3+298023223876953125\cdot 10^2+7450580596923828125\cdot 10^0\right)\cdot 10^{-27} \\
&=100000001490116119384765625\times 10^{-27} \\
&=0.100000001490116119384765625
\end{align*}
比較のため、倍精度で10進法の0.1がどうなるか、同様のことをやってみると小数第18位以降が真の値からずれてることが分かります。
メモリに格納するために倍精度浮動小数点数に変換します。
仮数部を53桁に零捨一入(四捨五入の2進法版)して、浮動小数点表示にします。
\begin{align*}
0.000110011001100110011001100110011001100110011001100110\cancelto{1}{0}\cancelto{0}{1}\cancel{10011\cdots}\fallingdotseq 0.00011001100110011001100110011001100110011001100110011010
\end{align*}
\begin{align*}
0.00011001100110011001100110011001100110011001100110011010=1.1001100110011001100110011001100110011001100110011010\times 10^{-100}
\end{align*}
仮数部を53桁の有限な桁数に打ち切ったために元の「0.1」からずれてしまいました。
ちなみに再度10進法に戻してみると次のようになります。
\begin{align*}
&\left(2^0+2^{-1}+2^{-4}+2^{-5}+2^{-8}+2^{-9}+2^{-12}+2^{-13}+2^{-16}+2^{-17}+2^{-20}+2^{-21}+2^{-24}+2^{-25}+2^{-28}+2^{-29}+2^{-32}+2^{-33}+2^{-36}+2^{-37}+2^{-40}+2^{-41}+2^{-44}+2^{-45}+2^{-48}+2^{-49}+2^{-51}\right)\cdot 2^{-4} \\
&=\left(2^{51}+2^{50}+2^{47}+2^{46}+2^{43}+2^{42}+2^{39}+2^{38}+2^{35}+2^{34}+2^{31}+2^{30}+2^{27}+2^{26}+2^{23}+2^{22}+2^{19}+2^{18}+2^{15}+2^{14}+2^{11}+2^{10}+2^{7}+2^{6}+2^{3}+2^{2}+2^{0}\right)\cdot 2^{-55} \\
&=\left(2^{51}+2^{50}+2^{47}+2^{46}+2^{43}+2^{42}+2^{39}+2^{38}+2^{35}+2^{34}+2^{31}+2^{30}+2^{27}+2^{26}+2^{23}+2^{22}+2^{19}+2^{18}+2^{15}+2^{14}+2^{11}+2^{10}+2^{7}+2^{6}+2^{3}+2^{2}+2^{0}\right)\cdot 5^{55}\cdot \left(5\cdot 2\right)^{-55} \\
&=\left(5^4\cdot 10^{51}+5^5\cdot 10^{50}+5^8\cdot 10^{47}+5^9\cdot 10^{46}+5^{12}\cdot 10^{43}+5^{13}\cdot 10^{42}+5^{16}\cdot 10^{39}+5^{17}\cdot 10^{38}+5^{20}\cdot 10^{35}+5^{21}\cdot 10^{34}+5^{24}\cdot 10^{31}+5^{25}\cdot 10^{30}+5^{28}\cdot 10^{27}+5^{29}\cdot 10^{26}+5^{32}\cdot 10^{23}+5^{33}\cdot 10^{22}+5^{36}\cdot 10^{19}+5^{37}\cdot 10^{18}+5^{40}\cdot 10^{15}+5^{41}\cdot 10^{14}+5^{44}\cdot 10^{11}+5^{45}\cdot 10^{10}+5^{48}\cdot 10^{7}+5^{49}\cdot 10^{6}+5^{52}\cdot 10^{3}+5^{53}\cdot 10^{2}+5^{55}\cdot 10^{0}\right)\cdot 10^{-55} \\
&=\left(625\cdot 10^{51}+3125\cdot 10^{50}+390625\cdot 10^{47}+1953125\cdot 10^{46}+244140625\cdot 10^{43}+1220703125\cdot 10^{42}+152587890625\cdot 10^{39}+762939453125\cdot 10^{38}+95367431640625\cdot 10^{35}+476837158203125\cdot 10^{34}+59604644775390625\cdot 10^{31}+298023223876953125\cdot 10^{30}+37252902984619140625\cdot 10^{27}+186264514923095703125\cdot 10^{26}+23283064365386962890625\cdot 10^{23}+116415321826934814453125\cdot 10^{22}+14551915228366851806640625\cdot 10^{19}+72759576141834259033203125\cdot 10^{18}+9094947017729282379150390625\cdot 10^{15}+45474735088646411895751953125\cdot 10^{14}+5684341886080801486968994140625\cdot 10^{11}+28421709430404007434844970703125\cdot 10^{10}+3552713678800500929355621337890625\cdot 10^{7}+17763568394002504646778106689453125\cdot 10^{6}+2220446049250313080847263336181640625\cdot 10^{3}+11102230246251565404236316680908203125\cdot 10^{2}+277555756156289135105907917022705078125\cdot 10^{0}\right)\cdot 10^{-55} \\
&=1000000000000000055511151231257827021181583404541015625\times 10^{-55} \\
&=0.1000000000000000055511151231257827021181583404541015625
\end{align*}
小数を入力しただけで、本当の値からずれてしまってるわけです。
単精度では10進法で7桁、倍精度では10進法で15桁程度が精度の目安だと思っていてください。
打切り誤差や丸め誤差以外にも、数値誤差には仮数の桁数制限に起因する数値誤差があります。
情報落ち(loss of trailing digits)と、桁落ち(loss of significant digits)です。
2進法だと分かり難いので、分かり易い10進法で説明します。
情報落ち
情報落ちは桁が大きく違う数の足し算や引き算で発生します。
例えば仮数7桁制限の10進法で $1.234567\times 10^6 + 2.345678\times 10^2$ を計算すると次のようになってしまいます。
\begin{align*}
&\phantom{+)}\quad 1.234567\phantom{0000} \times 10^6 \\
&\underline{+)\quad 0.0002345678 \times 10^6} \\
&\phantom{+)}\quad 1.234801\color{red}{5678} \times 10^6 \\
\end{align*}
赤字部分の情報が仮数7桁に収まらずに落ちてしまうことになります。
情報落ちが問題となる分かりやすい例が、大量の数の合計になります。
100万個の同じ程度の正の値のデータを1個ずつ足し上げていくと、どんどん膨れ上がっていき元のデータと桁違いの値になります。
桁違いに膨らんだ中間段階の合計に、追加でデータを足し上げる際に、情報落ちが起こります。
このようなことを回避する為に、足し上げる順番を変えるなど、アルゴリズムを工夫する必要があります。
また、使える場面が限られますが整数データでは浮動小数点数を使わないというのも解決策の一つになります。
単精度を使わないで、なるべく高い精度の浮動小数点数を使うということでも、ある程度は改善します。
桁落ち
桁落ちは、英語を直訳した表現の方が分かりやすいです。
有効数字の桁落ちです。
ほぼ同じ大きさの値の数同士の引き算で発生する、有効数字の桁の減少のことです。
有効数字とは、確かな精度を確保している数字のことです。
有効数字が何桁あるかで、「その数値が何桁信頼できるか」という数値の精度が決まります。
有効数字の桁数は大きければ大きいほど良いのですが、限界があります。
その時々の状況によって、最終的に確保したい精度の桁数を設定することになります。
有効数字より下の桁の数字には意味が有りませんのでゴミ数字と呼ばれます。
最終結果にはゴミ数字を表示しないのが大人のマナーです。
科学表記と呼ばれる表記では、浮動小数点数の仮数には有効数字のみを表示します。
例えば仮数7桁制限の10進法で $1.234567\times 10^5 - 1.234321\times 10^5$ を計算すると次のようになってしまいます。
\begin{align*}
&\phantom{-)}\quad 1.234567 \times 10^5 \\
&\underline{-)\quad 1.234321 \times 10^5} \\
&\phantom{-)}\quad 0.000246 \times 10^5 \\
&\;\;\; = 2.46\phantom{0000} \times 10^1 \\
&\;\;\; = 2.46\color{red}{0000} \times 10^1 \\
\end{align*}
赤字部分を0で埋めて仮数を7桁にしてますが、元々あった有効数字7桁が、桁落ちのため有効数字3桁になってしまっています。
この有効数字が3桁になってしまった数値を次の計算に用いると、「誤差の伝搬」という性質によって、次の計算結果の有効数字は3桁以下になってしまいます。
有効数字の桁数は、一度落ちたら取り返せないと思ってください。
桁落ちが大きな問題となる分かりやすい例が、数値微分の問題です。
分母・分子がほぼ0に近くなってしまうので、何も考えずに引き算をしてしまうと非常にまずい状況になります。
とにかく桁落ちという現象があるという知識を持っておいて、「近い値の引き算を避ける」ようにアルゴリズムを工夫するしかありません。
また、単精度を使わないで、なるべく高い精度の浮動小数点数を使うということでも、ある程度は改善します。
桁落ちを実感してもらう例として、$\sqrt{1001}-\sqrt{999}$ の計算をしてみましょう。
後の比較のため、有効数字25桁で計算しておきます。
\begin{align*}
\sqrt{1001} &= 31.638 584 039 112 749 143 106 29 \\
\sqrt{999} &= 31.606 961 258 558 216 545 204 21 \\
\sqrt{1001} - \sqrt{999} &= \phantom{0}0.031 622 780 554 532 597 902 08
\end{align*}
計算前の数値では有効数字は25桁だったものが、計算後の数値では有効数字が22桁になります。
有効数字7桁で計算してみましょう。
\begin{align*}
\sqrt{1001} &= 31.638 58 \\
\sqrt{999} &= 31.606 96 \\
\sqrt{1001} - \sqrt{999} &= \phantom{0}0.03162
\end{align*}
そのままの計算では、計算前の数値では有効数字は7桁だったものが、計算後の数値では有効数字は4桁になってしまいます。
そこで、有理化の逆を行うアルゴリズムで、引き算を避けて計算(有効数字7桁のまま手計算)してみます。
\begin{align*}
\sqrt{1001} - \sqrt{999} &= \frac{\left(\sqrt{1001}-\sqrt{999}\right)\left(\sqrt{1001}+\sqrt{999}\right)}{\sqrt{1001} + \sqrt{999}} \\[3pt]
&= \frac{\sqrt{1001^2}-\sqrt{999^2}}{\sqrt{1001} + \sqrt{999}} \\[3pt]
&= \frac{2}{\sqrt{1001} + \sqrt{999}} \\[3pt]
&= \frac{2}{31.63858 + 31.60696} \\[3pt]
&= \frac{2}{63.24554} \\[3pt]
&= \frac{100000}{3162277} \\[3pt]
&= 0.031622783203\ldots \\[3pt]
&= 0.03162278\color{red}{3203\ldots}
\end{align*}
最後の結果を有効数字25桁の結果と比較すると、赤の部分以外の7桁の数値が正しいことが分かります。
引き算以外の四則演算、足し算・掛け算・割り算、では桁落ちが起きないので、計算後も7桁の精度(有効数字)が確保できているというわけです。
コンピュータ内部では2進法の浮動小数点で計算していますが、桁落ちが起こる仕組みは上記例の10進法の手計算の場合と同じです。$\sqrt{1001}-\sqrt{999}$ を2進法で計算してみましょう。以下の枠内の $\rightarrow$ は2進法と10進法の基数変換を表しています。
後の比較のため、2進法の有効数字53桁(倍精度)で計算しておきます。
\begin{align*}
\sqrt{1001} &\rightarrow 11111.10100 01101 11101 00011 11100 10110 11101 11100 10100 010 \rightarrow 31.63858403911275\color{red}{019\ldots} \\
\sqrt{999} &\rightarrow 11111.10011 01101 10000 11101 00000 01000 11011 10010 01010 000 \rightarrow 31.60696125855821\color{red}{492\ldots} \\
\sqrt{1001} - \sqrt{999} &\rightarrow \phantom{0000}0.00001 00000 01100 00110 11100 01110 00010 01010 01010 010 \rightarrow \phantom{0}0.03162278055453\color{red}{527\ldots}
\end{align*}
計算前の数値では有効数字は53桁だったものが、計算後の数値では有効数字が44桁になってしまいます。10進法に再変換して上の枠の10進法での計算結果と比較してみると赤字の部分が不正確で、有効数字は15桁から13桁に桁落ちしてることが分かります。
($\log_{10}2^{44}\fallingdotseq 13.2$ なので2進法の44桁は10進法の約13桁に対応します。)
有効数字24桁(単精度)で計算してみましょう。
\begin{align*}
\sqrt{1001} &\rightarrow 11111.10100 01101 11101 0010 \rightarrow 31.63858\color{red}{414\ldots} \\
\sqrt{999} &\rightarrow 11111.10011 01101 10000 1111 \rightarrow 31.60696\color{red}{220\ldots}\\
\sqrt{1001} - \sqrt{999} &\rightarrow \phantom{0000}0.00001 00000 01100 0011 \rightarrow \phantom{0}0.03162\color{red}{193\ldots}
\end{align*}
計算前の数値では有効数字は24桁だったものが、計算後の数値では有効数字が15桁になってしまいます。
10進法に再変換してみると、有効数字は7桁から4桁に桁落ちしてることが分かります。
($\log_{10}2^{15}\fallingdotseq 4.5$ なので2進法の15桁は10進法の約4桁に対応します。)
有効数字24桁(単精度)で、有理化の逆のアルゴリズムを用いて、引き算を避けて計算してみましょう。
\begin{align*}
\sqrt{1001} + \sqrt{999} &\rightarrow 111111.00111110110111000\cancelto{1}{0}\cancel{1} \rightarrow 63.24554\color{red}{825\ldots} \\[3pt]
\frac{2}{\sqrt{1001} + \sqrt{999}} &\rightarrow \frac{10}{111111.001111101101110001} \\[3pt]
&= \frac{10000000000000000000}{111111001111101101110001} \\[3pt]
&= 0.0000 10000 00110 00011 01110 0011 \cancel{0001\cdots} \rightarrow 0.03162277\color{red}{862\ldots}
\end{align*}
最後の結果を倍精度の計算結果と比較すると、2進法では24桁、10進法では7桁の有効数字を確保していることが分かります。
国際規格のIEEE 754には、通常の浮動小数点数を表す正規化数(normalized number)と、指数部を固定して有効数字を犠牲にした数を表す非正規化数(denormalized number)があります。
正規化数の範囲
単精度では、8ビットの指数部 $00000001\sim 11111110$ の範囲で、指数 $-126\sim 127$ の正規化数を表します。
2進法の正規化数では仮数の最初の桁は必ず $1.\cdots$ で始まるので、最初の桁の $1.$ を省略して、仮数の小数点以下23ビットをメモリに格納します。
有効数字は2進法で24桁(10進法で7.22桁)になります。
単精度の正規化数で最大数は次のようなビット列になります。
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
この指数部は10進法で$2^{127}$を、仮数部は10進法で$2-2^{-23}$(2進法で$1.11111111111111111111111$)を表します。
\begin{align*}
\left(2-2^{-23}\right)\times 2^{127} &= 340282346638528859811704183484516925440 \\
&= 3.40282346638528859811704183484516925440\times 10^{38} \\
&\fallingdotseq 3.4028235 \times 10^{38}
\end{align*}
単精度の正規化数で正の最小数は次のようなビット列になります。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
この指数部は10進法で$2^{-126}$を、仮数部は10進法で$1$(2進法で$1.00000000000000000000000$)を表します。
\begin{align*}
1\times 2^{-126}
&\fallingdotseq 1.1754944 \times 10^{-38}
\end{align*}
上記の間に入る数値は、10進数での有効数字の最大桁7桁程度の精度があることになります。
数の絶対値が、上記最大値を上回ることをオーバーフロー(overflow)、上記最小値を下回ることをアンダーフロー(underflow)といいます。
非正規化数の範囲
単精度の非正規化数では、指数部の8ビットを全て0で埋めたものを用いて、指数を $-126$ に固定した数を表現します。
仮数は $0.\textrm{xxxxxxxxxxxxxxxxxxxxxxx}$ で表し、最初の $0.$ を省略して、小数点以下23ビットをメモリに格納します。
そのため、仮数が $0.1\textrm{xxxxxxxxxxxxxxxxxxxxxx}$ のような数では有効数字は23桁に、仮数が $0.0001\textrm{xxxxxxxxxxxxxxxxxxx}$ のような数では有効数字が20桁になってしまいます。
単精度の非正規化数で最大数は次のようなビット列になります。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
この指数部は10進法で$2^{-126}$を、仮数部は10進法で$1-2^{-23}$(2進法で$0.11111111111111111111111$)を表します。
この数値の有効数字は23桁(10進法で6.92桁)です。
\begin{align*}
\left(1-2^{-23}\right)\times 2^{-126} &\fallingdotseq 1.175494 \times 10^{-38}
\end{align*}
単精度の非正規化数で正の最小数は次のようなビット列になります。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
この指数部は10進法で$2^{-126}$を、仮数部は10進法で$2^{-23}$(2進法で$0.00000000000000000000001$)を表します。
この数値の有効数字は1桁(10進法で0.30桁)です。
\begin{align*}
2^{-23}\times 2^{-126} &\fallingdotseq 1.4 \times 10^{-45}
\end{align*}
上記の間に入る数値では、有効数字は23桁から1桁で、数値が小さくなるに従って、有効数字の桁数は小さくなります。
非正規化数は、有効数字の桁数は保てないけど、ゼロと最小正規化数の間を補完するための数だと考えてください。
国際規格のIEEE 754には、特別な値が幾つかあります。
ここではそれらを紹介していきます。
単精度実数の例で見てみましょう。
0(ゼロ)
指数部と仮数部のビットが全て0の値は「ゼロ」を表します。
符号部のビットが0の時は「正のゼロ」、符号部のビットが1の時は「負のゼロ」をあらわします。
符号はアンダーフローや、後の無限大で割り算した時の為に存在します。
+0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-0
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
無限大(∞)
指数部のビットが全て1、仮数部のビットが全て0、は「無限大(infinity)」を表します。
無限大には符号の正負がありまして、オーバーフローや、ゼロで割り算した時の為に存在します。
+Inf
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-Inf
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
非数(NaN)
指数部のビットが全て1、仮数部のビットのどこかに1が入っているものは、非数(NaN: Not a Number) を表します。
例えば $0/0, \pm\infty\times 0, \infty + (-\infty)$ 等の計算結果が不定になる場合や、負の平方根や負の対数など虚数が現れるような演算の結果が NaN になります。
NaN の一例
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
最後に様々な規格の、精度に関する目安を纏めておきます。
| 半精度 | 単精度 | 倍精度 | x86拡張倍精度 | 四倍精度 | 八倍精度 |
全ビット数 | $16$ | $32$ | $64$ | $80$ | $128$ | $256$ |
仮数部のビット数 | $10$ | $23$ | $52$ | $64$ | $112$ | $236$ |
2進法の有効数字の桁数 | $10+1=11$ | $23+1=24$ | $52+1=53$ | $64$ | $112+1=113$ | $236+1=237$ |
10進法の有効数字の桁数 | $\log_{10}2^{11}\fallingdotseq 3.31$ | $\log_{10}2^{24}\fallingdotseq 7.22$ | $\log_{10}2^{53}\fallingdotseq 15.95$ | $\log_{10}2^{64}\fallingdotseq 19.26$ | $\log_{10}2^{113}\fallingdotseq 34.02$ | $\log_{10}2^{237}\fallingdotseq 71.34$ |
指数部のビット数 | $5$ | $8$ | $11$ | $15$ | $15$ | $19$ |
最大指数 | $2^{5-1}-1=15$ | $2^{8-1}-1=127$ | $2^{11-1}-1=1023$ | $2^{15-1}-1=16383$ | $2^{15-1}-1=16383$ | $2^{19-1}-1=262143$ |
正規化数の範囲 | $2^{-15}\sim 2^{15}$ | $2^{-127}\sim 2^{127}$ | $2^{-1023}\sim 2^{1023}$ | $2^{-16383}\sim 2^{16383}$ | $2^{-16383}\sim 2^{16383}$ | $2^{-262143}\sim 2^{262143}$ |
10進換算した正規化数の範囲 | $10^{-4.51}\sim 10^{4.51}$ | $10^{-38.23}\sim 10^{38.23}$ | $10^{-307.95}\sim 10^{307.95}$ | $10^{-4931.77}\sim 10^{4931.77}$ | $10^{-4931.77}\sim 10^{4931.77}$ | $10^{-78912.90}\sim 10^{78912.90}$ |
高い精度を保つためには有効数字の桁数がどれだけ保たれているのかが一番重要です。
倍精度実数を用いて、桁落ちなどが起こらないように注意すれば、正規化数の範囲はそれほど気にしなくてもよさそうです。
現状での標準的な計算には倍精度を用いれば良いのですが、更に高い精度で計算したい場合は拡張倍精度や四倍精度で計算します。
以下、ちょっとマニアックな話になってしまいますが、そのような場合はハードウェアについてある程度のことを知っておいた方が良いと思われます。
x86と呼ばれる古いタイプのCPU(Central Prossessing Unit; 中央処理装置)では、x87 と呼ばれるFPU(Floating Point Unit; 浮動小数点演算処理装置)による拡張機能により、倍精度や拡張倍精度の計算が可能になりました。
このFPUのレジスタで用いられている浮動小数点の形式がx86拡張倍精度というものです。
仮数は64bitで、ケチ表現を用いていませんので、有効数字の桁数は2進数で64桁になります。
x64と呼ばれる新しいタイプのCPUでは、FPUを用いず、SSE2と呼ばれる拡張命令で倍精度実数の計算命令を扱っています。
CPUの命令には拡張倍精度や四倍精度を扱う命令はありませんが、言語処理系のソフトウェア(プログラミング言語のコンパイラ)等で対応します。