[インデックス 19512] ファイルの概要
このコミットは、Go言語のmath
パッケージにfloat32
型に対応するNextafter32
関数を実装し、既存のNextafter
関数をfloat64
型に特化したNextafter64
として明確化するものです。これにより、浮動小数点数の「次の」表現可能な値をより厳密に、かつ型安全に扱うための機能が拡充されました。
コミット
commit a9035ede1b7f705f7cd73c7de51d54f6119b123b
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jun 11 09:09:37 2014 -0700
math: implement Nextafter32
Provide Nextafter64 as alias to Nextafter.
For submission after the 1.3 release.
Fixes #8117.
LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/101750048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a9035ede1b7f705f7cd73c7de51d54f6119b123b
元コミット内容
commit a9035ede1b7f705f7cd73c7de51d54f6119b123b
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jun 11 09:09:37 2014 -0700
math: implement Nextafter32
Provide Nextafter64 as alias to Nextafter.
For submission after the 1.3 release.
Fixes #8117.
LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/101750048
---
src/pkg/math/all_test.go | 79 ++++++++++++++++++++++++++++++++++++++++-------\n src/pkg/math/nextafter.go | 40 +++++++++++++++++++-----\n 2 files changed, 101 insertions(+), 18 deletions(-)\n\ndiff --git a/src/pkg/math/all_test.go b/src/pkg/math/all_test.go\nindex 0d8b10f67f..8b00ef1d6c 100644\n--- a/src/pkg/math/all_test.go\n+++ b/src/pkg/math/all_test.go\n@@ -456,7 +456,19 @@ var modf = [][2]float64{\n \t{1.0000000000000000e+00, 8.2530809168085506044576505e-01},\n \t{-8.0000000000000000e+00, -6.8592476857560136238589621e-01},\n }\n-var nextafter = []float64{\n+var nextafter32 = []float32{\n+\t4.979012489318848e+00,\n+\t7.738873004913330e+00,\n+\t-2.768800258636475e-01,\n+\t-5.010602951049805e+00,\n+\t9.636294364929199e+00,\n+\t2.926377534866333e+00,\n+\t5.229084014892578e+00,\n+\t2.727940082550049e+00,\n+\t1.825308203697205e+00,\n+\t-8.685923576354980e+00,\n+}\n+var nextafter64 = []float64{\n \t4.97901192488367438926388786e+00,\n \t7.73887247457810545370193722e+00,\n \t-2.7688005719200153853520874e-01,\n@@ -1331,7 +1343,32 @@ var modfSC = [][2]float64{\n \t{NaN(), NaN()},\n }\n \n-var vfnextafterSC = [][2]float64{\n+var vfnextafter32SC = [][2]float32{\n+\t{0, 0},\n+\t{0, float32(Copysign(0, -1))},\n+\t{0, -1},\n+\t{0, float32(NaN())},\n+\t{float32(Copysign(0, -1)), 1},\n+\t{float32(Copysign(0, -1)), 0},\n+\t{float32(Copysign(0, -1)), float32(Copysign(0, -1))},\n+\t{float32(Copysign(0, -1)), -1},\n+\t{float32(NaN()), 0},\n+\t{float32(NaN()), float32(NaN())},\n+}\n+var nextafter32SC = []float32{\n+\t0,\n+\t0,\n+\t-1.401298464e-45, // Float32frombits(0x80000001)\n+\tfloat32(NaN()),\n+\t1.401298464e-45, // Float32frombits(0x00000001)\n+\tfloat32(Copysign(0, -1)),\n+\tfloat32(Copysign(0, -1)),\n+\t-1.401298464e-45, // Float32frombits(0x80000001)\n+\tfloat32(NaN()),\n+\tfloat32(NaN()),\n+}\n+\n+var vfnextafter64SC = [][2]float64{\n \t{0, 0},\n \t{0, Copysign(0, -1)},\n \t{0, -1},\n@@ -1343,7 +1380,7 @@ var vfnextafterSC = [][2]float64{\n \t{NaN(), 0},\n \t{NaN(), NaN()},\n }\n-var nextafterSC = []float64{\n+var nextafter64SC = []float64{\n \t0,\n \t0,\n \t-4.9406564584124654418e-324, // Float64frombits(0x8000000000000001)\n@@ -2303,15 +2340,29 @@ func TestModf(t *testing.T) {\n \t}\n }\n \n-func TestNextafter(t *testing.T) {\n+func TestNextafter32(t *testing.T) {\n+\tfor i := 0; i < len(vf); i++ {\n+\t\tvfi := float32(vf[i])\n+\t\tif f := Nextafter32(vfi, 10); nextafter32[i] != f {\n+\t\t\tt.Errorf(\"Nextafter32(%g, %g) = %g want %g\", vfi, 10.0, f, nextafter32[i])\n+\t\t}\n+\t}\n+\tfor i := 0; i < len(vfnextafter32SC); i++ {\n+\t\tif f := Nextafter32(vfnextafter32SC[i][0], vfnextafter32SC[i][1]); !alike(float64(nextafter32SC[i]), float64(f)) {\n+\t\t\tt.Errorf(\"Nextafter32(%g, %g) = %g want %g\", vfnextafter32SC[i][0], vfnextafter32SC[i][1], f, nextafter32SC[i])\n+\t\t}\n+\t}\n+}\n+\n+func TestNextafter64(t *testing.T) {\n \tfor i := 0; i < len(vf); i++ {\n-\t\tif f := Nextafter(vf[i], 10); nextafter[i] != f {\n-\t\t\tt.Errorf(\"Nextafter(%g, %g) = %g want %g\", vf[i], 10.0, f, nextafter[i])\n+\t\tif f := Nextafter64(vf[i], 10); nextafter64[i] != f {\n+\t\t\tt.Errorf(\"Nextafter64(%g, %g) = %g want %g\", vf[i], 10.0, f, nextafter64[i])\n \t\t}\n \t}\n-\tfor i := 0; i < len(vfnextafterSC); i++ {\n-\t\tif f := Nextafter(vfnextafterSC[i][0], vfnextafterSC[i][1]); !alike(nextafterSC[i], f) {\n-\t\t\tt.Errorf(\"Nextafter(%g, %g) = %g want %g\", vfnextafterSC[i][0], vfnextafterSC[i][1], f, nextafterSC[i])\n+\tfor i := 0; i < len(vfnextafter64SC); i++ {\n+\t\tif f := Nextafter64(vfnextafter64SC[i][0], vfnextafter64SC[i][1]); !alike(nextafter64SC[i], f) {\n+\t\t\tt.Errorf(\"Nextafter64(%g, %g) = %g want %g\", vfnextafter64SC[i][0], vfnextafter64SC[i][1], f, nextafter64SC[i])\n \t\t}\n \t}\n }\n@@ -2827,9 +2878,15 @@ func BenchmarkModf(b *testing.B) {\n \t}\n }\n \n-func BenchmarkNextafter(b *testing.B) {\n+func BenchmarkNextafter32(b *testing.B) {\n+\tfor i := 0; i < b.N; i++ {\n+\t\tNextafter32(.5, 1)\n+\t}\n+}\n+\n+func BenchmarkNextafter64(b *testing.B) {\n \tfor i := 0; i < b.N; i++ {\n-\t\tNextafter(.5, 1)\n+\t\tNextafter64(.5, 1)\n \t}\n }\n \ndiff --git a/src/pkg/math/nextafter.go b/src/pkg/math/nextafter.go\nindex 7c4b5bcdfe..fab1ad267e 100644\n--- a/src/pkg/math/nextafter.go\n+++ b/src/pkg/math/nextafter.go\n@@ -4,13 +4,33 @@\n \n package math\n \n-// Nextafter returns the next representable value after x towards y.\n-// If x == y, then x is returned.\n-//\n-// Special cases are:\n-// Nextafter(NaN, y) = NaN\n-// Nextafter(x, NaN) = NaN\n-func Nextafter(x, y float64) (r float64) {\n+// Nextafter32 returns the next representable float32 value after x towards y.\n+// Special cases:\n+//\tNextafter32(x, x) = x\n+// Nextafter32(NaN, y) = NaN\n+// Nextafter32(x, NaN) = NaN\n+func Nextafter32(x, y float32) (r float32) {\n+\tswitch {\n+\tcase IsNaN(float64(x)) || IsNaN(float64(y)): // special case\n+\t\tr = float32(NaN())\n+\tcase x == y:\n+\t\tr = x\n+\tcase x == 0:\n+\t\tr = float32(Copysign(float64(Float32frombits(1)), float64(y)))\n+\tcase (y > x) == (x > 0):\n+\t\tr = Float32frombits(Float32bits(x) + 1)\n+\tdefault:\n+\t\tr = Float32frombits(Float32bits(x) - 1)\n+\t}\n+\treturn\n+}\n+\n+// Nextafter64 returns the next representable float64 value after x towards y.\n+// Special cases:\n+//\tNextafter64(x, x) = x\n+// Nextafter64(NaN, y) = NaN\n+// Nextafter64(x, NaN) = NaN\n+func Nextafter64(x, y float64) (r float64) {\n \tswitch {\n \tcase IsNaN(x) || IsNaN(y): // special case\n \t\tr = NaN()\n@@ -25,3 +45,9 @@ func Nextafter(x, y float64) (r float64) {\n \t}\n \treturn\n }\n+\n+// Nextafter is the same as Nextafter64.\n+// It is provided for backward-compatibility only.\n+func Nextafter(x, y float64) float64 {\n+\treturn Nextafter64(x, y)\n+}\n```
## 変更の背景
このコミットは、Go言語の`math`パッケージにおける浮動小数点数操作の精度と汎用性を向上させることを目的としています。既存の`Nextafter`関数は`float64`型(倍精度浮動小数点数)のみを扱っていましたが、`float32`型(単精度浮動小数点数)に対しても同様の機能が求められていました。
背景には、IEEE 754浮動小数点数標準において、単精度と倍精度で異なる表現形式と精度を持つため、それぞれの型に特化した`Nextafter`関数が必要であるという認識があります。`Nextafter`関数は、与えられた浮動小数点数`x`から`y`の方向へ進んだときに、次に表現可能な浮動小数点数を返します。これは、数値計算における微小な誤差の分析、数値安定性のテスト、あるいは特定の数値範囲の列挙といった場面で非常に重要な役割を果たします。
コミットメッセージにある「For submission after the 1.3 release.」という記述は、Go 1.3リリース後の機能追加として計画されていたことを示唆しています。また、「Fixes #8117」は、この変更が特定の課題(おそらく`float32`版`Nextafter`の欠如)を解決するものであることを示しています。これにより、Go言語の`math`パッケージが、より包括的で堅牢な浮動小数点数操作機能を提供できるようになりました。
## 前提知識の解説
### 浮動小数点数表現 (IEEE 754)
コンピュータにおける浮動小数点数は、通常、国際標準であるIEEE 754形式で表現されます。この標準には、単精度(`float32`、32ビット)と倍精度(`float64`、64ビット)の2つの主要な形式があります。
* **単精度 (float32)**: 1ビットの符号部、8ビットの指数部、23ビットの仮数部(ケチ表現により実質24ビット)で構成されます。約7桁の10進精度を持ちます。
* **倍精度 (float64)**: 1ビットの符号部、11ビットの指数部、52ビットの仮数部(ケチ表現により実質53ビット)で構成されます。約15-17桁の10進精度を持ちます。
これらの表現では、連続する実数を完全に表現することはできず、特定の離散的な値のみを表現できます。`Nextafter`関数は、この「離散的な値」の隣接関係を利用して、ある数値の次に表現可能な値を特定します。
### `Nextafter`関数
`Nextafter(x, y)`関数は、浮動小数点数`x`から`y`の方向へ進んだときに、次に表現可能な浮動小数点数を返します。
* もし`y > x`であれば、`x`より大きく、`x`に最も近い表現可能な数を返します。
* もし`y < x`であれば、`x`より小さく、`x`に最も近い表現可能な数を返します。
* もし`x == y`であれば、`x`を返します。
* 特殊なケースとして、`NaN`(非数)が引数に含まれる場合は`NaN`を返します。
* 符号付きゼロ(+0, -0)や無限大(+Inf, -Inf)も適切に扱われます。
この関数は、数値計算のテストにおいて、境界条件の検証や、浮動小数点演算の丸め誤差の影響を評価する際に特に有用です。
### Go言語の`math`パッケージ
Go言語の標準ライブラリである`math`パッケージは、基本的な数学関数(三角関数、指数関数、対数関数など)や、浮動小数点数に関するユーティリティ関数を提供します。`IsNaN`、`Copysign`、`Float32bits`、`Float32frombits`、`Float64bits`、`Float64frombits`といった関数は、浮動小数点数の内部表現を操作したり、特殊な値を扱ったりするために使用されます。
* `Float32bits(f float32) uint32`: `float32`値をIEEE 754バイナリ表現の`uint32`として返します。
* `Float32frombits(b uint32) float32`: `uint32`のバイナリ表現から`float32`値を再構築します。
* `Copysign(x, y float64) float64`: `x`の絶対値と`y`の符号を持つ値を返します。
これらのビット操作関数は、`Nextafter`のような関数を実装する際に、浮動小数点数の内部構造を直接操作するために不可欠です。
## 技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
1. **`Nextafter32`関数の新規導入**: `float32`型を引数にとり、`float32`型の結果を返す`Nextafter32`関数が`src/pkg/math/nextafter.go`に新しく追加されました。これにより、単精度浮動小数点数に対しても「次の表現可能な値」を求める機能が提供されます。
2. **`Nextafter`関数の`Nextafter64`への明確化とエイリアス化**: 既存の`Nextafter(x, y float64) float64`関数は、その役割をより明確にするために`Nextafter64`と改名されました。そして、後方互換性のために、元の`Nextafter`関数は`Nextafter64`のエイリアスとして残されました。これは、既存のコードが`Nextafter`を呼び出している場合でも、変更なく動作し続けることを保証します。
3. **テストケースの拡充**: `src/pkg/math/all_test.go`において、`Nextafter32`および`Nextafter64`(旧`Nextafter`)の動作を検証するための新しいテストデータセットとテスト関数が追加されました。これには、通常の数値、ゼロ、符号付きゼロ、NaN、無限大などの特殊なケースが含まれます。
`Nextafter32`の実装は、`Nextafter64`(旧`Nextafter`)のロジックと非常に似ていますが、`float32`のビット表現(`uint32`)を操作する点が異なります。基本的なロジックは以下のようになります。
* **特殊ケースのハンドリング**:
* `x`または`y`が`NaN`の場合、結果は`NaN`。
* `x`と`y`が等しい場合、`x`をそのまま返す。
* **ゼロのハンドリング**:
* `x`がゼロの場合、`y`の符号を持つ最小の正規化数(または非正規化数)を返します。これは`Copysign(Float32frombits(1), y)`(`float32`の場合)または`Copysign(Float64frombits(1), y)`(`float64`の場合)で実現されます。
* **一般的なケース**:
* `y`が`x`より大きい場合(かつ`x`が正の場合)、または`y`が`x`より小さい場合(かつ`x`が負の場合)、`x`のビット表現に1を加算(または減算)して次の表現可能な値を求めます。これは`Float32bits(x) + 1`または`Float32bits(x) - 1`(`float32`の場合)で行われます。
* `y`が`x`と逆の方向にある場合(例: `x`が正で`y`が負)、`x`のビット表現から1を減算(または加算)して次の表現可能な値を求めます。
このビット操作によるアプローチは、IEEE 754浮動小数点数の特性(正規化された数値のビット表現が、数値の大小関係とほぼ一致する)を利用した効率的な方法です。
## コアとなるコードの変更箇所
### `src/pkg/math/nextafter.go`
```go
// Nextafter32 returns the next representable float32 value after x towards y.
// Special cases:
// Nextafter32(x, x) = x
// Nextafter32(NaN, y) = NaN
// Nextafter32(x, NaN) = NaN
func Nextafter32(x, y float32) (r float32) {
switch {
case IsNaN(float64(x)) || IsNaN(float64(y)): // special case
r = float32(NaN())
case x == y:
r = x
case x == 0:
r = float32(Copysign(float64(Float32frombits(1)), float64(y)))
case (y > x) == (x > 0):
r = Float32frombits(Float32bits(x) + 1)
default:
r = Float32frombits(Float32bits(x) - 1)
}
return
}
// Nextafter64 returns the next representable float64 value after x towards y.
// Special cases:
// Nextafter64(x, x) = x
// Nextafter64(NaN, y) = NaN
// Nextafter64(x, NaN) = NaN
func Nextafter64(x, y float64) (r float64) {
switch {
case IsNaN(x) || IsNaN(y): // special case
r = NaN()
case x == y:
r = x
case x == 0:
r = Copysign(Float64frombits(1), y)
case (y > x) == (x > 0):
r = Float64frombits(Float64bits(x) + 1)
default:
r = Float64frombits(Float64bits(x) - 1)
}
return
}
// Nextafter is the same as Nextafter64.
// It is provided for backward-compatibility only.
func Nextafter(x, y float64) float64 {
return Nextafter64(x, y)
}
src/pkg/math/all_test.go
// 新しいテストデータセットの追加
var nextafter32 = []float32{...} // float32のテスト期待値
var vfnextafter32SC = [][2]float32{...} // float32の特殊ケーステストデータ
var nextafter32SC = []float32{...} // float32の特殊ケーステスト期待値
// 既存のテストデータセットの名前変更
var nextafter64 = []float64{...} // 旧nextafter
var vfnextafter64SC = [][2]float64{...} // 旧vfnextafterSC
var nextafter64SC = []float64{...} // 旧nextafterSC
// 新しいテスト関数の追加
func TestNextafter32(t *testing.T) {
for i := 0; i < len(vf); i++ {
vfi := float32(vf[i])
if f := Nextafter32(vfi, 10); nextafter32[i] != f {
t.Errorf("Nextafter32(%g, %g) = %g want %g", vfi, 10.0, f, nextafter32[i])
}
}
for i := 0; i < len(vfnextafter32SC); i++ {
if f := Nextafter32(vfnextafter32SC[i][0], vfnextafter32SC[i][1]); !alike(float64(nextafter32SC[i]), float64(f)) {
t.Errorf("Nextafter32(%g, %g) = %g want %g", vfnextafter32SC[i][0], vfnextafter32SC[i][1], f, nextafter32SC[i])
}
}
}
// 既存のテスト関数の名前変更と型変更
func TestNextafter64(t *testing.T) {
for i := 0; i < len(vf); i++ {
if f := Nextafter64(vf[i], 10); nextafter64[i] != f {
t.Errorf("Nextafter64(%g, %g) = %g want %g", vf[i], 10.0, f, nextafter64[i])
}
}
for i := 0; i < len(vfnextafter64SC); i++ {
if f := Nextafter64(vfnextafter64SC[i][0], vfnextafter64SC[i][1]); !alike(nextafter64SC[i], f) {
t.Errorf("Nextafter64(%g, %g) = %g want %g", vfnextafter64SC[i][0], vfnextafter64SC[i][1], f, nextafter64SC[i])
}
}
}
// ベンチマーク関数の追加
func BenchmarkNextafter32(b *testing.B) {
for i := 0; i < b.N; i++ {
Nextafter32(.5, 1)
}
}
func BenchmarkNextafter64(b *testing.B) {
for i := 0; i < b.N; i++ {
Nextafter64(.5, 1)
}
}
コアとなるコードの解説
Nextafter32
関数の実装
Nextafter32
関数は、float32
型のx
とy
を受け取り、x
からy
の方向へ進んだ次のfloat32
値を返します。
IsNaN(float64(x)) || IsNaN(float64(y))
: まず、引数のいずれかがNaN
(非数)であるかをチェックします。float32
のNaN
チェックには、一度float64
にキャストしてからmath.IsNaN
を使用しています。これは、math
パッケージのIsNaN
がfloat64
を受け取るためです。いずれかがNaN
であれば、結果もNaN
となります。x == y
:x
とy
が同じ値であれば、x
自体が「次の」値であるため、x
をそのまま返します。x == 0
:x
がゼロの場合の特殊処理です。浮動小数点数には正のゼロ(+0
)と負のゼロ(-0
)が存在するため、y
の符号をコピーした上で、Float32frombits(1)
(float32
で表現可能な最小の正の非正規化数)を返します。これにより、ゼロからy
の方向へ進んだ最初の表現可能な非ゼロ値が得られます。(y > x) == (x > 0)
: この条件は、y
がx
よりも大きく、かつx
が正である場合(つまり、正の方向へ進む場合)、またはy
がx
よりも小さく、かつx
が負である場合(つまり、負の方向へ進む場合)に真となります。これは、x
の絶対値を大きくする方向に進むことを意味します。この場合、Float32bits(x) + 1
によってx
のビット表現に1を加算し、その結果をFloat32frombits
でfloat32
値に戻します。これにより、x
の次に大きい(または次に小さい)表現可能な値が得られます。default
: 上記のいずれにも当てはまらない場合、つまりy
がx
と逆の方向にある場合(例:x
が正でy
が負)、x
の絶対値を小さくする方向に進むことを意味します。この場合、Float32bits(x) - 1
によってx
のビット表現から1を減算し、その結果をFloat32frombits
でfloat32
値に戻します。
Nextafter64
関数の実装
Nextafter64
関数は、既存のNextafter
関数のロジックをそのまま引き継ぎ、float64
型に特化したバージョンとして明確化されました。実装ロジックはNextafter32
とほぼ同じですが、float64
のビット操作関数(Float64bits
, Float64frombits
)を使用する点が異なります。
Nextafter
エイリアス
Nextafter
関数は、後方互換性のためにNextafter64
のエイリアスとして定義されました。これにより、Go 1.3以前のコードでmath.Nextafter
を呼び出していた場合でも、コードの変更なしに新しいNextafter64
の機能を利用できます。
テストコードの変更
src/pkg/math/all_test.go
では、Nextafter32
とNextafter64
の導入に伴い、テストデータとテスト関数が適切に更新されています。
nextafter32
,vfnextafter32SC
,nextafter32SC
といったfloat32
用の新しいテストデータ配列が追加され、Nextafter32
の期待される挙動を定義しています。- 既存の
nextafter
,vfnextafterSC
,nextafterSC
といったfloat64
用のテストデータ配列は、それぞれnextafter64
,vfnextafter64SC
,nextafter64SC
に名前が変更され、Nextafter64
のテストに使用されます。 TestNextafter32
とTestNextafter64
という新しいテスト関数が追加され、それぞれの関数が対応するテストデータセットを用いて、Nextafter32
とNextafter64
の正確性を検証しています。- ベンチマーク関数も
BenchmarkNextafter32
とBenchmarkNextafter64
に分割され、それぞれの関数のパフォーマンスを個別に測定できるようになりました。
これらの変更により、math
パッケージの浮動小数点数操作の正確性と堅牢性が向上し、開発者がfloat32
とfloat64
の両方で「次の表現可能な値」を確実に取得できるようになりました。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/101750048
参考にした情報源リンク
- Go Gerrit Code Review: https://golang.org/cl/101750048
- IEEE 754 浮動小数点数標準に関する一般的な知識
- Go言語の
math
パッケージに関する一般的な知識 - Go言語のテストとベンチマークに関する一般的な知識