誤差を含む浮動小数点数を比較することについて

投稿者: Anonymous
double one() {
    volatile double result = 0.0;
    for (int i=0; i<10; ++i) {
        result += 0.1;
    }
    return result;
}

int main(void)
{
    double a = one();
    assert(a==1.0);
    return 0;
}

これが通らない(可能性がある)というのは有名な話ですが、

int main(void)
{
    double a = one();
    double b = a;
    double c = one();

    assert(a==a); // 1.同じ変数同士の比較
    assert(a==b); // 2.代入結果との比較
    assert(a==c); // 3.同じ方法での生成結果同士の比較

    return 0;
}

これらが通ることは言語レベルやコンパイラレベルで保証されているのでしょうか。
サンプルはCで書いていますが、様々な言語でどうなっているのか知りたいです。特にC,C++,C#,Python,JavaScript,Rustに興味があります。

解決

現在普通に手に入るコンピュータでは、組み込み用マイコンからスーパーコンピューターまでのほぼ全てで IEEE754 (ISO/IEC/IEEE 60559) を採用しているので、よほど古いないしは特殊な機械を除き同じ処理をさせれば同じ結果を得ます。「キャッシュ」は文字通り「隠されている代物」なので、あってもなくても同じ結果が出なければなりません。キャッシュの有無によって結果が異なるのであれば、それは「隠されていない」のでキャッシュと呼んではいけないっす。

IEEE754 で内部表現できる数値は正確ですので、それら同士の演算結果は正確で、つまり必ず同じ結果が出ます。丸めモードが違うと結果が異なることはありますが、それは丸めモードを指定していない(機器固有の初期状態に依存している)プログラムの不備です。

同じ演算を行うプログラムが同じ結果を返さないのであればコンピュータとは呼べません(再現実験できないことになる) Pentium FDIV Bug なんかも再現パターンを踏ませればきっちり再現します。

  • 浮動小数点数演算自体にて誤差は発生しない
  • 誤差が発生するのは入出力の際に10進数表記と2進数表記を変換するとき

コンピュータにとって与えられた値(の下位ビット)に意味があるかないかはどうでもよくて、コンピュータは単に演算するだけです。で、どんな演算であれ(バグがない限り)同じ入力に対しては必ず同じ結果が得られます。下位ビットに意味があるか無いかを決めるのは人間。「誤差」なるものを意味付けするのも人間です。逆に言えば、その辺の事情を理解せずに「誤差」を論じても無意味っす。

10進数表記文字列を浮動小数点数にする、あるいはその逆を行う関数が、その内部実装が違うことによって精度が違う可能性はあります。なので入力から出力まで二進数で行い精度落ちを排除したなら以下略。 CPU の気分次第で結果が違うなんてことはありません。


丸めモードを変更する関数を自分で呼び出すということは、丸めモードを意識的に変更したわけで、結果は違って当然(違うように変えたのだから)。今どきの OS では、この丸めモードはプロセスやスレッドごとに保持できるようになっているので、他プロセスやスレッドが変更しても自分の丸めモードが勝手に変わることはありません。

回答者: Anonymous

Leave a Reply

Your email address will not be published.