[インデックス 10761] ファイルの概要
このコミットは、Go言語の標準ライブラリである math
パッケージ内の Hypot
関数に関するものです。math
パッケージは、浮動小数点数に対する基本的な数学関数を提供しており、Hypot(x, y)
関数は、直角三角形の斜辺の長さを計算する、すなわち sqrt(x*x + y*y)
を計算する機能を提供します。この関数は、数値計算において、引数の値が非常に大きい場合や小さい場合に発生しうるオーバーフローやアンダーフローの問題を避けるように設計されていることが一般的です。
コミット
このコミットは、math
パッケージ内に存在していた2つの Hypot
実装のうち、Sqrt
関数に基づかない(そして不正確であった)実装を削除し、より正確な Sqrt
関数に基づく実装に統一することを目的としています。これにより、特にARMアーキテクチャにおけるビルドの問題が修正されました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2572803899485e6d07490ae04b1eb5aa5c758699
元コミット内容
math: delete non-Sqrt-based Hypot
I was confused by the existence of two portable Hypot
routines in the tree when I cleaned things up, and I made
ARM use the wrong (imprecise) one. Use the right one,
and delete the wrong one.
Fixes arm build.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5485065
変更の背景
Go言語の math
パッケージには、Hypot
関数を計算するための2つの異なる内部ルーチンが存在していました。一つは Sqrt
関数(平方根)を内部的に利用する hypotSqrt
であり、もう一つは Sqrt
を利用しない hypotNoSqrt
でした。コミットメッセージによると、コミッターであるRuss Cox氏は、これらの2つのポータブルな Hypot
ルーチンが共存していることに混乱し、クリーンアップの過程でARMアーキテクチャが誤って「不正確な」hypotNoSqrt
を使用するように設定してしまったと述べています。
Hypot
関数の計算は、単純に sqrt(x*x + y*y)
とすると、x
や y
が非常に大きい場合に x*x
や y*y
が浮動小数点数の最大値を超えてオーバーフローしたり、逆に非常に小さい場合にアンダーフローして精度が失われたりする可能性があります。これを避けるために、通常は max(|x|, |y|) * sqrt(1 + (min(|x|, |y|) / max(|x|, |y|))^2)
のようなスケーリングされた形式で計算されます。このスケーリングされた形式は Sqrt
関数に依存します。
hypotNoSqrt
は、おそらく Sqrt
関数が利用できない、あるいは非常に遅い環境のために、反復計算や級数展開などを用いて Hypot
を近似的に計算しようとしたものと推測されます。しかし、このような実装は、特定の条件下で精度が劣る("imprecise")か、あるいは計算コストが高い可能性があります。
このコミットの目的は、不正確な hypotNoSqrt
を削除し、より正確で標準的な Sqrt
ベースの Hypot
実装に統一することで、コードベースの簡素化と、特にARMアーキテクチャにおける計算の正確性を保証することでした。結果として、ARMビルドの問題も解決されました。
前提知識の解説
Hypot関数 (Hypotenuse Function)
Hypot(x, y)
は、数学的には sqrt(x^2 + y^2)
を計算する関数です。これは、直角三角形の2つの直角を挟む辺の長さ x
と y
から斜辺の長さを求める際に使用されます。また、2次元平面上の点 (x, y)
と原点 (0, 0)
との間のユークリッド距離を求める際にも用いられます。
浮動小数点数の精度とオーバーフロー/アンダーフロー
コンピュータにおける浮動小数点数(float32
, float64
など)は、限られたビット数で実数を表現するため、常に精度に限界があります。特に、非常に大きな数や非常に小さな数を扱う際には、以下の問題が発生する可能性があります。
- オーバーフロー (Overflow): 計算結果がそのデータ型で表現できる最大値を超えてしまうこと。例えば、
x
が非常に大きい場合、x*x
はfloat64
の最大値 (MaxFloat64
) を超えて+Inf
(無限大) になってしまうことがあります。 - アンダーフロー (Underflow): 計算結果がそのデータ型で表現できる最小の非ゼロ値よりも小さくなってしまい、ゼロとして扱われること。例えば、
x
が非常に小さい場合、x*x
はゼロとして扱われてしまい、精度が失われることがあります。
Hypot(x, y)
を単純に sqrt(x*x + y*y)
と計算すると、x
や y
が大きい場合に x*x
や y*y
がオーバーフローし、結果が Inf
になってしまう可能性があります。また、x
や y
が小さい場合に x*x
や y*y
がアンダーフローし、結果がゼロに近づきすぎて精度が失われる可能性があります。
Hypotの安定した実装戦略
これらの問題を回避するため、Hypot
関数は通常、以下のようなスケーリングされた形式で実装されます。
a = abs(x)
,b = abs(y)
とする。a
とb
のうち大きい方をp
、小さい方をq
とする(p = max(a, b)
,q = min(a, b)
)。- もし
p
がゼロであれば、結果はゼロ。 - そうでなければ、
result = p * sqrt(1 + (q/p)^2)
を計算する。
この方法では、q/p
は常に1以下になるため、(q/p)^2
は1以下となり、1 + (q/p)^2
は2以下となります。これにより、中間計算でのオーバーフローやアンダーフローが効果的に回避され、より広い範囲の入力値に対して正確な結果が得られます。この実装は Sqrt
関数に依存します。
ARMアーキテクチャ
ARM (Advanced RISC Machine) は、組み込みシステム、モバイルデバイス、サーバーなど、幅広いデバイスで使用されているRISC (Reduced Instruction Set Computer) ベースのプロセッサアーキテクチャです。ARMプロセッサは、その電力効率の高さと性能のバランスから広く採用されています。
特定のアーキテクチャ(この場合はARM)が、なぜ特定の Hypot
実装を使用するように設定されていたのかは、そのアーキテクチャの浮動小数点演算ユニット (FPU) の特性や、特定の命令の性能特性に起因する可能性があります。例えば、過去にはハードウェアによる sqrt
命令が遅い、あるいは存在しない環境のために、ソフトウェアによる代替実装が検討されることがありました。しかし、現代のARMプロセッサは通常、効率的なFPUと sqrt
命令を備えています。
技術的詳細
このコミットの核心は、src/pkg/math/hypot.go
ファイル内の2つの Hypot
実装、hypotSqrt
と hypotNoSqrt
のうち、hypotNoSqrt
を削除し、hypotSqrt
を hypot
にリネームして標準の実装とすることです。
-
hypotSqrt
(リネーム後hypot
): この関数は、前述の安定した実装戦略、すなわちp * Sqrt(1+q*q)
の形式でHypot
を計算します。func hypot(p, q float64) float64 { // ... (特殊ケースのハンドリング: NaN, Inf) if p < 0 { p = -p } if q < 0 { q = -q } if p < q { p, q = q, p // pを大きい方、qを小さい方にする } if p == 0 { return 0 } q = q / p // q/p を計算 return p * Sqrt(1+q*q) // p * sqrt(1 + (q/p)^2) }
この実装は、浮動小数点数のオーバーフロー/アンダーフローを効果的に回避し、高い精度を維持します。
-
hypotNoSqrt
(削除された関数): この関数は、Sqrt
関数を使用せずにHypot
を計算しようとするものでした。コミット前のコードを見ると、これは反復的な計算を行っていました。具体的なアルゴリズムはコードから読み取れますが、おそらくはテイラー級数展開やニュートン法のような数値解析的手法を用いてsqrt(1+x)
を近似的に計算し、それをp
に掛けることでHypot
を求めていたと考えられます。func hypotNoSqrt(p, q float64) float64 { // ... (特殊ケースのハンドリング: NaN, Inf) // ... (p, q の絶対値を取り、pを大きい方、qを小さい方にする) if p == 0 { return 0 } pfac := p q = q / p r := q p = 1 for { r = r * r s := r + 4 if s == 4 { // rが非常に小さい場合、sは4に収束 return p * pfac } r = r / s p = p + 2*r*p q = q * r r = q / p } // panic("unreachable") }
この反復計算は、特定の条件下で収束が遅い、あるいは丸め誤差が蓄積しやすいなど、数値的な不安定性や「不正確さ」を抱えていた可能性があります。コミットメッセージの「imprecise」という言葉は、この数値的な特性を指していると考えられます。
ARMアーキテクチャがこの hypotNoSqrt
を使用するように設定されていたため、ARMビルドで問題が発生していました。これは、hypotNoSqrt
の不正確さが、特定のテストケースや実運用環境で許容できない誤差を生じさせていたか、あるいは単にパフォーマンスが劣っていたためかもしれません。このコミットにより、ARMも他のアーキテクチャと同様に、より正確で安定した Sqrt
ベースの Hypot
実装を使用するようになりました。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルに集中しています。
-
src/pkg/math/all_test.go
:TestHypotSqrtGo
とTestHypotNoSqrtGo
という2つのテスト関数が削除され、TestHypotGo
という単一のテスト関数に統合されました。- 同様に、ベンチマーク関数
BenchmarkHypotNoSqrtGo
とBenchmarkHypotSqrtGo
が削除され、BenchmarkHypotGo
に統合されました。 - これにより、
Hypot
関数のテストが、単一の正確な実装に対して行われるようになりました。
-
src/pkg/math/export_test.go
:- テストのために内部関数をエクスポートするファイルです。
HypotSqrtGo
とHypotNoSqrtGo
のエクスポートが削除され、代わりにHypotGo
がエクスポートされるようになりました。これは、内部実装がhypot
に統一されたことを反映しています。
-
src/pkg/math/hypot.go
:hypotNoSqrt
関数が完全に削除されました。hypotSqrt
関数がhypot
にリネームされました。これにより、Hypot
関数のGo言語によるデフォルト実装がSqrt
ベースのものに統一されました。
-
src/pkg/math/hypot_arm.s
:- ARMアーキテクチャ向けのアセンブリ言語による
Hypot
関数の実装(またはラッパー)です。 - 以前は
hypotNoSqrt
を呼び出すように設定されていましたが、このコミットによりhypot
を呼び出すように変更されました。 - 具体的には、
B ·hypotNoSqrt(SB)
がB ·hypot(SB)
に変更されています。これは、ARMアーキテクチャがGoの標準的なSqrt
ベースのHypot
実装を使用するようになったことを意味します。
- ARMアーキテクチャ向けのアセンブリ言語による
コアとなるコードの解説
このコミットの目的は、math.Hypot
関数の実装を簡素化し、正確性を向上させることです。
-
hypotNoSqrt
の削除:Sqrt
を使用しないHypot
の実装は、おそらく特定の環境でのパフォーマンスや、Sqrt
が利用できない場合のフォールバックとして存在していたと考えられます。しかし、コミットメッセージが示唆するように、この実装は「不正確」であり、現代のGoの実行環境ではSqrt
が効率的に利用できるため、その存在意義が薄れていました。不正確な実装を削除することで、コードベースの複雑性が減り、将来的なメンテナンスが容易になります。 -
hypotSqrt
からhypot
へのリネーム:hypotSqrt
は、Hypot
関数の安定した計算方法(p * Sqrt(1 + (q/p)^2)
)を実装していました。これをhypot
にリネームすることで、この実装がmath.Hypot
の標準的かつ唯一のGo言語による実装であることを明確にしています。 -
hypot_arm.s
の変更: ARMアーキテクチャがhypotNoSqrt
を使用していたことが、このコミットの直接的なトリガーの一つでした。ARMアセンブリコードがhypotNoSqrt
からhypot
へと呼び出し先を変更したことで、ARM環境でも正確なSqrt
ベースのHypot
計算が保証されるようになりました。これにより、「Fixes arm build」というコミットメッセージの目的が達成されました。これは、ARMビルドがhypotNoSqrt
の不正確さに起因するテスト失敗や誤動作を起こしていた可能性を示唆しています。
全体として、このコミットは、Goの math
パッケージにおける Hypot
関数の実装を、より堅牢で正確な単一のバージョンに統一し、特定のアーキテクチャにおける潜在的な問題を解決する、クリーンアップと品質向上を目的とした変更です。
関連リンク
- Go言語の
math
パッケージのドキュメント: https://pkg.go.dev/math - Go言語の
math.Hypot
関数のドキュメント: https://pkg.go.dev/math#Hypot - このコミットのGo Gerrit Code Reviewへのリンク: https://golang.org/cl/5485065
参考にした情報源リンク
- IEEE 754 浮動小数点数標準に関する情報 (浮動小数点数の精度、オーバーフロー、アンダーフローの概念理解のため)
Hypot
関数の数値的に安定した実装に関する情報 (例: Wikipedia, 数値計算の教科書)- ARMアーキテクチャの浮動小数点演算に関する一般的な情報
- Go言語のソースコードとコミット履歴 (Goの内部実装の理解のため)