Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1265] ファイルの概要

このコミットは、Goコンパイラ(gc)における浮動小数点定数の取り扱い、特にfloat64またはfloat32のコンテキストで使用される際の丸め(truncating)に関する修正を導入しています。Go言語のコンパイラが、ソースコード中の浮動小数点リテラルを内部表現から実際のfloat32またはfloat64型に変換する際に、IEEE 754標準に準拠した正確な丸めが行われるように改善されています。

コミット

commit 89996e1fb1b3834746340570c2d1b48ef9fc1b9b
Author: Ken Thompson <ken@golang.org>
Date:   Tue Dec 2 17:03:47 2008 -0800

    truncating of float constants when
    used in float64 or float32 contexts
    
    R=r
    OCL=20297
    CL=20297

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/89996e1fb1b3834746340570c2d1b48ef9fc1b9b

元コミット内容

浮動小数点定数がfloat64またはfloat32コンテキストで使用される際の丸め処理。

変更の背景

Go言語のコンパイラは、ソースコード中の数値リテラルを内部的に高精度な多倍長演算(multi-precision arithmetic)で扱います。これにより、コンパイル時の計算精度を最大限に保ち、最終的な型への変換時にのみ精度を落とすことができます。しかし、このコミット以前は、高精度で表現された浮動小数点リテラルが、最終的にfloat32(単精度)やfloat64(倍精度)といった固定精度型に変換される際に、IEEE 754浮動小数点標準に準拠した適切な丸め処理が行われていなかった可能性があります。

特に、float32float64はそれぞれ24ビット(隠れビットを含め23ビットの仮数部)と53ビット(隠れビットを含め52ビットの仮数部)の精度しか持たないため、高精度なリテラルをこれらの型に代入する際には、余分な精度を適切に切り捨てる(丸める)必要があります。この丸めが不適切だと、コンパイル時と実行時で浮動小数点値の挙動が異なったり、期待される精度が得られなかったりする問題が発生します。

このコミットは、このような浮動小数点リテラルの型変換における精度管理と丸め処理の正確性を向上させることを目的としています。

前提知識の解説

Goコンパイラ (gc)

Go言語の公式コンパイラはgcと呼ばれ、Goのソースコードを機械語に変換する役割を担っています。gcは、型チェック、最適化、コード生成など、コンパイルの様々な段階で動作します。このコミットで変更されているsrc/cmd/gc/const.csrc/cmd/gc/mparith3.cは、gcの内部で定数処理や多倍長演算を司る部分です。

浮動小数点数 (IEEE 754)

現代のコンピュータにおける浮動小数点数の標準はIEEE 754です。Go言語のfloat32float64もこの標準に準拠しています。

  • float32 (単精度): 32ビットで表現され、約7桁の10進精度を持ちます。仮数部(significand)は23ビット(隠れビットを含め24ビット)、指数部は8ビットです。
  • float64 (倍精度): 64ビットで表現され、約15-17桁の10進精度を持ちます。仮数部は52ビット(隠れビットを含め53ビット)、指数部は11ビットです。

IEEE 754では、数値が表現可能な範囲を超えたり、精度が足りない場合に丸め(rounding)が必要になります。標準で推奨される丸めモードの一つに「最近接偶数への丸め(round half to even)」があります。これは、ちょうど中間にある値(例: 2.5, 3.5)を丸める際に、結果が偶数になる方へ丸めるというルールです。

多倍長演算 (Multi-precision Arithmetic)

多倍長演算とは、コンピュータのネイティブなワードサイズ(例: 64ビット)よりも大きな精度で数値を扱うための技術です。Goコンパイラは、ソースコード中の数値リテラルを解析する際に、この多倍長演算ライブラリ(mpプレフィックスの関数群)を使用して、可能な限り高い精度で中間値を保持します。これにより、コンパイル時の計算誤差を最小限に抑え、最終的な型への変換時にのみ必要な精度に丸めることができます。

コンパイラの定数処理

コンパイラは、ソースコード中の定数(リテラル)を特別な方法で扱います。例えば、const x = 0.1 + 0.2のような式は、コンパイル時に計算され、その結果が定数として埋め込まれます。この際、中間計算は高精度で行われ、最終的な定数の型(例: float64)に合わせて丸められます。

技術的詳細

このコミットの主要な変更点は、src/cmd/gc/const.ctruncfltlitという新しい関数が導入され、既存のconvlit関数内でこのtruncfltlitが呼び出されるようになったことです。また、src/cmd/gc/mparith3.c内のmpgetflt関数も、多倍長浮動小数点数をdouble型に変換する際の丸めロジックが改善されています。

truncfltlit関数の役割

truncfltlit関数は、多倍長浮動小数点リテラル(Mpflt *fv)を、指定された型(Type *tfloat32またはfloat64)の精度に丸める役割を担います。

  • TFLOAT64の場合:

    • d = mpgetflt(fv); で多倍長浮動小数点数fvをC言語のdouble型に変換します。
    • mpmovecflt(fv, d); でそのdouble値を再び多倍長浮動小数点数fvに戻します。この往復変換により、doubleの精度(IEEE 754倍精度)に丸められます。
  • TFLOAT32の場合:

    • d = mpgetflt(fv);fvdoubleに変換します。
    • f = d;doubleをC言語のfloat型にキャストします。これにより、floatの精度(IEEE 754単精度)に丸められます。
    • d = f;floatを再びdoubleにキャストします。これは、mpmovecfltdoubleを引数に取るための中間ステップです。
    • mpmovecflt(fv, d); でそのdouble値をfvに戻します。この一連の操作により、float32の精度に丸められます。

この関数は、コンパイラが内部的に使用する多倍長浮動小数点表現と、ターゲットとなるGoの浮動小数点型(float32/float64)との間の精度ギャップを埋めるための重要なブリッジとなります。

mpgetflt関数の改善

mpgetflt関数は、多倍長浮動小数点数(Mpflt)をC言語のdouble型に変換する際に使用されます。このコミットでは、特に以下の点が改善されています。

  1. 正規化ループの条件変更: while((a->val.a[Mpnorm-1] & (1L<<(Mpscale-1))) == 0) から while((a->val.a[Mpnorm-1] & Mpsign) == 0) へ変更されました。これは、多倍長数の最上位ビット(符号ビット)をチェックして正規化を行うロジックの修正であり、より正確な正規化を保証します。

  2. IEEE 754固有の丸め処理の明示と修正: 以前のコードには、// should do this in multi precisionというコメントがあり、多倍長精度で丸めを行うべきという意図が示唆されていました。今回の変更では、mpgetflt内でdoubleへの変換時にIEEE 754の「最近接偶数への丸め(round half to even)」に近いロジックが導入されています。

    • if((v&0x3ffULL) != 0x200ULL || (v&0x400) != 0) の条件で、v += 0x200ULL;(丸め)が行われます。これは、下位10ビット(0x3ffULL)と次のビット(0x400)を考慮して、丸めが必要かどうかを判断し、必要であれば0x200ULL(2の9乗、つまり10ビット目の位置)を加算して丸めを行います。コメントが// round toward evenに変更されており、IEEE 754の標準的な丸めモードに近づける意図が明確になっています。
    • v &= ~0x3ffULL;v >>= 10; に変更されました。これは、下位10ビットをマスクでクリアする代わりに、単純に右に10ビットシフトすることで、不要な精度を切り捨てる操作です。
    • f = ldexp(f, Mpnorm*Mpscale + a->exp - 63);f = ldexp(f, Mpnorm*Mpscale + a->exp - 53); に変更されました。53doubleの仮数部のビット数(隠れビットを含む)であり、この変更により、指数部の調整がdoubleの正確な表現に合致するようになりました。以前の63は誤りであったか、異なる中間表現を想定していた可能性があります。

これらの変更により、Goコンパイラが浮動小数点リテラルを処理する際の精度管理と丸め処理が、よりIEEE 754標準に準拠し、予測可能な挙動を示すようになりました。

コアとなるコードの変更箇所

src/cmd/gc/const.c

  1. truncfltlit関数の追加:

    void
    truncfltlit(Mpflt *fv, Type *t)
    {
        double d;
        float f;
    
        if(t == T)
            return;
    
        // convert large precision literal floating
        // into limited precision (float64 or float32)
        // botch -- this assumes that compiler fp
        //    has same precision as runtime fp
        switch(t->etype) {
        case TFLOAT64:
            d = mpgetflt(fv);
            mpmovecflt(fv, d);
            break;
    
        case TFLOAT32:
            d = mpgetflt(fv);
            f = d;
            d = f;
            mpmovecflt(fv, d);
            break;
        }
    }
    
  2. convlit関数内でのtruncfltlitの呼び出し追加:

    • 整数リテラルから浮動小数点リテラルへの変換後:
      // ...
      n->val.u.fval = fv; // fv is newly allocated Mpflt
      mpmovefixflt(fv, xv);
      n->val.ctype = CTFLT;
      truncfltlit(fv, t); // ADDED
      // ...
      
    • 浮動小数点リテラルから別の浮動小数点型への変換後:
      // ...
      // Old commented out block for truncation removed
      // replaced by:
      truncfltlit(fv, t); // ADDED
      // ...
      
  3. convlit1関数(retラベル付近)でのtruncfltlitの呼び出し追加:

    // ...
    } else
    if(wl == Wlitfloat) {
        n->val.u.fval = fval;
        truncfltlit(fval, n->type); // ADDED
    }
    // ...
    

src/cmd/gc/mparith3.c

  1. mpgetflt関数内の正規化ループ条件の変更:

    // Old: while((a->val.a[Mpnorm-1] & (1L<<(Mpscale-1))) == 0) {
    // New:
    while((a->val.a[Mpnorm-1] & Mpsign) == 0) { // MODIFIED
        mpshiftfix(&a->val, 1);
        a->exp -= 1;
    }
    
  2. mpgetflt関数内の丸めロジックと指数調整の変更:

    // ...
    // New comment:
    // the magic numbers (64, 63, 53, 10) are
    // IEEE specific. this should be done machine
    // independently or in the 6g half of the compiler
    
    // ...
    // Old: if((v&0x3ffULL) != 0x200ULL || (v&0x400) != 0)
    // Old:   v += 0x200ULL;        // round
    // Old: v &= ~0x3ffULL;
    // New:
    if((v&0x3ffULL) != 0x200ULL || (v&0x400) != 0)
        v += 0x200ULL;        // round toward even // MODIFIED COMMENT
    v >>= 10; // MODIFIED
    f = (double)(v);
    // Old: f = ldexp(f, Mpnorm*Mpscale + a->exp - 63);
    // New:
    f = ldexp(f, Mpnorm*Mpscale + a->exp - 53); // MODIFIED
    // ...
    

コアとなるコードの解説

このコミットの核心は、Goコンパイラが浮動小数点リテラルを扱う際の精度管理を、より厳密にIEEE 754標準に準拠させる点にあります。

src/cmd/gc/const.cに追加されたtruncfltlit関数は、コンパイラ内部で高精度に保持されている浮動小数点リテラル(Mpflt型)を、最終的にGoのfloat32またはfloat64型に変換する際の「丸めゲートウェイ」として機能します。この関数は、C言語のネイティブなdouble型やfloat型へのキャストを利用することで、コンパイラが動作するシステム(通常はIEEE 754準拠)の浮動小数点演算ユニットに丸め処理を委ねています。これにより、Goコンパイラ自身が複雑な丸めロジックを実装する代わりに、既存の信頼性の高いハードウェア/ソフトウェアの浮動小数点処理を利用できるという利点があります。

src/cmd/gc/mparith3.cmpgetflt関数への変更は、多倍長浮動小数点数からCのdouble型への変換自体を改善するものです。特に、仮数部のビットシフト(v >>= 10;)と指数部の調整(- 53)は、double型が持つ53ビットの仮数部(隠れビットを含む)に正確に合わせるためのものです。また、「最近接偶数への丸め」を意図したロジックの修正は、IEEE 754標準で推奨される丸めモードに準拠することで、浮動小数点演算の予測可能性と正確性を高めます。

これらの変更が組み合わさることで、Go言語のソースコードに記述された浮動小数点リテラルは、コンパイル時に適切な精度で丸められ、実行時の挙動がより一貫性を持つようになります。これは、数値計算の正確性が求められるアプリケーションにおいて非常に重要です。

関連リンク

参考にした情報源リンク