[インデックス 10646] ファイルの概要
このコミットは、Go言語の math
パッケージにおける Dim
、Max
、Min
関数について、浮動小数点数の特殊なケース(無限大 Inf
、非数 NaN
、符号付きゼロ ±0
)の振る舞いを明確に文書化し、それに対応するテストケースと実装の修正を行ったものです。特に、amd64
アーキテクチャ向けの低レベルアセンブリコードにも変更が加えられ、これらの特殊ケースが正しく処理されるように改善されています。また、Sin
関数のコメント内の誤字修正も含まれています。
コミット
commit e4de2e7fd04c92d4035cd268d5043f2380aef437
Author: Charles L. Dorian <cldorian@gmail.com>
Date: Wed Dec 7 14:52:17 2011 -0500
math: document special-cases behavior for Dim, Max and Min
Max returns +Inf if x or y is +Inf; else it returns NaN if either x or y is NaN. Max(-0, -0) returns -0.
Min returns -Inf if x or y is -Inf; else it returns NaN if either x or y is NaN. Min(+0, -0) returns -0.
Dim(+Inf, +Inf) = NaN, Dim(-Inf, -Inf) = NaN and Dim(NaN, anything) = NaN.
Also, change "conditions" to "cases" for Sin (missed it in previous CL).
R=rsc, dave
CC=golang-dev
https://golang.org/cl/5437137
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4de2e7fd04c92d4035cd268d5043f2380aef437
元コミット内容
math
パッケージの Dim
、Max
、Min
関数における特殊ケースの振る舞いを文書化する。
Max
は、x
またはy
が+Inf
の場合+Inf
を返す。それ以外の場合、x
またはy
のいずれかがNaN
であればNaN
を返す。Max(-0, -0)
は-0
を返す。Min
は、x
またはy
が-Inf
の場合-Inf
を返す。それ以外の場合、x
またはy
のいずれかがNaN
であればNaN
を返す。Min(+0, -0)
は-0
を返す。Dim(+Inf, +Inf) = NaN
、Dim(-Inf, -Inf) = NaN
、Dim(NaN, anything) = NaN
。- また、
Sin
関数のコメント内の "conditions" を "cases" に変更する(前回の変更で修正し忘れたもの)。
変更の背景
浮動小数点演算は、通常の数値だけでなく、特殊な値(無限大 Inf
、非数 NaN
、符号付きゼロ ±0
)を扱う際に、その振る舞いが厳密にIEEE 754標準によって定義されています。Go言語の math
パッケージは、これらの標準に準拠した正確な浮動小数点演算を提供することを目指しています。
このコミットの背景には、Dim
、Max
、Min
といった基本的な数学関数が、これらの特殊な入力値に対してどのような結果を返すのかを明確にし、その振る舞いをコードのコメントとして文書化する必要性がありました。これにより、開発者がこれらの関数を使用する際に、予期せぬ結果に遭遇することなく、関数の挙動を正確に理解できるようになります。
特に、Max
や Min
のような比較関数では、NaN
が含まれる場合の比較結果や、+0
と -0
の比較における符号の扱いなど、直感に反する可能性のある挙動が存在します。これらの特殊ケースを明示的に定義し、テストで検証することで、関数の堅牢性と信頼性を向上させることが目的です。また、amd64
アーキテクチャ固有のアセンブリ実装においても、これらの特殊ケースが効率的かつ正確に処理されるように修正が加えられています。
前提知識の解説
このコミットを理解するためには、以下の浮動小数点数に関する前提知識が不可欠です。
-
IEEE 754 浮動小数点標準:
- 現代のほとんどのコンピュータシステムで採用されている浮動小数点数の表現と演算に関する国際標準です。
- 単精度 (float32) と 倍精度 (float64) があり、Go言語の
float64
は倍精度に相当します。 - この標準は、通常の数値だけでなく、以下の特殊な値を定義しています。
- 無限大 (Infinity,
Inf
): 正の無限大 (+Inf
) と負の無限大 (-Inf
) があります。例えば、1.0 / 0.0
は+Inf
になります。 - 非数 (Not a Number,
NaN
): 不定な結果(例:0.0 / 0.0
、Inf - Inf
、Inf * 0
、sqrt(-1)
)を表します。NaN
は、いかなる値(NaN
自身を含む)とも比較してもfalse
になります(NaN == NaN
はfalse
)。 - 符号付きゼロ (
±0
): 正のゼロ (+0
) と負のゼロ (-0
) があります。これらは数値的には等しい (+0 == -0
はtrue
) ですが、一部の演算(例:1.0 / +0
は+Inf
、1.0 / -0
は-Inf
)で異なる結果を生じることがあります。
- 無限大 (Infinity,
-
math
パッケージの関数:- Go言語の標準ライブラリ
math
パッケージは、基本的な数学関数を提供します。 Dim(x, y float64) float64
:max(x-y, 0)
を計算します。つまり、x
がy
より大きい場合はx-y
を、そうでない場合は0
を返します。Max(x, y float64) float64
:x
とy
のうち大きい方を返します。Min(x, y float64) float64
:x
とy
のうち小さい方を返します。IsInf(f float64, sign int) bool
:f
が無限大であるかどうかをチェックします。sign
が1
なら+Inf
、-1
なら-Inf
、0
ならどちらかの無限大をチェックします。IsNaN(f float64) bool
:f
がNaN
であるかどうかをチェックします。Signbit(x float64) bool
:x
の符号ビットがセットされている(つまり負の数であるか、負のゼロであるか)場合にtrue
を返します。
- Go言語の標準ライブラリ
-
Go言語のアセンブリ (
.s
ファイル):- Go言語の標準ライブラリの一部関数は、パフォーマンス最適化のために特定アーキテクチャ向けのアセンブリ言語で実装されています。
src/pkg/math/dim_amd64.s
は、amd64
アーキテクチャにおけるDim
、Max
、Min
関数のアセンブリ実装です。 - アセンブリコードでは、浮動小数点レジスタ(例:
X0
,X1
)や命令(例:MOVSD
,SUBSD
,MAXSD
,MINSD
)が直接使用されます。 - 特殊ケースの処理は、ビットパターンを直接比較したり、条件分岐(例:
JEQ
,JNE
,JLE
)を用いて実装されます。 PosInf
(0x7FF0000000000000
)、NaN
(0x7FF0000000000001
など)、NegInf
(0xFFF0000000000000
) は、IEEE 754標準で定義された倍精度浮動小数点数の特定のビットパターンを表します。
- Go言語の標準ライブラリの一部関数は、パフォーマンス最適化のために特定アーキテクチャ向けのアセンブリ言語で実装されています。
技術的詳細
このコミットの技術的詳細は、主に math
パッケージの Dim
、Max
、Min
関数の特殊ケース処理の改善と、それに対応するテストの追加、そして amd64
アセンブリ実装の最適化にあります。
-
src/pkg/math/dim.go
の変更:Dim
、Max
、Min
関数のGo言語実装に、特殊ケースの振る舞いを明確にするコメントが追加されました。これは、関数の仕様を明確にし、開発者が期待する結果を理解する上で非常に重要です。Max
およびMin
関数には、Inf
、NaN
、±0
の特殊ケースを処理するためのswitch
ステートメントが追加されました。Max
の場合:x
またはy
が+Inf
であれば+Inf
を返す。x
またはy
がNaN
であればNaN
を返す。x
とy
が両方ともゼロの場合、Signbit(x)
に基づいて-0
を優先する(Max(+0, -0)
は+0
、Max(-0, -0)
は-0
)。
Min
の場合:x
またはy
が-Inf
であれば-Inf
を返す。x
またはy
がNaN
であればNaN
を返す。x
とy
が両方ともゼロの場合、Signbit(x)
に基づいて-0
を優先する(Min(-0, +0)
は-0
、Min(+0, +0)
は+0
)。
Dim
関数は、Max(x-y, 0)
を呼び出すように変更されました。これにより、Dim
の特殊ケース処理はMax
関数に委ねられる形になります。
-
src/pkg/math/dim_amd64.s
の変更:amd64
アーキテクチャ向けのアセンブリ実装において、Dim
、Max
、Min
関数の特殊ケース処理が追加・修正されました。Dim
:(+Inf, +Inf)
および(-Inf, -Inf)
のケースでNaN
を返す処理が追加されました。NaN
が入力に含まれる場合にNaN
を返す処理が追加されました。これは、浮動小数点数のビットパターンを直接比較することで実現されています。
Max
:+Inf
が入力に含まれる場合に+Inf
を返す処理が追加されました。NaN
が入力に含まれる場合にNaN
を返す処理が追加されました。±0
の特殊ケース(Max(+0, ±0)
は+0
、Max(-0, -0)
は-0
)を処理するためのロジックが追加されました。これは、符号ビットをチェックし、適切なゼロを返すことで実現されています。
Min
:-Inf
が入力に含まれる場合に-Inf
を返す処理が追加されました。NaN
が入力に含まれる場合にNaN
を返す処理が追加されました。±0
の特殊ケース(Min(-0, ±0)
は-0
、Min(+0, +0)
は+0
)を処理するためのロジックが追加されました。
-
src/pkg/math/all_test.go
の変更:Dim
、Max
、Min
関数の特殊ケースの振る舞いを検証するための新しいテストケースが大量に追加されました。vffdimSC
という二次元配列が導入され、Inf
、NaN
、±0
を含む様々な入力ペアが定義されています。fdimSC
、fmaxSC
、fminSC
という配列には、vffdimSC
の入力に対する期待されるDim
、Max
、Min
の結果が格納されています。TestDim
、TestMax
、TestMin
関数内で、これらの新しいテストデータセットを使用して、関数の結果が期待値と一致するかどうかがalike
関数(浮動小数点数の比較を適切に行うヘルパー関数)を用いて検証されています。
-
src/pkg/math/sin.go
の変更:Sin
関数のコメント内の "Special conditions are:" が "Special cases are:" に修正されました。これは、用語の統一と正確性を目的とした軽微な修正です。
これらの変更は、Go言語の math
パッケージがIEEE 754標準に厳密に準拠し、浮動小数点数の特殊なケースに対しても予測可能で正しい振る舞いを保証するための重要なステップです。特に、アセンブリレベルでの最適化は、これらの特殊ケース処理がパフォーマンスに影響を与えないようにするために不可欠です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
src/pkg/math/all_test.go
:vffdimSC
、fdimSC
、fmaxSC
、fminSC
という新しいテストデータ配列が追加されました。TestDim
、TestMax
、TestMin
関数内に、これらの新しいテストデータを用いたループが追加され、特殊ケースの振る舞いを検証しています。
-
src/pkg/math/dim.go
:Dim
、Max
、Min
関数のGo言語実装に、特殊ケースの振る舞いを説明する詳細なコメントが追加されました。Max
およびMin
関数に、Inf
、NaN
、±0
を処理するためのswitch
ステートメントによるロジックが追加されました。Dim
関数がMax(x-y, 0)
を呼び出すように変更されました。
-
src/pkg/math/dim_amd64.s
:Dim
、Max
、Min
関数のamd64
アセンブリ実装に、Inf
、NaN
、±0
の特殊ケースを効率的に処理するための分岐ロジックとビットパターン比較が追加されました。具体的には、PosInf
,NaN
,NegInf
の定数が定義され、これらを用いて入力値が特殊ケースであるかを判定しています。
-
src/pkg/math/sin.go
:Sin
関数のコメント行// Special conditions are:
が// Special cases are:
に変更されました。
コアとなるコードの解説
src/pkg/math/dim.go
Max
関数の変更を例に取ります。
func Max(x, y float64) float64 {
// TODO(rsc): Remove manual inlining of IsNaN, IsInf
// when compiler does it for us
// special cases
switch {
case x > MaxFloat64 || y > MaxFloat64: // IsInf(x, 1) || IsInf(y, 1):
return Inf(1)
case x != x || y != y: // IsNaN(x) || IsNaN(y):
return NaN()
case x == 0 && x == y:
if Signbit(x) {
return y
}
return x
}
if x > y {
return x
}
return y
}
- コメントの追加:
Max
関数の上に、+Inf
、NaN
、±0
の特殊ケースの振る舞いが明確に記述されています。これは、関数の契約を明確にするための重要な文書化です。 switch
ステートメントによる特殊ケース処理:case x > MaxFloat64 || y > MaxFloat64:
: これはIsInf(x, 1) || IsInf(y, 1)
と同等で、x
またはy
のいずれかが正の無限大 (+Inf
) であるかをチェックします。もしそうであれば、+Inf
を返します。MaxFloat64
はfloat64
で表現できる最大の有限値であり、これより大きい値は無限大として扱われます。case x != x || y != y:
: これはIsNaN(x) || IsNaN(y)
と同等で、x
またはy
のいずれかがNaN
であるかをチェックします。NaN
は自分自身と等しくないという特性 (NaN == NaN
はfalse
) を利用しています。もしそうであれば、NaN
を返します。case x == 0 && x == y:
: これは、両方の入力がゼロである場合の処理です。if Signbit(x)
:x
が負のゼロ (-0
) であるかをチェックします。- もし
x
が-0
であれば、y
を返します。これにより、Max(-0, -0)
は-0
を返し、Max(+0, -0)
は+0
を返すというIEEE 754の規則に準拠します。
- 通常の比較: 特殊ケースに該当しない場合、通常の
x > y
の比較が行われ、大きい方の値が返されます。
Min
関数も同様のロジックで、負の無限大 (-Inf
) や負のゼロ (-0
) の特殊ケースを優先的に処理するように変更されています。
src/pkg/math/dim_amd64.s
Max
関数のアセンブリコードの変更を例に取ります。
TEXT ·Max(SB),7,$0
// +Inf special cases
MOVQ $PosInf, AX
MOVQ x+0(FP), R8
CMPQ AX, R8
JEQ isPosInf
MOVQ y+8(FP), R9
CMPQ AX, R9
JEQ isPosInf
// NaN special cases
MOVQ $~(1<<63), DX // bit mask
MOVQ $NaN, AX
MOVQ R8, BX
ANDQ DX, BX // x = |x|
CMPQ AX, BX
JLE isMaxNaN
MOVQ R9, CX
ANDQ DX, CX // y = |y|
CMPQ AX, CX
JLE isMaxNaN
// ±0 special cases
ORQ CX, BX
JEQ isMaxZero
MOVQ R8, X0
MOVQ R9, X1
MAXSD X1, X0
MOVSD X0, r+16(FP)
RET
isMaxNaN: // return NaN
isPosInf: // return +Inf
MOVQ AX, r+16(FP)
RET
isMaxZero:
MOVQ $(1<<63), AX // -0.0
CMPQ AX, R8
JEQ +3(PC)
MOVQ R8, r+16(FP) // return 0
RET
MOVQ R9, r+16(FP) // return other 0
RET
PosInf
定義:#define PosInf 0x7FF0000000000000
は、倍精度浮動小数点数の+Inf
のビットパターンを定義しています。+Inf
特殊ケース:- 入力
x
またはy
がPosInf
のビットパターンと一致するかをCMPQ
とJEQ
でチェックします。 - 一致すれば
isPosInf
ラベルにジャンプし、+Inf
を返します。
- 入力
NaN
特殊ケース:NaN
のビットパターンは複数存在するため、NaN
のビットパターン範囲をチェックします。0x7FF0000000000001
は一般的なNaN
のビットパターンの一つです。MOVQ ~(1<<63), DX
は、符号ビットを除くすべてのビットをマスクするための値 (0x7FFFFFFFFFFFFFFF
) を作成します。ANDQ DX, BX
で入力値の絶対値(符号ビットをクリアした値)を取得し、NaN
のビットパターンと比較します。JLE isMaxNaN
で、NaN
の範囲内であればisMaxNaN
ラベルにジャンプし、NaN
を返します。
±0
特殊ケース:ORQ CX, BX
で両方の入力がゼロであるかを効率的にチェックします。JEQ isMaxZero
で、両方がゼロであればisMaxZero
ラベルにジャンプします。isMaxZero
内では、-0.0
のビットパターン (0x8000000000000000
、または(1<<63)
) とx
を比較し、x
が-0
であればy
を返し、そうでなければx
を返すことで、Max(-0, -0)
は-0
、Max(+0, -0)
は+0
となるように調整しています。
- 通常の
MAXSD
命令: 特殊ケースに該当しない場合、MAXSD
命令(SSE2命令セットの浮動小数点最大値命令)を使用して、x
とy
のうち大きい方を効率的に計算します。
Min
関数も同様に、-Inf
や NaN
、±0
の特殊ケースをアセンブリレベルで効率的に処理するように実装されています。
これらの変更は、Go言語の math
パッケージが、IEEE 754標準に準拠した正確で堅牢な浮動小数点演算を提供するための、細部にわたる配慮と最適化を示しています。
関連リンク
- Go言語の
math
パッケージドキュメント: https://pkg.go.dev/math - Go言語の
math/bits
パッケージドキュメント (浮動小数点数のビット操作に関連): https://pkg.go.dev/math/bits - Go言語の
src/pkg/math/dim.go
ソースコード (現在のバージョン): https://github.com/golang/go/blob/master/src/math/dim.go - Go言語の
src/pkg/math/dim_amd64.s
ソースコード (現在のバージョン): https://github.com/golang/go/blob/master/src/math/dim_amd64.s
参考にした情報源リンク
- 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%95%B0
- IEEE Standard for Floating-Point Arithmetic (IEEE 754-2008): (通常は有料ですが、概要は多くの技術記事で解説されています)
- 浮動小数点数の特殊な値 (NaN, Inf, ±0):
- What Every Computer Scientist Should Know About Floating-Point Arithmetic: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html (非常に詳細な解説)
- Stack Overflow や各種プログラミングブログでの解説記事
- Go言語のアセンブリ:
- Go Assembly Language (公式ドキュメント): https://go.dev/doc/asm
- Goのソースコード内の
.s
ファイルのコメントや、Goのコンパイラに関する技術ブログ記事。
- Change List (CL) 5437137:
- Go Gerrit Code Review: https://go.googlesource.com/go/+/5437137 (コミットメッセージに記載されているCLへのリンク)
- このリンクは、コミットがGoのGerritコードレビューシステムでどのようにレビューされたか、関連する議論、および最終的な変更内容を確認するために非常に有用です。
- ただし、このCLは非常に古いため、直接アクセスしてもリダイレクトされる可能性があります。GoのGitリポジトリのコミット履歴から辿るのが確実です。
- Go Gerrit Code Review: https://go.googlesource.com/go/+/5437137 (コミットメッセージに記載されているCLへのリンク)