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

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

このコミットは、Go言語の math パッケージにおける DimMaxMin 関数について、浮動小数点数の特殊なケース(無限大 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 パッケージの DimMaxMin 関数における特殊ケースの振る舞いを文書化する。

  • 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) = NaNDim(-Inf, -Inf) = NaNDim(NaN, anything) = NaN
  • また、Sin 関数のコメント内の "conditions" を "cases" に変更する(前回の変更で修正し忘れたもの)。

変更の背景

浮動小数点演算は、通常の数値だけでなく、特殊な値(無限大 Inf、非数 NaN、符号付きゼロ ±0)を扱う際に、その振る舞いが厳密にIEEE 754標準によって定義されています。Go言語の math パッケージは、これらの標準に準拠した正確な浮動小数点演算を提供することを目指しています。

このコミットの背景には、DimMaxMin といった基本的な数学関数が、これらの特殊な入力値に対してどのような結果を返すのかを明確にし、その振る舞いをコードのコメントとして文書化する必要性がありました。これにより、開発者がこれらの関数を使用する際に、予期せぬ結果に遭遇することなく、関数の挙動を正確に理解できるようになります。

特に、MaxMin のような比較関数では、NaN が含まれる場合の比較結果や、+0-0 の比較における符号の扱いなど、直感に反する可能性のある挙動が存在します。これらの特殊ケースを明示的に定義し、テストで検証することで、関数の堅牢性と信頼性を向上させることが目的です。また、amd64 アーキテクチャ固有のアセンブリ実装においても、これらの特殊ケースが効率的かつ正確に処理されるように修正が加えられています。

前提知識の解説

このコミットを理解するためには、以下の浮動小数点数に関する前提知識が不可欠です。

  1. IEEE 754 浮動小数点標準:

    • 現代のほとんどのコンピュータシステムで採用されている浮動小数点数の表現と演算に関する国際標準です。
    • 単精度 (float32)倍精度 (float64) があり、Go言語の float64 は倍精度に相当します。
    • この標準は、通常の数値だけでなく、以下の特殊な値を定義しています。
      • 無限大 (Infinity, Inf): 正の無限大 (+Inf) と負の無限大 (-Inf) があります。例えば、1.0 / 0.0+Inf になります。
      • 非数 (Not a Number, NaN): 不定な結果(例: 0.0 / 0.0Inf - InfInf * 0sqrt(-1))を表します。NaN は、いかなる値(NaN 自身を含む)とも比較しても false になります(NaN == NaNfalse)。
      • 符号付きゼロ (±0): 正のゼロ (+0) と負のゼロ (-0) があります。これらは数値的には等しい (+0 == -0true) ですが、一部の演算(例: 1.0 / +0+Inf1.0 / -0-Inf)で異なる結果を生じることがあります。
  2. math パッケージの関数:

    • Go言語の標準ライブラリ math パッケージは、基本的な数学関数を提供します。
    • Dim(x, y float64) float64: max(x-y, 0) を計算します。つまり、xy より大きい場合は x-y を、そうでない場合は 0 を返します。
    • Max(x, y float64) float64: xy のうち大きい方を返します。
    • Min(x, y float64) float64: xy のうち小さい方を返します。
    • IsInf(f float64, sign int) bool: f が無限大であるかどうかをチェックします。sign1 なら +Inf-1 なら -Inf0 ならどちらかの無限大をチェックします。
    • IsNaN(f float64) bool: fNaN であるかどうかをチェックします。
    • Signbit(x float64) bool: x の符号ビットがセットされている(つまり負の数であるか、負のゼロであるか)場合に true を返します。
  3. Go言語のアセンブリ (.s ファイル):

    • Go言語の標準ライブラリの一部関数は、パフォーマンス最適化のために特定アーキテクチャ向けのアセンブリ言語で実装されています。src/pkg/math/dim_amd64.s は、amd64 アーキテクチャにおける DimMaxMin 関数のアセンブリ実装です。
    • アセンブリコードでは、浮動小数点レジスタ(例: X0, X1)や命令(例: MOVSD, SUBSD, MAXSD, MINSD)が直接使用されます。
    • 特殊ケースの処理は、ビットパターンを直接比較したり、条件分岐(例: JEQ, JNE, JLE)を用いて実装されます。
    • PosInf (0x7FF0000000000000)、NaN (0x7FF0000000000001 など)、NegInf (0xFFF0000000000000) は、IEEE 754標準で定義された倍精度浮動小数点数の特定のビットパターンを表します。

技術的詳細

このコミットの技術的詳細は、主に math パッケージの DimMaxMin 関数の特殊ケース処理の改善と、それに対応するテストの追加、そして amd64 アセンブリ実装の最適化にあります。

  1. src/pkg/math/dim.go の変更:

    • DimMaxMin 関数のGo言語実装に、特殊ケースの振る舞いを明確にするコメントが追加されました。これは、関数の仕様を明確にし、開発者が期待する結果を理解する上で非常に重要です。
    • Max および Min 関数には、InfNaN±0 の特殊ケースを処理するための switch ステートメントが追加されました。
      • Max の場合:
        • x または y+Inf であれば +Inf を返す。
        • x または yNaN であれば NaN を返す。
        • xy が両方ともゼロの場合、Signbit(x) に基づいて -0 を優先する(Max(+0, -0)+0Max(-0, -0)-0)。
      • Min の場合:
        • x または y-Inf であれば -Inf を返す。
        • x または yNaN であれば NaN を返す。
        • xy が両方ともゼロの場合、Signbit(x) に基づいて -0 を優先する(Min(-0, +0)-0Min(+0, +0)+0)。
    • Dim 関数は、Max(x-y, 0) を呼び出すように変更されました。これにより、Dim の特殊ケース処理は Max 関数に委ねられる形になります。
  2. src/pkg/math/dim_amd64.s の変更:

    • amd64 アーキテクチャ向けのアセンブリ実装において、DimMaxMin 関数の特殊ケース処理が追加・修正されました。
    • Dim:
      • (+Inf, +Inf) および (-Inf, -Inf) のケースで NaN を返す処理が追加されました。
      • NaN が入力に含まれる場合に NaN を返す処理が追加されました。これは、浮動小数点数のビットパターンを直接比較することで実現されています。
    • Max:
      • +Inf が入力に含まれる場合に +Inf を返す処理が追加されました。
      • NaN が入力に含まれる場合に NaN を返す処理が追加されました。
      • ±0 の特殊ケース(Max(+0, ±0)+0Max(-0, -0)-0)を処理するためのロジックが追加されました。これは、符号ビットをチェックし、適切なゼロを返すことで実現されています。
    • Min:
      • -Inf が入力に含まれる場合に -Inf を返す処理が追加されました。
      • NaN が入力に含まれる場合に NaN を返す処理が追加されました。
      • ±0 の特殊ケース(Min(-0, ±0)-0Min(+0, +0)+0)を処理するためのロジックが追加されました。
  3. src/pkg/math/all_test.go の変更:

    • DimMaxMin 関数の特殊ケースの振る舞いを検証するための新しいテストケースが大量に追加されました。
    • vffdimSC という二次元配列が導入され、InfNaN±0 を含む様々な入力ペアが定義されています。
    • fdimSCfmaxSCfminSC という配列には、vffdimSC の入力に対する期待される DimMaxMin の結果が格納されています。
    • TestDimTestMaxTestMin 関数内で、これらの新しいテストデータセットを使用して、関数の結果が期待値と一致するかどうかが alike 関数(浮動小数点数の比較を適切に行うヘルパー関数)を用いて検証されています。
  4. src/pkg/math/sin.go の変更:

    • Sin 関数のコメント内の "Special conditions are:" が "Special cases are:" に修正されました。これは、用語の統一と正確性を目的とした軽微な修正です。

これらの変更は、Go言語の math パッケージがIEEE 754標準に厳密に準拠し、浮動小数点数の特殊なケースに対しても予測可能で正しい振る舞いを保証するための重要なステップです。特に、アセンブリレベルでの最適化は、これらの特殊ケース処理がパフォーマンスに影響を与えないようにするために不可欠です。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/math/all_test.go:

    • vffdimSCfdimSCfmaxSCfminSC という新しいテストデータ配列が追加されました。
    • TestDimTestMaxTestMin 関数内に、これらの新しいテストデータを用いたループが追加され、特殊ケースの振る舞いを検証しています。
  2. src/pkg/math/dim.go:

    • DimMaxMin 関数のGo言語実装に、特殊ケースの振る舞いを説明する詳細なコメントが追加されました。
    • Max および Min 関数に、InfNaN±0 を処理するための switch ステートメントによるロジックが追加されました。
    • Dim 関数が Max(x-y, 0) を呼び出すように変更されました。
  3. src/pkg/math/dim_amd64.s:

    • DimMaxMin 関数の amd64 アセンブリ実装に、InfNaN±0 の特殊ケースを効率的に処理するための分岐ロジックとビットパターン比較が追加されました。具体的には、PosInf, NaN, NegInf の定数が定義され、これらを用いて入力値が特殊ケースであるかを判定しています。
  4. 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 関数の上に、+InfNaN±0 の特殊ケースの振る舞いが明確に記述されています。これは、関数の契約を明確にするための重要な文書化です。
  • switch ステートメントによる特殊ケース処理:
    • case x > MaxFloat64 || y > MaxFloat64:: これは IsInf(x, 1) || IsInf(y, 1) と同等で、x または y のいずれかが正の無限大 (+Inf) であるかをチェックします。もしそうであれば、+Inf を返します。MaxFloat64float64 で表現できる最大の有限値であり、これより大きい値は無限大として扱われます。
    • case x != x || y != y:: これは IsNaN(x) || IsNaN(y) と同等で、x または y のいずれかが NaN であるかをチェックします。NaN は自分自身と等しくないという特性 (NaN == NaNfalse) を利用しています。もしそうであれば、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 または yPosInf のビットパターンと一致するかを CMPQJEQ でチェックします。
    • 一致すれば 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)-0Max(+0, -0)+0 となるように調整しています。
  • 通常の MAXSD 命令: 特殊ケースに該当しない場合、MAXSD 命令(SSE2命令セットの浮動小数点最大値命令)を使用して、xy のうち大きい方を効率的に計算します。

Min 関数も同様に、-InfNaN±0 の特殊ケースをアセンブリレベルで効率的に処理するように実装されています。

これらの変更は、Go言語の math パッケージが、IEEE 754標準に準拠した正確で堅牢な浮動小数点演算を提供するための、細部にわたる配慮と最適化を示しています。

関連リンク

参考にした情報源リンク

  • IEEE 754 浮動小数点標準:
  • 浮動小数点数の特殊な値 (NaN, Inf, ±0):
  • 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リポジトリのコミット履歴から辿るのが確実です。