[インデックス 10156] ファイルの概要
このコミットは、Go言語のmath
パッケージにおけるSin
(サイン)およびCos
(コサイン)関数の精度向上を目的としています。特に、既存の実装が抱えていた精度に関する問題を解決し、より広範な入力値に対して正確な結果を返すように改善されています。
コミット
commit 739c442e42312ee843f4736d10f73f9c7d292226
Author: Charles L. Dorian <cldorian@gmail.com>
Date: Mon Oct 31 14:26:05 2011 -0400
math: Improved accuracy for Sin and Cos.
Fixes #1564.
R=rsc, dchest
CC=golang-dev
https://golang.org/cl/5320056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/739c442e42312ee843f4736d10f73f9c7d292226
元コミット内容
このコミットの元のメッセージは以下の通りです。
math: Improved accuracy for Sin and Cos.
Fixes #1564.
R=rsc, dchest
CC=golang-dev
https://golang.org/cl/5320056
これは、math
パッケージのSin
およびCos
関数の精度が改善されたことを示しています。Fixes #1564
は、GoのIssueトラッカーで報告されていたIssue 1564を修正したことを意味します。
変更の背景
この変更の背景には、Go言語のmath
パッケージにおける三角関数(特にサインとコサイン)の計算精度に関する問題がありました。Issue #1564で報告されたように、既存の実装では特定の入力値に対して期待される精度が得られないケースが存在していました。特に、大きな数値や特定の境界値において、浮動小数点演算の性質上、誤差が蓄積しやすくなることが課題でした。
このような数値計算の精度問題は、科学技術計算、グラフィックス、物理シミュレーションなど、正確な浮動小数点演算が求められる多くのアプリケーションにおいて致命的な影響を与える可能性があります。そのため、標準ライブラリとして提供されるmath
パッケージの関数は、可能な限り高い精度とロバスト性を持つことが求められます。
このコミットは、より信頼性の高い数値計算を提供するために、既存のアルゴリズムをより高精度なものに置き換えることで、この課題を解決しようとしたものです。
前提知識の解説
浮動小数点数と精度
コンピュータにおける浮動小数点数は、実数を近似的に表現するための形式です。IEEE 754規格が広く用いられており、Go言語のfloat64
型もこれに準拠しています。浮動小数点数は、有限のビット数で表現されるため、全ての実数を正確に表現することはできません。このため、計算には常に「丸め誤差」が伴います。
三角関数のような超越関数を計算する場合、テイラー展開やチェビシェフ近似などの多項式近似が用いられます。これらの近似は、ある範囲内で関数を多項式で近似することで計算を効率化しますが、近似の次数や係数の選択、そして入力値の範囲(定義域)によって精度が大きく左右されます。特に、入力値が大きくなると、周期関数の性質上、引数を適切な範囲に「還元」する処理が必要になりますが、この還元処理自体にも誤差が伴い、最終的な結果の精度に影響を与えます。
範囲還元 (Range Reduction)
三角関数は周期関数であるため、任意の入力値x
に対して、x
を2π
の倍数で割った余り(主値)を計算し、その主値に対して関数を評価することで、計算の範囲を限定することができます。このプロセスを「範囲還元」と呼びます。
例えば、sin(x)
を計算する際に、x
が非常に大きな値であっても、x = 2nπ + θ
(ここで0 <= θ < 2π
)と表現できるため、sin(x) = sin(θ)
として計算できます。しかし、x
が非常に大きい場合、x
から2nπ
を引く際に、x
と2nπ
の有効桁数が大きく異なるため、減算によって有効桁数が失われ、結果としてθ
の精度が著しく低下する可能性があります。これを「桁落ち」と呼びます。
高精度な範囲還元を実現するためには、π
の値を非常に高い精度で保持し、多倍長演算のような手法を用いて桁落ちを防ぐ必要があります。
Cephes Math Library
Cephes Math Libraryは、Stephen L. Moshierによって開発された、C言語で書かれた数値計算ライブラリです。このライブラリは、様々な特殊関数や超越関数(三角関数、指数関数、対数関数、ガンマ関数など)の高精度な実装を提供しており、その堅牢性と精度で知られています。多くの科学技術計算ソフトウェアやライブラリで、そのアルゴリズムや実装が参考にされています。
このコミットでは、Sin
およびCos
関数の実装において、Cephes Math Libraryのアルゴリズムと係数が採用されています。これは、Cephesライブラリが提供する高精度な計算手法を取り入れることで、Goのmath
パッケージの精度を向上させることを意図しています。
技術的詳細
このコミットの主要な変更点は、src/pkg/math/sin.go
ファイルにおけるSin
およびCos
関数の実装が、従来のHart & Cheneyの係数を用いたものから、Cephes Math Libraryのアルゴリズムと係数を用いたものへと全面的に刷新されたことです。
従来のsinus
関数(削除されたコード)
変更前のsinus
関数は、x
を2/Pi
で乗算して正規化し、Modf
関数を使って整数部と小数部に分け、象限(quad)に基づいて符号と値を調整していました。そして、P
とQ
という係数を用いた有理関数近似(多項式の比)によって最終的な値を計算していました。
func sinus(x float64, quad int) float64 {
const (
P0 = .1357884097877375669092680e8
// ...
Q0 = .8644558652922534429915149e7
// ...
)
// ... 範囲還元と象限の調整 ...
yy := y * y
temp1 := ((((P4*yy+P3)*yy+P2)*yy+P1)*yy + P0) * y
temp2 := ((((yy+Q3)*yy+Q2)*yy+Q1)*yy + Q0)
return temp1 / temp2
}
この実装は、特定の範囲では機能しますが、特に大きな入力値に対する範囲還元において精度が問題となる可能性がありました。
新しいSin
およびCos
関数の実装(追加されたコード)
新しい実装は、Cephes Math Libraryの設計思想に基づいています。
-
特殊ケースのハンドリング:
NaN
やInf
といった特殊な浮動小数点値に対する挙動が明示的に定義されています。switch { case x != x || x < -MaxFloat64 || x > MaxFloat64: // IsNaN(x) || IsInf(x, 0): return NaN() }
Sin
関数ではx == 0
の場合の±0
のハンドリングも追加されています。 -
引数の正数化と符号の保存: 入力
x
が負の場合、絶対値を取り、最終的な結果の符号を決定するためにsign
フラグを立てます。sign := false if x < 0 { x = -x sign = true // Sinの場合 // Cosの場合、xが負でもCos(x) = Cos(-x)なので、符号は変わらない }
-
高精度な範囲還元: ここが最も重要な変更点です。従来の
2/Pi
による単純な正規化ではなく、PI4A
,PI4B
,PI4C
という3つの定数に分割されたπ/4
と、M4PI
(4/π
)を用いて、拡張精度(extended precision)でのモジュラ演算を行います。const ( PI4A = 7.85398125648498535156E-1 // 0x3fe921fb40000000, Pi/4 split into three parts PI4B = 3.77489470793079817668E-8 // 0x3e64442d00000000, PI4C = 2.69515142907905952645E-15 // 0x3ce8469898cc5170, M4PI = 1.273239544735162542821171882678754627704620361328125 // 4/pi ) j := int64(x * M4PI) // integer part of x/(Pi/4) y := float64(j) // integer part as float // map zeros to origin if j&1 == 1 { j += 1 y += 1 } j &= 7 // octant modulo 2Pi radians (360 degrees) // reflect in x axis (Sinの場合) if j > 3 { sign = !sign j -= 4 } // Cosの場合の象限調整 if j > 3 { j -= 4 sign = !sign } if j > 1 { sign = !sign } z := ((x - y*PI4A) - y*PI4B) - y*PI4C // Extended precision modular arithmetic zz := z * z
この
z := ((x - y*PI4A) - y*PI4B) - y*PI4C
という計算は、x
からy * (PI4A + PI4B + PI4C)
を引く操作を、複数の浮動小数点数で段階的に行うことで、桁落ちによる精度損失を最小限に抑えるためのテクニックです。PI4A
,PI4B
,PI4C
はπ/4
を多倍長精度で表現したものであり、これによりx
をπ/4
の倍数で割った余りz
を非常に高い精度で計算できます。 -
多項式近似: 還元された
z
とzz
(z*z
)を用いて、定義域[0, π/4]
におけるサインまたはコサインの多項式近似を計算します。// sin coefficients var _sin = [...]float64{ 1.58962301576546568060E-10, // ... // ... } // cos coefficients var _cos = [...]float64{ -1.13585365213876817300E-11, // ... // ... } if j == 1 || j == 2 { // Cos(z)を計算する象限 y = 1.0 - 0.5*zz + zz*zz*((((((_cos[0]*zz)+_cos[1])*zz+_cos[2])*zz+_cos[3])*zz+_cos[4])*zz+_cos[5]) } else { // Sin(z)を計算する象限 y = z + z*zz*((((((_sin[0]*zz)+_sin[1])*zz+_sin[2])*zz+_sin[3])*zz+_sin[4])*zz+_sin[5]) }
_sin
と_cos
は、Cephes Math Libraryから導出された多項式近似の係数です。j
の値(象限)に応じて、sin(z)
またはcos(z)
のどちらかの多項式を評価します。これは、sin(x)
とcos(x)
がπ/2
の位相差を持つことを利用し、常に[0, π/4]
の範囲で最も収束の良い多項式近似を使用するためです。例えば、sin(x)
を計算する際にx
がπ/4
からπ/2
の範囲にある場合、cos(π/2 - x)
としてコサインの多項式で計算する方が精度が良い、といった最適化が行われています。 -
最終的な符号の適用: 最初に保存した
sign
フラグに基づいて、最終結果の符号を調整します。
テストコードの変更
src/pkg/math/all_test.go
では、TestCos
, TestSin
, TestSincos
関数において、結果の比較に使用されるアサーション関数がclose
からveryclose
に変更されています。
--- a/src/pkg/math/all_test.go
+++ b/src/pkg/math/all_test.go
@@ -1709,7 +1709,7 @@ func TestCopysign(t *testing.T) {
func TestCos(t *testing.T) {
for i := 0; i < len(vf); i++ {
- if f := Cos(vf[i]); !close(cos[i], f) {
+ if f := Cos(vf[i]); !veryclose(cos[i], f) {
t.Errorf("Cos(%g) = %g, want %g", vf[i], f, cos[i])
}
}
@@ -2192,7 +2192,7 @@ func TestSignbit(t *testing.T) {
}
func TestSin(t *testing.T) {
for i := 0; i < len(vf); i++ {
- if f := Sin(vf[i]); !close(sin[i], f) {
+ if f := Sin(vf[i]); !veryclose(sin[i], f) {
t.Errorf("Sin(%g) = %g, want %g", vf[i], f, sin[i])
}
}
@@ -2205,7 +2205,7 @@ func TestSincos(t *testing.T) {
func TestSincos(t *testing.T) {
for i := 0; i < len(vf); i++ {
- if s, c := Sincos(vf[i]); !close(sin[i], s) || !close(cos[i], c) {
+ if s, c := Sincos(vf[i]); !veryclose(sin[i], s) || !veryclose(cos[i], c) {
t.Errorf("Sincos(%g) = %g, %g want %g, %g", vf[i], s, c, sin[i], cos[i])
}
}
これは、新しい実装がより高精度になったため、テストにおける許容誤差の基準をより厳しくしたことを示唆しています。veryclose
関数は、close
関数よりもさらに厳密な浮動小数点数の比較を行うものと推測されます。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、主にsrc/pkg/math/sin.go
ファイルに集中しています。
-
著作権表示の更新:
// Copyright 2009 The Go Authors. All rights reserved.
から// Copyright 2011 The Go Authors. All rights reserved.
へ変更。 -
sinus
関数の削除: 従来のsinus
関数(Sin
とCos
の両方から呼び出されていた内部ヘルパー関数)が完全に削除されました。 -
Cephes Math Libraryに関するコメントの追加: 新しい
Sin
とCos
関数の実装がCephes Math Libraryに基づいていることを説明する詳細なコメントブロックが追加されました。これには、Cephesライブラリの概要、精度情報、著作権情報などが含まれています。 -
サイン・コサイン係数の定義: Cephesライブラリから導出された多項式近似のための係数配列
_sin
と_cos
がグローバル変数として定義されました。var _sin = [...]float64{ 1.58962301576546568060E-10, // 0x3de5d8fd1fd19ccd -2.50507477628578072866E-8, // 0xbe5ae5e5a9291f5d // ... (合計6つの係数) } var _cos = [...]float64{ -1.13585365213876817300E-11, // 0xbda8fa49a0861a9b 2.08757008419747316778E-9, // 0x3e21ee9d7b4e3f05 // ... (合計6つの係数) }
-
Cos
関数の再実装:Cos
関数が、Cephesのアルゴリズムに基づいて全面的に書き直されました。これには、特殊ケースのハンドリング、高精度な範囲還元、そして_cos
および_sin
係数を用いた多項式近似の適用が含まれます。 -
Sin
関数の再実装:Sin
関数も同様に、Cephesのアルゴリズムに基づいて全面的に書き直されました。Cos
関数と非常に似た構造を持ちますが、符号の扱いと多項式近似の選択(j
の値に基づく)が異なります。
コアとなるコードの解説
src/pkg/math/sin.go
におけるCos
関数とSin
関数の新しい実装
新しいCos
関数とSin
関数は、以下の主要なステップで構成されています。
-
定数の定義:
PI4A
,PI4B
,PI4C
は、π/4
を3つのfloat64
値に分割して表現したものです。これにより、π/4
をより高い精度で扱うことが可能になります。M4PI
は4/π
の近似値です。これらの定数は、高精度な範囲還元のために使用されます。 -
特殊値の処理: 入力
x
がNaN
(非数)やInf
(無限大)の場合、Goのmath.NaN()
を返します。Sin
関数では、x
が±0
の場合に±0
を返すというIEEE 754の規定も満たしています。 -
引数の正規化と符号の決定: 入力
x
の絶対値を取り、元のx
が負であった場合はsign
フラグをtrue
に設定します。Cos
関数は偶関数なので、Cos(-x) = Cos(x)
であるため、Cos
関数ではsign
フラグは最終結果の符号には影響しません。しかし、Sin
関数は奇関数なので、Sin(-x) = -Sin(x)
であるため、sign
フラグが最終結果の符号を決定します。 -
範囲還元(Range Reduction):
j := int64(x * M4PI)
:x
をπ/4
で割ったおおよその整数部分j
を計算します。y := float64(j)
:j
をfloat64
に変換します。if j&1 == 1 { j += 1; y += 1 }
: これは、j
が奇数の場合(つまりx
がπ/4
の奇数倍に近い場合)、j
を偶数に調整することで、後続の計算でz
が0に近くなるようにするための最適化です。これにより、多項式近似の精度が向上します。j &= 7
:j
を8で割った余りを取ります。これは、x
が2π
(8 * π/4
)のどの「八分円」(octant)に属するかを特定するためです。これにより、三角関数の周期性を利用して、計算を[0, 2π]
の範囲にマッピングします。if j > 3 { j -= 4; sign = !sign }
:j
が4以上の場合、x
がπ
より大きいことを意味します。この場合、x
をπ
だけずらしてj
を調整し、結果の符号を反転させます。これは、sin(x + π) = -sin(x)
やcos(x + π) = -cos(x)
といった関係を利用したものです。if j > 1 { sign = !sign }
:Cos
関数にのみ存在するこの行は、j
が2または3の場合(つまりx
がπ/2
からπ
の範囲にある場合)に符号を反転させます。これは、cos(x + π/2) = -sin(x)
やcos(x + π)
の関係を利用したものです。z := ((x - y*PI4A) - y*PI4B) - y*PI4C
: これが「拡張精度モジュラ演算」の核心です。x
からy * (PI4A + PI4B + PI4C)
を引くことで、x
をπ/4
の倍数で割った余りz
を非常に高い精度で計算します。複数のfloat64
値でπ/4
を表現し、段階的に減算を行うことで、通常の浮動小数点減算で発生しやすい桁落ちを防ぎ、z
の精度を保ちます。 -
多項式近似の選択と評価:
zz := z * z
:z
の二乗を計算します。多項式近似は通常、x^2
の項で構成されるため、事前に計算しておきます。if j == 1 || j == 2
: この条件は、還元されたz
が[0, π/4]
の範囲にあり、かつ元のx
がπ/4
からπ/2
の範囲(またはそれに対応する象限)にある場合に真となります。この場合、sin(x)
はcos(π/2 - x)
として、cos(x)
はsin(π/2 - x)
として計算するのが精度上有利です。y = 1.0 - 0.5*zz + zz*zz*((((((_cos[0]*zz)+_cos[1])*zz+_cos[2])*zz+_cos[3])*zz+_cos[4])*zz+_cos[5])
: この式は、cos(z)
のテイラー展開(またはチェビシェフ近似)の多項式を評価しています。係数_cos
が使用されます。else
: 上記の条件が偽の場合、つまり還元されたz
が[0, π/4]
の範囲にあり、かつ元のx
が[0, π/4]
の範囲(またはそれに対応する象限)にある場合です。y = z + z*zz*((((((_sin[0]*zz)+_sin[1])*zz+_sin[2])*zz+_sin[3])*zz+_sin[4])*zz+_sin[5])
: この式は、sin(z)
のテイラー展開(またはチェビシェフ近似)の多項式を評価しています。係数_sin
が使用されます。 -
最終結果の符号適用:
if sign { y = -y }
: 最初に保存したsign
フラグがtrue
であれば、最終結果の符号を反転させます。
この新しい実装は、Cephes Math Libraryの知見を取り入れることで、特に大きな入力値に対する三角関数の計算精度を大幅に向上させています。高精度な範囲還元と、定義域の異なる多項式近似の使い分けが、その精度向上の鍵となっています。
関連リンク
- Go Issue #1564: https://code.google.com/p/go/issues/detail?id=1564 (古いGoのIssueトラッカーのリンクですが、現在はGitHubに移行しています)
- Go CL 5320056: https://golang.org/cl/5320056 (Goのコードレビューシステムにおける変更リスト)
参考にした情報源リンク
- Cephes Math Library: http://netlib.sandia.gov/cephes/
- sin.c from Cephes: http://netlib.sandia.gov/cephes/cmath/sin.c
- cos.c from Cephes: http://netlib.sandia.gov/cephes/cmath/cos.c
- IEEE 754 浮動小数点数: 浮動小数点数の表現と演算に関する国際標準。
- 数値計算における桁落ち: 浮動小数点演算で発生する誤差の一種。
- 多項式近似: 関数を多項式で近似する数学的手法。