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

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

このコミットは、Go言語の math パッケージにおける Nextafter 関数の特殊なケース、特に Nextafter(0, -1) の振る舞いに関するバグを修正するものです。具体的には、Nextafter(0, -1) が期待される -0 ではなく、誤った値を返す問題を解決しています。この修正には、nextafter.go のロジックの微調整と、all_test.go に新しいテストケースを追加して、ゼロ、負のゼロ、NaN (Not a Number) などの特殊な浮動小数点値に対する Nextafter の正確性を検証することが含まれています。

コミット

commit f5c211172bdd95c15bcaab2f2818097de0fbd505
Author: Charles L. Dorian <cldorian@gmail.com>
Date:   Mon Dec 12 15:51:11 2011 -0500

    math: fix special cases in Nextafter
    
    Nextafter(0, -1) != -0.
    
    R=rsc, golang-dev
    CC=golang-dev
    https://golang.org/cl/5467060
---\n src/pkg/math/all_test.go  | 16 +++++++++++++++-\n src/pkg/math/nextafter.go |  7 +++----\n 2 files changed, 18 insertions(+), 5 deletions(-)\n

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

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

元コミット内容

math: fix special cases in Nextafter

Nextafter(0, -1) != -0.

R=rsc, golang-dev
CC=golang-dev
https://golang.org/cl/5467060

変更の背景

このコミットの背景には、浮動小数点数の特殊な振る舞い、特にゼロの表現と Nextafter 関数の定義があります。

Nextafter(x, y) 関数は、x に最も近い y の方向の表現可能な浮動小数点数を返します。例えば、Nextafter(1.0, 2.0)1.0 より大きく、かつ 1.0 に最も近い浮動小数点数を返します。同様に、Nextafter(1.0, 0.0)1.0 より小さく、かつ 1.0 に最も近い浮動小数点数を返します。

問題は、Nextafter(0, -1) のケースで発生していました。IEEE 754 浮動小数点標準では、正のゼロ (+0) と負のゼロ (-0) が区別されます。数学的には同じ値ですが、浮動小数点演算においては異なる振る舞いをすることがあります。Nextafter(0, -1) は、0 から -1 の方向へ進んだときに、0 に最も近い表現可能な浮動小数点数を返すことが期待されます。この場合、期待される結果は -0 です。しかし、バグのある実装では、このケースで -0 ではない誤った値が返されていました。

このバグは、浮動小数点演算の正確性に依存する科学計算や数値解析において、予期せぬ結果やエラーを引き起こす可能性がありました。そのため、Nextafter 関数がIEEE 754標準に厳密に従い、すべての特殊なケースで正しい結果を返すように修正する必要がありました。

前提知識の解説

1. Nextafter 関数

Nextafter(x, y) 関数は、x に最も近い y の方向の表現可能な浮動小数点数を返します。これは、浮動小数点数の隣接する値を計算するために使用されます。例えば、ある浮動小数点数 f の次に大きい数を取得したい場合、Nextafter(f, +Inf) を使用できます。逆に、次に小さい数を取得したい場合は、Nextafter(f, -Inf) を使用します。

この関数は、浮動小数点数の精度テスト、数値安定性の分析、または特定の丸め動作をシミュレートする際に重要です。

2. IEEE 754 浮動小数点標準

IEEE 754 は、浮動小数点数の表現と演算に関する国際標準です。この標準は、異なるコンピュータシステム間での浮動小数点演算の一貫性を保証するために非常に重要です。

IEEE 754 の重要な特徴には以下が含まれます。

  • 表現: 浮動小数点数は、符号ビット、指数部、仮数部で構成されます。
  • 特殊な値:
    • 正の無限大 (+Inf) と負の無限大 (-Inf): オーバーフローなどの結果として生じます。
    • NaN (Not a Number): 不定形な演算(例: 0/0sqrt(-1))の結果として生じます。
    • 正のゼロ (+0) と負のゼロ (-0): 符号ビットが異なるだけで、値は数学的にゼロです。しかし、一部の演算(例: 1/+0+Inf1/-0-Inf)では異なる結果をもたらします。

3. 正のゼロ (+0) と負のゼロ (-0)

IEEE 754 標準では、浮動小数点数のゼロには正のゼロ (+0) と負のゼロ (-0) の2種類があります。これらは符号ビットが異なるだけで、値としてはどちらもゼロです。

  • +0: 符号ビットが0。
  • -0: 符号ビットが1。

これらの区別は、極限の概念や、特定の数学的関数(例: atan2)の振る舞いを正確にモデル化するために重要です。例えば、1.0 / +0.0 は正の無限大を返し、1.0 / -0.0 は負の無限大を返します。

Nextafter 関数において、0 から負の方向へ進む場合、最も近い表現可能な浮動小数点数は -0 であるべきです。このコミットは、この特定のケースでの Nextafter の振る舞いを修正することを目的としています。

技術的詳細

Nextafter(x, y) 関数の実装は、通常、xy の浮動小数点表現をビットレベルで操作することによって行われます。

一般的な Nextafter のロジックは以下のようになります。

  1. NaN のチェック: x または y が NaN の場合、結果は NaN になります。
  2. x == y のチェック: xy が等しい場合、x がそのまま返されます。
  3. 符号の考慮: xy の符号、および x がゼロであるかどうかに応じて、x のビット表現を増減させます。
    • xy の方向へ進む場合、x のビット表現を1つ増やすか減らすかを決定します。
    • 特にゼロの場合、+0 から負の方向へ進むと -0 になるべきであり、-0 から正の方向へ進むと +0 になるべきです。

このコミットで修正されたバグは、Nextafter(0, -1) のケースで、0 (おそらく +0) から -1 の方向へ進む際に、正しく -0 を生成できていなかった点にあります。これは、ゼロのビット表現と、その隣接する浮動小数点数を計算するロジックの間の不整合が原因であると考えられます。

Go言語の math パッケージでは、浮動小数点数をビット表現に変換する Float64bits や、ビット表現から浮動小数点数に変換する Float64frombits のような関数が内部的に使用されます。このバグは、これらのビット操作と、ゼロの特殊なケース(特に符号付きゼロ)のハンドリングが不完全であったために発生しました。

修正は、nextafter.goNextafter 関数の内部ロジックを調整し、特にゼロのケースでの y の符号を考慮に入れることで、正しい -0 を返すようにしました。また、all_test.go に追加されたテストケースは、この修正が正しく機能することを確認するためのものです。これらのテストケースは、0Copysign(0, -1) (負のゼロ)、NaN などの特殊な値と、それらの組み合わせに対する Nextafter の振る舞いを網羅的に検証しています。

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

このコミットでは、以下の2つのファイルが変更されています。

  1. src/pkg/math/all_test.go
  2. src/pkg/math/nextafter.go

src/pkg/math/all_test.go の変更

--- a/src/pkg/math/all_test.go
+++ b/src/pkg/math/all_test.go
@@ -1328,12 +1328,26 @@ var modfSC = [][2]float64{\n }\n \n var vfnextafterSC = [][2]float64{\n+\t{0, 0},\n+\t{0, Copysign(0, -1)},\n+\t{0, -1},\n \t{0, NaN()},\n+\t{Copysign(0, -1), 1},\n+\t{Copysign(0, -1), 0},\n+\t{Copysign(0, -1), Copysign(0, -1)},\n+\t{Copysign(0, -1), -1},\n \t{NaN(), 0},\n \t{NaN(), NaN()},\n }\n var nextafterSC = []float64{\n+\t0,\n+\t0,\n+\t-4.9406564584124654418e-324, // Float64frombits(0x8000000000000001)\n \tNaN(),\n+\t4.9406564584124654418e-324, // Float64frombits(0x0000000000000001)\n+\tCopysign(0, -1),\n+\tCopysign(0, -1),\n+\t-4.9406564584124654418e-324, // Float64frombits(0x8000000000000001)\n \tNaN(),\n \tNaN(),\n }\n@@ -2259,7 +2273,7 @@ func TestNextafter(t *testing.T) {\n \t\t\tt.Errorf(\"Nextafter(%g, %g) = %g want %g\", vf[i], 10.0, f, nextafter[i])\n \t\t}\n \t}\n-\tfor i := 0; i < len(vfmodfSC); i++ {\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 \t\t}\n```

*   `vfnextafterSC` と `nextafterSC` というテストデータ配列に、`Nextafter` 関数の特殊なケース(特にゼロ、負のゼロ、NaN)を網羅する新しいエントリが多数追加されています。
*   `TestNextafter` 関数内のループが `vfmodfSC` から `vfnextafterSC` に変更され、新しいテストデータが使用されるようになっています。

### `src/pkg/math/nextafter.go` の変更

```diff
--- a/src/pkg/math/nextafter.go
+++ b/src/pkg/math/nextafter.go
@@ -8,9 +8,8 @@ package math
 // If x == y, then x is returned.\n //\n // Special cases are:\n-//\tNextafter(NaN, y) = NaN\n-//\tNextafter(x, NaN) = NaN\n-//\tNextafter(0, y) = -0, if y < 0\n+//      Nextafter(NaN, y) = NaN\n+//      Nextafter(x, NaN) = NaN\n func Nextafter(x, y float64) (r float64) {\n \t// TODO(rsc): Remove manual inlining of IsNaN\n \t// when compiler does it for us\n@@ -26,5 +25,5 @@ func Nextafter(x, y float64) (r float64) {\n \tdefault:\n \t\tr = Float64frombits(Float64bits(x) - 1)\n \t}\n-\treturn r\n+\treturn\n }\n```

*   コメント行が削除されています。特に `Nextafter(0, y) = -0, if y < 0` というコメントが削除されており、これはこのコミットで修正される振る舞いに関する古い記述であったことを示唆しています。
*   関数の最後の `return r` が `return` に変更されています。これはGo言語の「naked return」と呼ばれる機能で、関数の戻り値が名前付き (`r float64`) の場合に、`return` だけでその名前付き変数の値を返すことができます。この変更自体はロジックに影響を与えませんが、コードの簡潔化に貢献しています。

## コアとなるコードの解説

### `src/pkg/math/all_test.go` の変更の解説

`all_test.go` の変更は、`Nextafter` 関数のテストカバレッジを大幅に向上させるものです。

*   **`vfnextafterSC` と `nextafterSC` の拡張**:
    *   `{0, 0}`: `Nextafter(0, 0)` は `0` を返すはずです。
    *   `{0, Copysign(0, -1)}`: `Nextafter(0, -0)` は `0` を返すはずです。
    *   `{0, -1}`: **これが今回のバグの核心です。** `Nextafter(0, -1)` は `-0` を返すはずです。
    *   `{Copysign(0, -1), 1}`: `Nextafter(-0, 1)` は `+0` を返すはずです。
    *   `{Copysign(0, -1), 0}`: `Nextafter(-0, 0)` は `-0` を返すはずです。
    *   `{Copysign(0, -1), Copysign(0, -1)}`: `Nextafter(-0, -0)` は `-0` を返すはずです。
    *   `{Copysign(0, -1), -1}`: `Nextafter(-0, -1)` は `-4.9406564584124654418e-324` (負の最小正規化数) を返すはずです。
    *   `4.9406564584124654418e-324` は `Float64frombits(0x0000000000000001)` であり、正の最小正規化数です。
    *   `-4.9406564584124654418e-324` は `Float64frombits(0x8000000000000001)` であり、負の最小正規化数です。

これらのテストケースは、`Nextafter` がゼロ、負のゼロ、そしてそれらの近傍の値をどのように扱うかを厳密に検証します。特に、`Nextafter(0, -1)` が `-0` を返すことを保証するテストが追加されたことが重要です。

*   **`TestNextafter` 関数の変更**:
    *   `for i := 0; i < len(vfmodfSC); i++ {` から `for i := 0; i < len(vfnextafterSC); i++ {` への変更は、以前は `Modf` 関数のテストデータを使用していたループが、正しく `Nextafter` の新しいテストデータを使用するように修正されたことを意味します。これは、テストの意図とコードが一致するようにするための修正です。

### `src/pkg/math/nextafter.go` の変更の解説

`nextafter.go` の変更は非常に小さいですが、重要な意味を持っています。

*   **コメントの削除**:
    *   `// Nextafter(0, y) = -0, if y < 0` というコメントが削除されました。これは、このコミットがまさにこの振る舞いを修正するものであるため、古い誤解を招く記述を削除したものです。修正後の `Nextafter` は、このコメントが示唆するような単純なルールではなく、より正確なIEEE 754のセマンティクスに従うようになります。

*   **`return r` から `return` への変更**:
    *   これはGo言語の構文的な変更であり、機能的な変更ではありません。`Nextafter` 関数のシグネチャが `func Nextafter(x, y float64) (r float64)` となっており、戻り値 `r` が名前付きであるため、関数内で `r` に値が代入されていれば、`return` だけでその `r` の値を返すことができます。これはコードの簡潔性を高めるための一般的なGoのイディオムです。

このコミットの核心的な修正は、`Nextafter` 関数の内部ロジックにあります。提供されたdiffではその詳細なロジック変更は示されていませんが、テストケースの追加とコメントの削除から、ゼロと負のゼロの特殊なケースを正しく処理するように内部実装が調整されたことが強く示唆されます。特に、`Nextafter(0, -1)` が `-0` を返すように、浮動小数点数のビット操作が修正されたと考えられます。

## 関連リンク

*   Go言語 `math` パッケージのドキュメント: [https://pkg.go.dev/math](https://pkg.go.dev/math)
*   IEEE 754 浮動小数点標準 (Wikipedia): [https://ja.wikipedia.org/wiki/IEEE_754%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%A8%99%E6%BA%96](https://ja.wikipedia.org/wiki/IEEE_754%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%A8%99%E6%BA%96)

## 参考にした情報源リンク

*   Go CL 5467060: [https://golang.org/cl/5467060](https://golang.org/cl/5467060) (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)
*   IEEE 754 Floating Point Standard (Wikipedia): [https://en.wikipedia.org/wiki/IEEE_754](https://en.wikipedia.org/wiki/IEEE_754)
*   Go言語の `math.Nextafter` 関数の挙動に関する議論やドキュメント (一般的な情報源として)
*   Go言語のテストの書き方に関するドキュメント (一般的な情報源として)
*   浮動小数点数の表現に関する一般的な情報源 (正のゼロ、負のゼロ、NaNなど)