仮数部の最小位ビットでの最近値丸め(Round to Nearest)を実現するために,仮数部の下に,ガード ビット(Guard bit),ラウンド ビット(Round Bit),スティッキービット(Sticky Bit)の3つのビットが用意されている.これら3つのビットの値はFPUが演算中に自動でセットするもので,ユーザーが値をとりだして演算に利用することはできない(できなくなっているのが普通).
ガード ビットは仮数部 最小位ビットの 1/2 の桁の値,ラウンド ビットは 1/4 の桁の値を保持し,スティッキービットは演算中に 1/8 以下のビットがひとつでも1になる状況が発生した場合に 1 に固定されるようになっている.このように仮数部 最小位ビット以下の情報を保持することで,最小位ビットまでの正確な最近値丸めが実現できるようになっている.
ガード ビット,ラウンド ビット,スティッキービットについては,PowerPC や MIPS などの RISC 系 CPU のマニュアル・解説書には明確な記載があるが,Intel x86 系では記載がない場合がある.ただ,同様の実装がおこなわれているようで,次のような,精度落ちをさせるサンプルプログラムを使うことでスティッキービットが存在していることが確認できる(最小位ビット以下の情報を単純に切り捨てているわけではないことがわかる).
#include <stdio.h>
#include <fenv.h>
int main(void)
{
fesetround(FE_TOWARDZERO);
double f1 = 1.0e20;
double f2 = 1.0;
double f3 = 0.0;
long long int* p;
p = (long long int*)&f1;
printf("f1: %30f (%llx)\n", f1, *p);
f3 = f1 - f2;
p = (long long int*)&f3;
printf("f3: %30f (%llx)\n", f3, *p);
return 0;
}
結果
f1: 100000000000000000000.000000 (4415af1d78b58c40)
f3: 99999999999999983616.000000 (4415af1d78b58c3f)
解説
非常に大きな数(f1 = 1.0×10^20)から小さな数(f2 = 1.0)を引くことで,減算前の桁合わせを行う際に減数(f2)側の仮数部の精度がたりなくなり,仮数部が 0 になる.スティッキービットがない場合,減数側は単に 0 とみなされて減算をしない場合と同じ結果になるはずだが,スティッキービットがあると,桁合わせ処理中に仮数部が 0 になったあともスティッキービットに 1 が残るため,減算で被減数(f1)側 仮数部を“取り崩す”ことができる.
なお,減算後に“最近値丸め”処理で再度 1.0 にもどってしまうのを防ぐため,fesetround(FE_TOWARDZERO)
を使って丸め処理を“切り捨て”に変更している.
サンプル プログラムのダウンロード