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

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

このコミットは、Go言語のmath/bigパッケージにRat.Float32メソッドを実装するものです。math/bigパッケージは、任意精度の数値演算を可能にするためのもので、この変更により、任意精度の有理数(big.Rat型)をIEEE 754単精度浮動小数点数(float32型)に変換する機能が追加されます。これは、big.Ratで表現された数値を、より一般的な浮動小数点形式で利用できるようにするための重要な機能拡張です。

コミット

commit be91bc29a43ae582b6ca7f6adf561cfb25bd6911
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Jun 11 09:10:49 2014 -0700

    math/big: implement Rat.Float32
    
    Pending CL 101750048.
    For submission after the 1.3 release.
    
    Fixes #8065.
    
    LGTM=adonovan
    R=adonovan
    CC=golang-codereviews
    https://golang.org/cl/93550043

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

https://github.com/golang/go/commit/be91bc29a43ae582b6ca7f6adf561cfb25bd6911

元コミット内容

このコミットは、Go言語の標準ライブラリであるmath/bigパッケージに、Rat型からfloat32型への変換メソッドFloat32を追加することを目的としています。コミットメッセージには、この変更がCL 101750048という保留中の変更リストに関連しており、Go 1.3リリース後に提出される予定であることが示されています。また、Fixes #8065という記述がありますが、このIssueに関する公開情報は確認できませんでした。

変更の背景

Go言語のmath/bigパッケージは、標準のintfloat64型では扱いきれない非常に大きな数値や高精度な計算を必要とする場面で利用されます。これまでのbig.Rat型は、有理数を任意精度で表現できましたが、それを標準のfloat32型に変換する直接的なメソッドが存在しませんでした。

float32は、グラフィックス処理や機械学習など、精度よりもパフォーマンスやメモリ効率が重視される多くのアプリケーションで広く使用されています。big.Ratで計算された高精度な結果を、これらのアプリケーションで利用するためには、float32への効率的かつ正確な変換機能が不可欠でした。

このコミットは、big.Ratで表現された有理数をIEEE 754標準に準拠したfloat32形式に変換する機能を提供することで、math/bigパッケージの汎用性と実用性を向上させることを目的としています。特に、浮動小数点数の変換において重要な「最近接偶数への丸め(round-half-to-even)」規則を正確に実装することが求められました。

前提知識の解説

math/bigパッケージ

math/bigパッケージは、Go言語で任意精度の数値演算を可能にするためのパッケージです。Goの組み込み型(int64float64など)には表現できる数値の範囲に限界がありますが、math/bigパッケージを使用することで、その限界を超える非常に大きな整数、有理数、浮動小数点数を扱うことができます。

  • big.Int: 任意精度の符号付き整数を扱います。
  • big.Rat: 任意精度の有理数(分数)を扱います。分子と分母をbig.Intで保持します。
  • big.Float: 任意精度の浮動小数点数を扱います。

これらの型は、通常の算術演算子(+, -, *, /など)ではなく、専用のメソッド(Add, Sub, Mul, Divなど)を使用して演算を行います。

IEEE 754浮動小数点数表現(float32float64

IEEE 754は、浮動小数点数のコンピュータ上での表現方法と演算方法を標準化したものです。Go言語のfloat32float64は、それぞれIEEE 754の単精度(binary32)と倍精度(binary64)浮動小数点数に準拠しています。

  • float32 (単精度): 32ビットで構成されます。

    • 符号部 (1ビット): 数値の符号(0: 正、1: 負)を表します。
    • 指数部 (8ビット): 数値の大きさを表します。バイアス(127)が加えられた形で格納されます。
    • 仮数部 (23ビット): 数値の精度を表します。正規化された数では、先頭に暗黙の「1」が仮定されるため、実質24ビットの精度を持ちます。
  • float64 (倍精度): 64ビットで構成されます。

    • 符号部 (1ビット)
    • 指数部 (11ビット): バイアス(1023)が加えられた形で格納されます。
    • 仮数部 (52ビット): 先頭に暗黙の「1」が仮定されるため、実質53ビットの精度を持ちます。

最近接偶数への丸め(Round Half To Even)

浮動小数点数演算において、正確に表現できない数値を丸める際に使用される規則の一つです。IEEE 754標準のデフォルトの丸めモードとして採用されています。

  • 規則: ちょうど中間点にある数値(例: 2.5, 3.5)を丸める場合、最も近い偶数に丸めます。
    • 例: 2.5 は 2 に丸められます(2は偶数)。
    • 例: 3.5 は 4 に丸められます(4は偶数)。
    • 例: -2.5 は -2 に丸められます。
    • 例: -3.5 は -4 に丸められます。

この丸め方法は、単純な四捨五入(常に切り上げ)と比較して、多数の計算を行った際に発生する累積誤差の偏りを最小限に抑える効果があります。

技術的詳細

このコミットの核心は、math/big/rat.goに追加されたquotToFloat32関数と、Rat.Float32メソッドです。

quotToFloat32関数

quotToFloat32は、非負の有理数 a/b を最も近いfloat32値に変換する内部関数です。quotToFloat64(旧quotToFloat)と同様のロジックで、float32の特性に合わせて定数(Fsize, Msize, Esize, Ebiasなど)が調整されています。

この関数の主要なステップは以下の通りです。

  1. 正規化とシフト:

    • abのビット長を計算し、商a/bfloat32の仮数部(Msize)の範囲に収まるように、aまたはbを左シフトします。これにより、浮動小数点数の正規化された形式に近づけます。
    • Msize1は仮数部のビット幅に暗黙の1ビットを加えたもの(24ビット)、Msize2はさらに1ビット加えたもの(25ビット)で、丸め処理のために余分な精度を保持します。
  2. 商と剰余の計算:

    • シフトされたa2b2を用いて、a2b2で除算し、商qと剰余rを計算します。
    • mantissaには商qの最下位32ビットが格納されます。haveRemは剰余が存在するかどうかを示します。
  3. 仮数部の調整:

    • 計算されたmantissaMsize2ビットに収まらない場合(つまり、mantissa1<<Msize2以上の場合)、mantissaを右シフトし、指数expをインクリメントして調整します。これは、正規化された浮動小数点数の形式に合わせるための処理です。
    • mantissaMsize1ビットに収まらない場合はパニックを発生させます。
  4. 丸め処理:

    • 非正規化数(Denormalized numbers)の処理: 指数expが最小指数Eminの範囲内にある場合、非正規化数として扱われます。この場合、精度が失われるため、mantissaを右シフトし、haveRemを更新します。
    • 最近接偶数への丸め:
      • mantissaの最下位ビットが1の場合、exactfalseに設定します。
      • mantissaの最下位ビットが1で、かつ剰余がある場合、またはmantissaの2番目のビットが1の場合(つまり、ちょうど中間点にある場合)、mantissaをインクリメントします。
      • インクリメントの結果、mantissa1<<Msize2以上になった場合(桁上がり)、mantissaを右シフトし、指数expをインクリメントします。
      • 最後に、丸め処理に使用したビットを破棄するためにmantissaを1ビット右シフトします。
  5. float32値の生成:

    • math.Ldexp関数を使用して、調整されたmantissaexpから最終的なfloat32値を生成します。
    • 結果が無限大(Inf)になる場合、exactfalseに設定します。

Rat.Float32メソッド

Rat.Float32メソッドは、big.Rat型のレシーバxに対して呼び出され、その有理数をfloat32値に変換します。

  1. 分母の処理: xの分母x.b.absがゼロの場合、natOne(値1を表す内部定数)を設定して分母を実体化します。これは、ゼロ除算を避けるための措置です。
  2. quotToFloat32の呼び出し: xの分子の絶対値x.a.absと、調整された分母bを引数としてquotToFloat32を呼び出し、float32fexactフラグを取得します。
  3. 符号の適用: xの分子x.aが負の場合、fの符号を反転させます。これにより、結果のfloat32値が元の有理数の符号と一致するようにします。
  4. 結果の返却: 最終的なfloat32fと、変換が正確であったかを示すexactフラグを返します。

テストの追加

このコミットでは、src/pkg/math/big/rat_test.goTestFloat32SpecialCasesTestFloat32Distributionという新しいテスト関数が追加されています。

  • TestFloat32SpecialCases: float64inputsという既存のテストデータセットを利用して、Rat.Float32strconv.ParseFloatと一貫した結果を返すか、最も近い近似値であるか、そして非損失的なラウンドトリップ(float32 -> Rat -> float32)が可能であるかを検証します。
  • TestFloat32Distribution: float32の範囲を超える広範な(符号、仮数、指数)の組み合わせを生成し、Rat.Float32が常に最も近いfloat32近似値を選択するかを検証します。これにより、様々な数値に対する変換の正確性を保証します。

また、既存のcheckNonLossyRoundtripcheckIsBestApprox関数が、float32float64の両方に対応するようにcheckNonLossyRoundtrip32/64checkIsBestApprox32/64に分割・汎用化されています。

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

src/pkg/math/big/int.go

 // low32 returns the least significant 32 bits of z.
 func low32(z nat) uint32 {
 	if len(z) == 0 {
 		return 0
 	}
 	return uint32(z[0])
 }

 // low64 returns the least significant 64 bits of z.
 func low64(z nat) uint64 {
 	if len(z) == 0 {
 		return 0
 	}
 	v := uint64(z[0])
 	if _W == 32 && len(z) > 1 {
 		v |= uint64(z[1]) << 32
 	}
 	return v
 }

 // Int64 returns the int64 representation of x.
 // If x cannot be represented in an int64, the result is undefined.
 func (x *Int) Int64() int64 {
-	v := int64(x.Uint64())
+	v := int64(low64(x.abs))
 	if x.neg {
 		v = -v
 	}
 	return v
 }

 // Uint64 returns the uint64 representation of x.
 // If x cannot be represented in a uint64, the result is undefined.
 func (x *Int) Uint64() uint64 {
-	if len(x.abs) == 0 {
-		return 0
-	}
-	v := uint64(x.abs[0])
-	if _W == 32 && len(x.abs) > 1 {
-		v |= uint64(x.abs[1]) << 32
-	}
-	return v
+	return low64(x.abs)
 }

src/pkg/math/big/rat.go

 // quotToFloat32 returns the non-negative float32 value
 // nearest to the quotient a/b, using round-to-even in
 // halfway cases.  It does not mutate its arguments.
 // Preconditions: b is non-zero; a and b have no common factors.
 func quotToFloat32(a, b nat) (f float32, exact bool) {
 	const (
 		// float size in bits
 		Fsize = 32

 		// mantissa
 		Msize  = 23
 		Msize1 = Msize + 1 // incl. implicit 1
 		Msize2 = Msize1 + 1

 		// exponent
 		Esize = Fsize - Msize1
 		Ebias = 1<<(Esize-1) - 1
 		Emin  = 1 - Ebias
 		Emax  = Ebias
 	)

 	// TODO(adonovan): specialize common degenerate cases: 1.0, integers.
 	alen := a.bitLen()
 	if alen == 0 {
 		return 0, true
 	}
 	blen := b.bitLen()
 	if blen == 0 {
 		panic("division by zero")
 	}

 	// 1. Left-shift A or B such that quotient A/B is in [1<<Msize1, 1<<(Msize2+1)
 	// (Msize2 bits if A < B when they are left-aligned, Msize2+1 bits if A >= B).
 	// This is 2 or 3 more than the float32 mantissa field width of Msize:
 	// - the optional extra bit is shifted away in step 3 below.
 	// - the high-order 1 is omitted in "normal" representation;
 	// - the low-order 1 will be used during rounding then discarded.
 	exp := alen - blen
 	var a2, b2 nat
 	a2 = a2.set(a)
 	b2 = b2.set(b)
 	if shift := Msize2 - exp; shift > 0 {
 		a2 = a2.shl(a2, uint(shift))
 	} else if shift < 0 {
 		b2 = b2.shl(b2, uint(-shift))
 	}

 	// 2. Compute quotient and remainder (q, r).  NB: due to the
 	// extra shift, the low-order bit of q is logically the
 	// high-order bit of r.
 	var q nat
 	q, r := q.div(a2, a2, b2) // (recycle a2)
 	mantissa := low32(q)
 	haveRem := len(r) > 0 // mantissa&1 && !haveRem => remainder is exactly half

 	// 3. If quotient didn't fit in Msize2 bits, redo division by b2<<1
 	// (in effect---we accomplish this incrementally).
 	if mantissa>>Msize2 == 1 {
 		if mantissa&1 == 1 {
 			haveRem = true
 		}
 		mantissa >>= 1
 		exp++
 	}
 	if mantissa>>Msize1 != 1 {
 		panic(fmt.Sprintf("expected exactly %d bits of result", Msize2))
 	}

 	// 4. Rounding.
 	if Emin-Msize <= exp && exp <= Emin {
 		// Denormal case; lose 'shift' bits of precision.
 		shift := uint(Emin - (exp - 1)) // [1..Esize1)
 		lostbits := mantissa & (1<<shift - 1)
 		haveRem = haveRem || lostbits != 0
 		mantissa >>= shift
 		exp = 2 - Ebias // == exp + shift
 	}
 	// Round q using round-half-to-even.
 	exact = !haveRem
 	if mantissa&1 != 0 {
 		exact = false
 		if haveRem || mantissa&2 != 0 {
 			if mantissa++; mantissa >= 1<<Msize2 {
 				// Complete rollover 11...1 => 100...0, so shift is safe
 				mantissa >>= 1
 				exp++
 			}
 		}
 	}
 	mantissa >>= 1 // discard rounding bit.  Mantissa now scaled by 1<<Msize1.

 	f = float32(math.Ldexp(float64(mantissa), exp-Msize1))
 	if math.IsInf(float64(f), 0) {
 		exact = false
 	}
 	return
 }

 // quotToFloat64 returns the non-negative float64 value
 // nearest to the quotient a/b, using round-to-even in
 // halfway cases.  It does not mutate its arguments.
 // Preconditions: b is non-zero; a and b have no common factors.
 func quotToFloat64(a, b nat) (f float64, exact bool) {
 	const (
 		// float size in bits
 		Fsize = 64

 		// mantissa
 		Msize  = 52
 		Msize1 = Msize + 1 // incl. implicit 1
 		Msize2 = Msize1 + 1

 		// exponent
 		Esize = Fsize - Msize1
 		Ebias = 1<<(Esize-1) - 1
 		Emin  = 1 - Ebias
 		Emax  = Ebias
 	)

 	// ... (rest of quotToFloat64, similar to quotToFloat32 but with float64 constants)
 }

 // Float32 returns the nearest float32 value for x and a bool indicating
 // whether f represents x exactly. If the magnitude of x is too large to
 // be represented by a float32, f is an infinity and exact is false.
 // The sign of f always matches the sign of x, even if f == 0.
 func (x *Rat) Float32() (f float32, exact bool) {
 	b := x.b.abs
 	if len(b) == 0 {
 		b = b.set(natOne) // materialize denominator
 	}
 	f, exact = quotToFloat32(x.a.abs, b)
 	if x.a.neg {
 		f = -f
 	}
 	return
 }

 // Float64 returns the nearest float64 value for x and a bool indicating
 // whether f represents x exactly. If the magnitude of x is too large to
 // be represented by a float64, f is an infinity and exact is false.
 // The sign of f always matches the sign of x, even if f == 0.
 func (x *Rat) Float64() (f float64, exact bool) {
 	b := x.b.abs
 	if len(b) == 0 {
 		b = b.set(natOne) // materialize denominator
 	}
 	f, exact = quotToFloat64(x.a.abs, b)
 	if x.a.neg {
 		f = -f
 	}
 	return
 }

src/pkg/math/big/rat_test.go

 // isFinite reports whether f represents a finite rational value.
 // It is equivalent to !math.IsNan(f) && !math.IsInf(f, 0).
 func isFinite(f float64) bool {
 	return math.Abs(f) <= math.MaxFloat64
 }

 func TestFloat32SpecialCases(t *testing.T) {
 	for _, input := range float64inputs {
 		// ... (test logic for float32 special cases)
 	}
 }

 func TestFloat32Distribution(t *testing.T) {
 	// ... (test logic for float32 distribution)
 }

 // checkNonLossyRoundtrip32 checks that a float->Rat->float roundtrip is
 // non-lossy for finite f.
 func checkNonLossyRoundtrip32(t *testing.T, f float32) {
 	// ... (logic for float32 roundtrip check)
 }

 // checkNonLossyRoundtrip64 checks that a float->Rat->float roundtrip is
 // non-lossy for finite f.
 func checkNonLossyRoundtrip64(t *testing.T, f float64) {
 	// ... (logic for float64 roundtrip check)
 }

 // checkIsBestApprox32 checks that f is the best possible float32
 // approximation of r.
 // Returns true on success.
 func checkIsBestApprox32(t *testing.T, f float32, r *Rat) bool {
 	// ... (logic for float32 best approximation check)
 }

 // checkIsBestApprox64 checks that f is the best possible float64
 // approximation of r.
 // Returns true on success.
 func checkIsBestApprox64(t *testing.T, f float64, r *Rat) bool {
 	// ... (logic for float64 best approximation check)
 }

 func isEven32(f float32) bool { return math.Float32bits(f)&1 == 0 }
 func isEven64(f float64) bool { return math.Float64bits(f)&1 == 0 }

コアとなるコードの解説

src/pkg/math/big/int.goの変更

  • low32関数とlow64関数が新しく追加されました。これらは、nat型(math/bigパッケージ内部で数値を表現するために使われるスライス)の最下位32ビットまたは64ビットを取得するためのヘルパー関数です。
  • Int64()Uint64()メソッドの内部実装が変更され、新しく追加されたlow64関数を利用するように簡素化されました。これにより、コードの重複が減り、可読性が向上しています。

src/pkg/math/big/rat.goの変更

  • quotToFloat32関数の追加:
    • この関数は、big.Ratの分子と分母(nat型)を受け取り、それらの商をfloat32に変換します。
    • Fsize, Msize, Msize1, Msize2, Esize, Ebias, Emin, Emaxといった定数は、float32のIEEE 754表現の特性(ビット幅、バイアスなど)を正確に反映しています。
    • 変換ロジックは、有理数を浮動小数点数に変換する際の正規化、シフト、除算、そして最も重要な「最近接偶数への丸め」を厳密に実装しています。特に、mantissaの調整とhaveRemフラグの管理によって、丸め処理の精度を保証しています。
    • math.Ldexpは、仮数部と指数部から浮動小数点数を構築するために使用されます。
  • quotToFloat64関数の変更:
    • 既存のquotToFloat関数がquotToFloat64にリネームされ、float64のIEEE 754表現に対応する定数を使用するように変更されました。これにより、float32float64の変換ロジックが明確に分離され、それぞれの浮動小数点形式に特化した処理が可能になりました。
  • Rat.Float32メソッドの追加:
    • このメソッドは、big.Ratインスタンスに対して呼び出され、その値をfloat32に変換します。
    • 内部でquotToFloat32を呼び出し、計算されたfloat32値と、変換が正確であったかを示すブール値を返します。
    • 元のbig.Ratが負の値である場合、結果のfloat32値の符号を適切に反転させます。
  • Rat.Float64メソッドの変更:
    • 既存のRat.Float64メソッドが、新しくリネームされたquotToFloat64関数を呼び出すように変更されました。

src/pkg/math/big/rat_test.goの変更

  • TestFloat32SpecialCasesの追加:
    • float32変換における特殊なケース(例: ゼロ、非常に小さい数、非常に大きい数、境界値など)をテストします。
    • strconv.ParseFloatとの比較、最も近い近似値であることの確認、そしてfloat32からRatへの変換、そして再びfloat32への変換(ラウンドトリップ)が非損失的であることの検証を行います。
  • TestFloat32Distributionの追加:
    • float32の表現範囲全体にわたる広範な有理数に対して、Rat.Float32が常に最も近いfloat32近似値を選択するかを検証します。これは、ランダムに生成された数値の分布をテストすることで、変換ロジックの堅牢性を確認します。
  • ヘルパー関数の汎用化:
    • checkNonLossyRoundtripcheckIsBestApproxが、float32float64の両方に対応するように、それぞれcheckNonLossyRoundtrip32/64checkIsBestApprox32/64に分割されました。これにより、テストコードの再利用性が高まり、それぞれの浮動小数点形式に特化したテストが可能になりました。
    • isEven関数もisEven32isEven64に分割され、それぞれの浮動小数点形式のビット表現に基づいて偶数性をチェックします。

これらの変更により、math/bigパッケージは、任意精度の有理数をfloat32に正確かつ効率的に変換する機能を提供し、Go言語の数値計算能力をさらに向上させます。

関連リンク

参考にした情報源リンク