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

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

このコミットは、Go言語のテストスイートの一部である test/float_lit2.go ファイルに対する変更です。具体的には、浮動小数点数リテラルのテストに使用される値の生成方法を改善し、ハードコードされたマジックナンバーではなく、浮動小数点数の定義から「第一原理」に基づいて計算されるように修正しています。これにより、テストの正確性と保守性が向上しています。

コミット

commit 765b4a3f8651d7523f46f7e6f3839ae4b704b4f2
Author: Robert Griesemer <gri@golang.org>
Date:   Wed May 21 08:53:47 2014 -0700

    test/float_lit2.go: compute test values from first principles
    
    These constants pass go/types constant conversions as well.
    
    LGTM=r
    R=r
    CC=golang-codereviews
    https://golang.org/cl/91590047

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

https://github.com/golang/go/commit/765b4a3f8651d7523f46f7e6f3839ae4b704b4f2

元コミット内容

test/float_lit2.go ファイルにおいて、浮動小数点数リテラルのテスト値を「第一原理」から計算するように変更しました。これらの定数は go/types パッケージの定数変換もパスします。

変更の背景

Go言語のコンパイラや型システムは、浮動小数点数リテラルを正確に処理する必要があります。特に、IEEE 754標準に準拠した float32 (単精度浮動小数点数) および float64 (倍精度浮動小数点数) の表現において、その最大値やそれに近い値、あるいは特定のビットパターンを持つ値が正しく扱われるかを確認することは非常に重要です。

このコミット以前は、test/float_lit2.go 内のテストケースで使用される浮動小数点数値がハードコードされていました。これは、値の由来が不明瞭であり、浮動小数点数の内部表現やGo言語の定数評価ロジックの変更があった場合に、テスト値の妥当性を検証し直す手間が発生するという問題がありました。

この変更の目的は、テスト値の生成方法を改善し、浮動小数点数の基本的な定義(仮数部と指数部のビット数、最大値など)から直接計算するようにすることです。これにより、テスト値の正確性が保証され、コードの可読性と保守性が向上します。また、go/types パッケージにおける定数変換の正確性も同時に検証できるようになります。

前提知識の解説

IEEE 754 浮動小数点数標準

現代のコンピュータシステムで広く採用されている浮動小数点数の表現に関する国際標準です。Go言語の float32float64 もこの標準に準拠しています。

  • 単精度 (float32): 32ビットで表現されます。
    • 1ビットの符号部
    • 8ビットの指数部 (バイアス付き)
    • 23ビットの仮数部 (ケチ表現により暗黙の1ビットが存在するため、実質24ビットの精度)
  • 倍精度 (float64): 64ビットで表現されます。
    • 1ビットの符号部
    • 11ビットの指数部 (バイアス付き)
    • 52ビットの仮数部 (ケチ表現により暗黙の1ビットが存在するため、実質53ビットの精度)

浮動小数点数の値は、符号 × 仮数部 × 2^指数部 の形式で表されます。

仮数部 (Mantissa) と指数部 (Exponent)

  • 仮数部: 数値の有効数字部分を表します。IEEE 754では、仮数部は通常 1.xxxx... の形式で正規化され、先頭の 1 は暗黙的に表現されるため、格納されるのは小数点以下の部分のみです(ケチ表現)。
  • 指数部: 仮数部をどれだけシフトするか(2の何乗を掛けるか)を表します。バイアス付き表現が用いられ、実際の指数値に一定のオフセット(バイアス)が加算されて格納されます。これにより、負の指数も正の整数として表現できます。

Go言語における定数と型変換

Go言語では、数値リテラルは型を持たない定数として扱われます。これらの定数は、必要に応じて特定の型に変換されます。コンパイラは、定数式をコンパイル時に評価し、その結果を埋め込みます。このコミットでは、go/types パッケージがこれらの定数変換をどのように処理するかのテストも兼ねています。

init() 関数

Go言語の init() 関数は、パッケージがインポートされた際に自動的に実行される特殊な関数です。各パッケージは複数の init() 関数を持つことができ、それらは宣言された順序で実行されます。このコミットでは、計算された定数の値が期待通りであるかを実行時に検証するために init() 関数が使用されています。これにより、コンパイル時の定数評価が正しく行われていることを確認できます。

浮動小数点数のバイナリ表現と fmt.Sprintf

浮動小数点数は、内部的にはバイナリ形式で表現されます。このコミットでは、fmt.Sprintf を使用して、浮動小数点数の値を 仮数部p+指数部 の形式で文字列化しています。これは、IEEE 754のバイナリ表現を人間が読みやすい形で示すための一般的な表記法の一つです。例えば、1.0e+01p+0 と表現されます。

技術的詳細

このコミットの核心は、浮動小数点数の最大値に近いテスト値を、その内部表現の定義から直接導出する点にあります。

float32 の定数計算

float32 の場合、以下の定数が定義されています。

  • m32bits = 23: 仮数部のビット数。
  • e32max = 127: 指数部の最大値(バイアス付き)。
  • maxExp32 = e32max - m32bits: これは、正規化された浮動小数点数で表現できる最大の指数部を計算しています。具体的には、仮数部が 1.xxxx... の形式で表現されるため、仮数部のビット数分だけ指数部が「消費」されることを考慮しています。
  • maxMant32 = 1<<(m32bits+1) - 1: これは、仮数部が取りうる最大の整数値を計算しています。m32bits+1 は、暗黙の1ビットを含めた仮数部の全ビット数を表します。例えば、23ビットの仮数部の場合、実質24ビットの精度を持つため、1 << 24 - 1 となり、これは 16777215 に相当します。

これらの定数を用いて、float32 の最大値に近い3つの値が計算されます。

  • maxFloat32_0 = (maxMant32 - 0) << maxExp32
  • maxFloat32_1 = (maxMant32 - 1) << maxExp32
  • maxFloat32_2 = (maxMant32 - 2) << maxExp32

ここで、<< maxExp32 は、仮数部の整数値を maxExp32 だけ左シフトすることで、浮動小数点数の指数部を調整しています。これにより、仮数部が最大値に近い状態で、指数部も最大に近い値を持つ浮動小数点数が生成されます。

init() 関数では、これらの計算結果が期待通りの値になるかを確認しています。例えば、maxExp32104 であること、maxMant3216777215 であること、そして maxFloat32_0 が特定の大きな値になることを検証しています。

float64 の定数計算

float64 の場合も同様に、以下の定数が定義されています。

  • m64bits = 52: 仮数部のビット数。
  • e64max = 1023: 指数部の最大値(バイアス付き)。
  • maxExp64 = e64max - m64bits: float64 における最大の指数部。
  • maxMant64 = 1<<(m64bits+1) - 1: float64 における仮数部の最大の整数値。

しかし、float64 の最大値に近い値 (maxFloat64_0, maxFloat64_1, maxFloat64_2) は、float32 のように直接ビットシフト演算で計算されていません。コミットメッセージのコメントにもあるように、// These expressions are not permitted due to implementation restrictions. と記載されており、Go言語のコンパイル時定数評価の制約により、非常に大きな整数値に対するビットシフト演算が直接サポートされていないためです。

代わりに、// These equivalent values were computed using math/big. とコメントされており、math/big パッケージ(任意精度演算を提供するGoの標準ライブラリ)を使用して事前に計算されたリテラル値が直接使用されています。これは、コンパイル時の制約を回避しつつ、正確なテスト値を保証するための現実的なアプローチです。

init() 関数では、float64 の計算結果も同様に検証されています。

テストケースの生成

cvt スライスは、浮動小数点数の値とそのバイナリ文字列表現のペアを格納するテストケースの集合です。このコミットでは、cvt スライスの初期化方法が変更され、ハードコードされた浮動小数点数リテラルが、上記で定義された定数と fmt.Sprintf を用いた動的な文字列生成に置き換えられています。

例えば、以前は float32(340282356779733661637539395458142568447) のように直接数値が書かれていましたが、変更後は float32(maxFloat32_0) となり、そのバイナリ文字列表現も fmt.Sprintf("%dp+%d", maxMant32-0, maxExp32) のように、計算された仮数部と指数部から生成されます。

この変更により、テスト値が浮動小数点数の定義に厳密に準拠するようになり、Goコンパイラがこれらの値をどのように解釈し、内部表現に変換するかをより正確にテストできるようになります。また、go/types パッケージが定数変換を正しく行っているかどうかの検証も強化されます。

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

test/float_lit2.go ファイルの以下の部分が変更されました。

  1. float32 および float64 の浮動小数点数表現に関する定数定義の追加。
  2. これらの定数を用いた init() 関数の追加による計算値の検証。
  3. var cvt スライス内のテストケースの初期化方法の変更。ハードコードされた数値リテラルが、新しく定義された定数と fmt.Sprintf を用いた動的な値に置き換えられました。
--- a/test/float_lit2.go
+++ b/test/float_lit2.go
@@ -12,20 +12,75 @@ import (
 	"fmt"
 )
 
+const (
+	m32bits   = 23  // number of float32 mantissa bits
+	e32max    = 127 // max. float32 exponent
+	maxExp32  = e32max - m32bits
+	maxMant32 = 1<<(m32bits+1) - 1
+
+	maxFloat32_0 = (maxMant32 - 0) << maxExp32
+	maxFloat32_1 = (maxMant32 - 1) << maxExp32
+	maxFloat32_2 = (maxMant32 - 2) << maxExp32
+)
+
+func init() {
+	if maxExp32 != 104 {
+		panic("incorrect maxExp32")
+	}
+	if maxMant32 != 16777215 {
+		panic("incorrect maxMant32")
+	}
+	if maxFloat32_0 != 340282346638528859811704183484516925440 {
+		panic("incorrect maxFloat32_0")
+	}
+}
+
+const (
+	m64bits   = 52   // number of float64 mantissa bits
+	e64max    = 1023 // max. float64 exponent
+	maxExp64  = e64max - m64bits
+	maxMant64 = 1<<(m64bits+1) - 1
+
+	// These expressions are not permitted due to implementation restrictions.
+	// maxFloat64_0 = (maxMant64-0) << maxExp64
+	// maxFloat64_1 = (maxMant64-1) << maxExp64
+	// maxFloat64_2 = (maxMant64-2) << maxExp64
+
+	// These equivalent values were computed using math/big.
+	maxFloat64_0 = 1.7976931348623157e308
+	maxFloat64_1 = 1.7976931348623155e308
+	maxFloat64_2 = 1.7976931348623153e308
+)
+
+func init() {
+	if maxExp64 != 971 {
+		panic("incorrect maxExp64")
+	}
+	if maxMant64 != 9007199254740991 {
+		panic("incorrect maxMant64")
+	}
+}
+
 var cvt = []struct {
 	val    interface{}
 	binary string
 }{
-	{float32(-340282356779733661637539395458142568447), "-16777215p+104"},
-	{float32(-340282326356119256160033759537265639424), "-16777214p+104"},
-	{float32(340282326356119256160033759537265639424), "16777214p+104"},
-	{float32(340282356779733661637539395458142568447), "16777215p+104"},
-	{float64(-1.797693134862315807937289714053e+308), "-9007199254740991p+971"},
-	{float64(-1.797693134862315708145274237317e+308), "-9007199254740991p+971"},
-	{float64(-1.797693134862315608353258760581e+308), "-9007199254740990p+971"},
-	{float64(1.797693134862315608353258760581e+308), "9007199254740990p+971"},
-	{float64(1.797693134862315708145274237317e+308), "9007199254740991p+971"},
-	{float64(1.797693134862315807937289714053e+308), "9007199254740991p+971"},
+	{float32(maxFloat32_0), fmt.Sprintf("%dp+%d", maxMant32-0, maxExp32)},
+	{float32(maxFloat32_1), fmt.Sprintf("%dp+%d", maxMant32-1, maxExp32)},
+	{float32(maxFloat32_2), fmt.Sprintf("%dp+%d", maxMant32-2, maxExp32)},
+
+	{float64(maxFloat64_0), fmt.Sprintf("%dp+%d", maxMant64-0, maxExp64)},
+	{float64(maxFloat64_1), fmt.Sprintf("%dp+%d", maxMant64-1, maxExp64)},
+	{float64(maxFloat64_2), fmt.Sprintf("%dp+%d", maxMant64-2, maxExp64)},
+
+	{float32(-maxFloat32_0), fmt.Sprintf("-%dp+%d", maxMant32-0, maxExp32)},
+	{float32(-maxFloat32_1), fmt.Sprintf("-%dp+%d", maxMant32-1, maxExp32)},
+	{float32(-maxFloat32_2), fmt.Sprintf("-%dp+%d", maxMant32-2, maxExp32)},
+
+	{float64(-maxFloat64_0), fmt.Sprintf("-%dp+%d", maxMant64-0, maxExp64)},
+	{float64(-maxFloat64_1), fmt.Sprintf("-%dp+%d", maxMant64-1, maxExp64)},
+	{float64(-maxFloat64_2), fmt.Sprintf("-%dp+%d", maxMant64-2, maxExp64)},
 }
 
 func main() {

コアとなるコードの解説

このコミットの主要な変更は、浮動小数点数のテスト値を、その内部表現の構成要素から動的に生成するようにした点です。

  1. 定数定義:

    • m32bits (23) と e32max (127) は、それぞれ float32 の仮数部と指数部のビット数および最大指数値を定義しています。
    • maxExp32e32max - m32bits で計算され、これは float32 で表現可能な最大の正規化された指数部を示します。
    • maxMant321<<(m32bits+1) - 1 で計算され、これは float32 の仮数部が取りうる最大の整数値(暗黙の1ビットを含む)を示します。
    • maxFloat32_0, maxFloat32_1, maxFloat32_2 は、float32 の最大値、およびそれに近い2つの値をビットシフト演算によって生成しています。例えば (maxMant32 - 0) << maxExp32 は、仮数部が最大値で、指数部も最大値である浮動小数点数を表現しようとしています。これは、IEEE 754の浮動小数点数の表現形式を直接操作するようなアプローチです。
    • float64 についても同様の定数 (m64bits, e64max, maxExp64, maxMant64) が定義されています。ただし、maxFloat64_0 などは、Goのコンパイル時定数評価の制約により、直接ビットシフトで計算する代わりに、math/big パッケージで事前に計算されたリテラル値が使用されています。これは、Goのコンパイラが非常に大きな整数に対するビットシフトをコンパイル時に処理できないためです。
  2. init() 関数による検証:

    • float32 および float64 の定数ブロックの後に init() 関数が追加されています。
    • これらの init() 関数は、計算された定数 (maxExp32, maxMant32, maxFloat32_0 など) が期待通りの値になっているかを実行時に panic を用いて検証します。これにより、定数計算のロジックが正しく、Goコンパイラがそれらを正しく評価していることを確認できます。
  3. cvt スライスの更新:

    • cvt スライスは、val (浮動小数点数値) と binary (そのバイナリ文字列表現) のペアを持つ構造体の配列です。
    • このコミットでは、val の部分がハードコードされた数値から、新しく定義された maxFloat32_XmaxFloat64_X といった定数に置き換えられました。
    • binary の部分も、ハードコードされた文字列から fmt.Sprintf("%dp+%d", ...) の形式で動的に生成されるようになりました。これにより、仮数部と指数部を直接指定して文字列を生成するため、浮動小数点数の内部表現とテストケースの整合性が高まります。例えば、maxMant32-0 は仮数部の値、maxExp32 は指数部の値に対応します。

この変更により、浮動小数点数のテストケースがより堅牢になり、GoコンパイラがIEEE 754標準に準拠した浮動小数点数を正確に処理できることを保証するための重要なステップとなります。特に、go/types パッケージが定数変換を正しく行っているかどうかの検証にも寄与します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • IEEE 754標準に関する一般的な情報源 (Wikipediaなど)
  • Go言語のコンパイル時定数評価に関する情報 (必要に応じてGoのソースコードや関連する設計ドキュメントを参照)
  • コミットメッセージと差分情報 (git diff)
  • Go言語のテストコードの慣習