概要
概要
一連の話題に関連する資料です.
内容は必ずしも FreeBSD にも i386/i387 にも限定していません.
「普通に書籍に書いてあるような話題は取り扱いません.」と書きましたが,ここは例外です.
浮動小数点数の表現や演算に関する用語解説です. 内容は基本的に参考文献 [1],[2] の丸写しですが,仮数や仮数部のところは相当書き直しました. また,それらに「(注)」と記して補足を加えています. なお,本編で使用していない用語も多数あります.
FreeBSD (5 系以降) の man page の記述とも比較して下さい. 本当は,これらの内容も反映させて,もっとすっきりした「用語解説」にしたいところです.
% jman ieee % jman fenv
データの内部形式
浮動小数点数が実際にメモリーに格納される際の書式.
符号,指数部,仮数部の bit 数と順番,およびそれらの換算,変換のされ方.
(基数はメモリーに格納されない.)
仮数 (mantissa)
浮動小数点数 x の値は,符号 (sign) s,基数 (radix) b,指数 (exponential) e,仮数 (mantissa) p から次式で計算する.
x = s*pow(b,e)*p
基数が 2 の場合には,正規化すると MSB が必ず 1 になるので,これは表示しなくてもよい. この陽に現われない 1 bit をインプリシット・ビット (implicit bit) と呼ぶ. これよって 1 bit を余分に使うことができ,精度が高くなる.
(注1) ややこしいですが,「implicit bit を持つ表現」と言ったら,この 1 bit を省略した表現のことです.
i386 + gcc においては,float と double は implicit bit を持つ表現が使われています.
(注2) 私見ですが,「仮数」という場合はあくまで上式の p に限定し,implicit bit の概念はデータの内部形式,すなわち下記の「仮数部」または「有効数字部」に対して適用するのが良いでしょう.
仮数部,有効数字部 (significant)
仮数のうち浮動小数点数の内部形式の中に存在している (すなわちメモリーに格納された) 部分のこと.
implicit bit を持たない表現に対しては有効数字部と仮数とは一致するが,それを持つ表現に対しては有効数字部の前に 1 bit を追加したものが仮数となる.
(注1) 二つの呼称は同じものを指しています.
一般には「仮数部」の方が使われていますが,上の解説は文献 [2] に依るのでそれに従って「有効数字部」を使いました.
(注2)「内部形式のこの部分」を指すために「指数部」「仮数部」のように「部」を付加していると解釈すれば良いでしょう.
(注3)「有効数字部」が「仮数」と混同されている場合もあります.
例えば man ieee では "significant bit" は仮数の bit 数の意味で使われていますが,この用語解説を正とするならば誤用です.
指数 (expnent)
浮動小数点数において,基数のべき乗の項を,基数を省略してべきだけで表わしたもの.
そのデータのオーダーを示す.
浮動小数点表示したデータの大きさを変化させていくと,これが一定で仮数だけが変わっていく値の範囲がある.
これを,基数が 2 ならばバイネード (binade),10 ならばディケード (decade) のように表現する.
あるバイネード,ディケードのなかでは,値の分解能は等しい.
指数は実際の値にある一定の値が加えられたものが使われている. このことは「バイアス付き」とか「ゲタはき」とか呼ばれている. これにより,負のある値から正のある値までを,正の値だけで処理することができ,符号の煩わしさを避けることができる.
正規化 (normalization)
仮数の有効数字を最大限にとるために,仮数を,最上位桁に 0 が立たなくなるまで左にずらし,同時に指数を減らす操作.
仮数の先頭桁が 0 のデータを,一般に非正規化数 (not normalized number) といい,IEEE 規格ではアンノーマル (unnormal),デノーマル (denormal) の二種類ある.
デノーマルは指数が最小値のもので,正規化数の最小値と 0 の間のギャップを埋める働きをもつ.
アンノーマルは,デノーマルを除いた,一般的な非正規化数である.
(注) implicit bit を使う場合,非正規化数は表現できません. また,正規化数に対しては小数点は仮数の先頭ビットの次にあるものと考えます.
ゼロ (zero)
浮動小数点数は「仮数×基数の指数乗」で表現されるが,0 に何を掛けても結果は 0 であるから,仮数が 0 のデータはすべて 0 の値を表わす.
これらの 0 のうち,指数が 0 でないものをきたない 0 という.
きれいな 0 は指数も 0 であり,ビット・パターンがすべて 0 である.
符号はどちらでもよく,したがって,正の 0 と負の 0 がある.
(注) 基数が 2 の場合に implicit bit を使うと,仮数 p の値域は 1 <= p < 2 であり 0 にはなれません. この場合,0 はきれいな 0 で表わします.
無限大 (infinite)
0, 非数などと同様の,IEEE 規格における特殊数の一つで,オーバーフローや 0 除算が発生し,ユーザでトラップしないとき返される.
無限大には符合があるが,正負を同一のものとして扱う射影モード (projection mode) と,通常の数直線のように並ぶアフィン・モード (affine mode) の二つが用意されている.
i386 + gcc では,無限大は指数部の bit がすべて 1 であり,仮数部の bit がすべて 0 である.
符号はどちらでもよく,したがって,正の無限大と負の無限大がある.
非数 (Not-A-Number : NaN)
数値が存在しないことを示すビット・パターン.
マシンによっては予約数,不定数などともいう.
これらと通常の数の大小は比較できないから,比較演算に非数が使われると比較不能 (unordered) になる.
また,これに対する演算は無効 (invalid) の例外を発生する.
指数部の bit がすべて 1 であり,仮数部の bit が一つでも 1 であるもの.
(すべてが 0 であれば,無限大になる.)
演算の中で NaN があれば,結果は NaN になる.
演算に伴って誤差がなぜ,どのように発生するかに関しては文献 [4], [5] に解説があります.
誤差 (error)
浮動小数点演算で発生する誤差は 3 種類ある.
(1) 被演算項の値が正しく,演算も正確だとしても,演算結果は,仮数が元と同じビット数のフォーマットに丸める必要がある.
この段階で生ずるのが丸め誤差である.
IEEE 規格では,丸めで結果不正確 (inexact result) の例外が発生する.
(2) 二つのデータの絶対値が大きく異なる加減算では,桁そろえによって,小さい方の数の下位何桁かが失われる.
これを情報落ちという.
IEEE 規格では,この誤差は許されない.
(3) 減算で 2 数が近い場合は,上位何桁かが失われる.
これが桁落ち (cancelling) である.
アンダーフロー (underflow)
日本語では「下位桁あふれ」.
演算結果が,正規化した浮動小数点数で表現できる最小値を下回った状況をいう.
これまでは,メモリーに格納する形式には非正規化数が許されないため,アンダーフローが起きたとたんに 0 にしてしまう 0 フラッシュ処置がとられた.
IEEE 規格では,この場合も指数が最小の非正規化数 (デノーマル : denormal) を許し,絶対値が最小の正規化数と 0 との間を等間隔の分解能で表現できるようにしている.
(段階的アンダーフロー)
オーバーフロー (overflow)
桁あふれ.
演算結果が大きくなりすぎて,浮動小数点数の最大値でも表現できない場合をいう.
精度 (precision)
有効数字の桁数.
仮数に割り当てられたビット数に比例する.
なお,ガード・ビット (guard bits) とよばれる余分なビットを浮動小数点アキュムレーターの LSB のさらに下位に設けると,アキュムレーターのビット幅が,演算中は仮数より長くなり,誤差の軽減に役立つ.
丸め (rounding)
演算結果の有効桁数が所定の仮数部より長くなって,二つの浮動小数の間に入った場合,値をどちらかにする操作.
方法として切り上げ,切り捨て,四捨五入の三つが考えられるが,丸めは通常は四捨五入を指す.
例外 (exception)
被演算項または演算結果が異常で,通常の演算操作が続けられない状態.
関数 (function)
関数の計算法は,大きく二つに分かれる.
第一は「二つの変数があり,一方が特定の値に接近するにつれて,他方が目的の関数値に収束するような関数関係を利用する」方法である.
これの代表例は CORDIC (coordinate rotation digital computation) で,加減算とシフトだけで実現できるため,三角関数や対数の計算が乗除算と同程度の速度でできる長所をもつ.
第二が Taylor,ミニマックス,Chebyshev などによる多項式近似である.
この方法は,計算が簡単なことや,多項式に対する演算 (加減算,微積分) の結果もやはり多項式になる,といった理由でよく使われる.
(注) 無責任に引用して済みませんが,私はこの第一の場合に出会ったことがなく,上記の説明は理解できません. 算術関数に関しては,次節で私なりの知見を述べます.
参考文献
[1]「浮動小数点演算の基本用語」小林 史典,インターフェース (1985.3) pp.329-330 (CQ 出版社)
ちなみにこの号の特集は「浮動小数点演算と 8087 の活用」です.
[2]「8087 と C 言語」高橋 嘉光,bit, Vol.18, No.6, p.91 (共立出版) (1986)
[3]「C 言語とはどんな言語?」大貫 広幸,別冊トランジスタ技術 ソフトマインド C on the PC98 (CQ 出版社) (1985)
この記事は pp.7-126 に渡りますが,ここでの話題に関係するのは p.13 の「図2 データの各型別の内部型式」です.
[4]「数値計算の常識」伊理 正夫,藤野 和建 (共立出版) (1985)
[5]「パソコン数値計算における誤差と対策」春海 佳三郎,インターフェース (1985.3) pp.314-328 (CQ 出版社)
浮動小数点数の四則演算の結果は,(最下位の 1 bit の問題は別として) 表現能力一杯の精度で得られます. 一方,算術関数は一般には表現能力一杯の精度で数値を返しません. 使用する算術関数の精度は,その計算法や引数の範囲も含めて把握しておくべきでしょう. (これは精度は必ず良くなければならないという話ではなく,目的とのバランスの問題です.)
計算法としては,反復計算,多項式,級数,補間などがあります. どれが使われるかは,対象となる関数や library によって異なります. また,引数の値に応じて計算法を切り換えている場合もあります.
なお,多項式や補間を実現し,またその誤差を明示するためには,その関数の高精度の数値表が必要になります. 想像ですが,qlib は元々はそれを求めるために作られたように思います.
以下の参考文献は主に多項式を使って算術関数を実現しています. libm, ldouble, qlib の source も参考になるでしょう.
参考文献
[1]「4 倍精度の数学関数 (1),(2)」Tim Prince, C Magazine (1996.7,8) (ソフトバンク)
以下の文献に,高精度な浮動小数点数の表現と,その演算をソフトウェアでエミュレートする方法が c の source 付きで解説されています. この記事の目的はおそらく基本概念の説明,すなわち学習用なので,配列の使い方がもったいない,機能に制約が多いなど,実用には適しません.
学習にはこれを,実用には出来合の library をと使い分けるのが合理的でしょう.
参考文献
[1]「C 言語による高精度計算」Alan Filipski, ASCII (1986.6) pp.205-208 (アスキー)
なお,ASCII (1986.9) p.234 の「AFTER CARE」にこの正誤表があります.