big.Float

2025-06-01

通常のfloat64(倍精度浮動小数点数)は、IEEE 754規格に基づき、約15〜17桁の10進数の精度を持ちます。しかし、科学計算、金融計算、あるいは非常に高い精度が要求されるアルゴリズムなどでは、この精度では不十分な場合があります。このような場合にbig.Floatが役立ちます。

big.Floatの主な特徴と使い方を以下に説明します。

big.Floatの主な特徴

  1. 任意精度 (Arbitrary Precision): big.Floatは、必要に応じて精度(仮数部のビット数)を自由に設定できます。これにより、float64では表現できない非常に高い精度の計算が可能です。
  2. 誤差の制御: 計算の過程で発生する丸め誤差をより詳細に制御できます。これは、金融計算など、わずかな誤差も許されない場合に非常に重要です。
  3. 正確な演算: 四則演算(加算、減算、乗算、除算)や平方根などの数学関数を、指定された精度で正確に実行します。
  4. ポインタ型: big.Floatは値型ではなくポインタ型 (*big.Float) として扱われます。これは、大きな数値データを効率的に扱うためです。新しいbig.Floatを作成したり、値を設定したりする際には、new(big.Float)big.NewFloat()のようにポインタで操作します。
  5. モードと精度:
    • 精度 (Precision): 仮数部のビット数を指します。この値が大きいほど、より多くの有効桁数を表現できます。SetPrec()メソッドで設定します。
    • 丸めモード (RoundingMode): 計算結果をどのように丸めるかを指定します。例えば、ToNearestEven(最近接偶数への丸め)、AwayFromZero(ゼロから遠い方への丸め)、ToZero(ゼロ方向への丸め)などがあります。SetMode()メソッドで設定します。

big.Floatの基本的な使い方

  1. big.Floatの作成:

    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	// 新しいbig.Floatを作成 (初期値は0.0, 精度はデフォルト)
    	f1 := new(big.Float)
    	fmt.Println("f1:", f1) // f1: +0.0e+0
    
    	// 初期値を指定して作成 (float64から変換、精度は53ビット)
    	f2 := big.NewFloat(3.1415926535)
    	fmt.Println("f2:", f2) // f2: 3.1415926535
    
    	// 文字列から作成
    	f3, ok := new(big.Float).SetString("0.12345678901234567890123456789")
    	if !ok {
    		fmt.Println("文字列のパースに失敗しました")
    	}
    	fmt.Println("f3:", f3) // f3: 0.12345678901234567890123456789
    }
    

    big.NewFloat()float64から変換する場合、float64自体の精度制限があるため、元のfloat64がすでに丸められた値である可能性がある点に注意が必要です。正確な値が必要な場合は、文字列からSetString()で設定するのが最も安全です。

  2. 精度の設定:

    f := new(big.Float).SetPrec(128) // 128ビットの精度を設定
    fmt.Println("f (prec 128):", f) // f (prec 128): +0.0e+0
    

    精度は、計算を行う前に設定する必要があります。また、計算結果を受け取るbig.Float変数の精度と丸めモードが、その計算に適用されます。

  3. 計算の実行: big.Floatの演算は、メソッドチェーンのように記述されます。結果はレシーバー (z) に格納されます。

    a := big.NewFloat(1.0).SetPrec(100) // 精度100ビットで1.0
    b := big.NewFloat(3.0).SetPrec(100) // 精度100ビットで3.0
    
    // z = a / b
    z := new(big.Float).SetPrec(100).Quo(a, b) // 1/3 を計算
    fmt.Println("1/3 (prec 100):", z) // 1/3 (prec 100): 0.33333333333333333333333333333333333333333333333333
    

    Add, Sub, Mul, Quo (除算) などのメソッドが用意されています。

  4. 値の取得: Float64(), Int64(), String()などのメソッドで、他の型に変換したり、文字列として取得したりできます。

    f := big.NewFloat(0.125)
    f64, _ := f.Float64()
    fmt.Println("f64:", f64) // f64: 0.125
    
    // 大きな数値を文字列で出力
    largeNum, _ := new(big.Float).SetString("12345678901234567890.1234567890")
    fmt.Println("largeNum string:", largeNum.String()) // largeNum string: 1.2345678901234567890123456789e+19
    

big.Floatを使うべきケース

  • 任意精度の数値が必要なアルゴリズム: 特定のアルゴリズムが高精度な浮動小数点数を要求する場合。
  • 暗号学: 非常に大きな数値を扱う必要がある場合。
  • 科学技術計算: 物理シミュレーションや数値解析などで、高精度な計算が不可欠な場合。
  • 金融アプリケーション: 金額計算で小数点以下の誤差が許されない場合。
  • NaN (Not a Number): big.FloatはIEEE 754のNaNを直接サポートしていません。代わりに、NaNが発生するような演算(例:0/0)はErrNaNというpanicを引き起こします。
  • メモリ使用量: 精度を高く設定するほど、より多くのメモリを使用します。
  • パフォーマンス: big.Floatは通常のfloat64に比べて計算コストが高くなります。これは、メモリ管理や複雑なアルゴリズムが必要になるためです。そのため、高精度が本当に必要な場合にのみ使用を検討すべきです。


float64からの初期化による精度の喪失

エラーの原因: big.Floatを使用する目的は高精度な計算ですが、float64型のリテラルや変数からbig.Floatを初期化すると、その時点でfloat64の精度限界による丸め誤差が導入されてしまいます。 big.NewFloat(3.14)someFloat64Value.SetFloat64()を使用した場合、既にfloat64の53ビット(約15〜17桁)の精度に制限された値がbig.Floatに渡されるため、高精度な計算のメリットが失われます。

トラブルシューティング:

  • big.Ratを経由する: 分数で表現できる場合は、big.Rat(有理数)を一度経由してbig.Floatに変換する方法も検討できます。
  • 文字列からの初期化: 最も正確な方法です。高精度な数値を扱う場合は、数値リテラルを直接float64として書くのではなく、文字列としてSetString()メソッドで初期化します。
    // 悪い例: float64の精度で丸められる
    f1 := big.NewFloat(0.1) // 0.1 は float64 で正確に表現できない
    fmt.Println(f1.Text('f', 50)) // 0.10000000000000000555111512312578270211815834045410...
    
    // 良い例: 文字列から初期化し、指定した精度で表現される
    f2, _ := new(big.Float).SetString("0.1")
    f2.SetPrec(100) // 必要に応じて精度を設定
    fmt.Println(f2.Text('f', 50)) // 0.10000000000000000000000000000000000000000000000000...
    

精度の設定忘れまたは不適切な設定

エラーの原因: big.Floatの精度は、デフォルトではfloat64と同じ53ビットです。計算の途中で十分な精度が設定されていないと、期待する高精度な結果が得られません。また、計算を行うbig.Floatインスタンスと、結果を格納するbig.Floatインスタンスの両方で適切な精度が設定されているか確認が必要です。

トラブルシューティング:

  • 結果の精度: 演算の結果を格納するbig.Floatも、十分な精度を持つように初期化または設定する必要があります。そうしないと、計算結果が高精度であっても、格納時に丸められてしまいます。
  • SetPrec()の利用: big.NewFloat()SetString()で初期化した後、すぐにSetPrec()で必要な精度を設定します。必要な精度は、計算の性質や最終的に必要な有効桁数によって異なります。一般的に、10進数1桁あたり約3.32ビットが必要です(log_2(10)approx3.32)。
    // 精度を明示的に設定する
    f := new(big.Float).SetPrec(256) // 例えば256ビットの精度
    f.SetString("1.0")
    g := new(big.Float).SetPrec(256)
    g.SetString("3.0")
    
    result := new(big.Float).SetPrec(256).Quo(f, g)
    fmt.Println(result.Text('f', 70)) // 0.3333333333333333333333333333333333333333333333333333333333333333333333
    

ポインタの取り扱いミス

エラーの原因: big.Floatはポインタ型 (*big.Float) として扱われます。Goの他の組み込み型のように直接値をコピーする感覚で変数に代入すると、予期しない動作をする可能性があります。

f1 := big.NewFloat(1.0)
f2 := f1 // f2 は f1 と同じメモリを指す

f2.Add(f2, big.NewFloat(1.0)) // f2 (つまり f1 も) が 2.0 になる

fmt.Println("f1:", f1) // f1: 2.0
fmt.Println("f2:", f2) // f2: 2.0

トラブルシューティング:

  • 各演算のレシーバー: Add(), Sub(), Mul(), Quo()などのメソッドは、結果をレシーバー(メソッドを呼び出す側のbig.Floatインスタンス)に格納します。これを理解していないと、意図しない値の上書きや、不要な一時オブジェクトの生成につながることがあります。
    a := big.NewFloat(1.0)
    b := big.NewFloat(2.0)
    c := big.NewFloat(3.0)
    
    // d = a + b + c を計算したい場合
    // 悪い例: 各ステップで新しいbig.Floatを作成しないと、前の値が上書きされる可能性がある
    // temp := new(big.Float).Add(a, b)
    // result := new(big.Float).Add(temp, c)
    
    // 良い例: 結果を格納する変数を明示的に用意し、連鎖的にメソッドを呼び出す
    result := new(big.Float)
    result.Add(a, b).Add(result, c) // (a+b) の結果が result に入り、次に result+c が result に入る
    fmt.Println(result) // 6.0
    
  • Set()メソッドでコピー: 別のbig.Floatインスタンスに値をコピーしたい場合は、Set()メソッドを使用します。
    f1 := big.NewFloat(1.0)
    f2 := new(big.Float).Set(f1) // f2 は f1 の値をコピーした新しいインスタンス
    
    f2.Add(f2, big.NewFloat(1.0))
    
    fmt.Println("f1:", f1) // f1: 1.0
    fmt.Println("f2:", f2) // f2: 2.0
    

ゼロ除算 (Division by zero) や未定義演算

エラーの原因: big.Floatの除算 (Quo) でゼロ除算が発生した場合、Goのランタイムパニック(panic)が発生します。通常のfloat64ではInf(無限大)やNaN(非数)が生成されますが、big.Floatはデフォルトではパニックを発生させます。

トラブルシューティング:

  • ErrNaNのチェック: 0/0のような結果が未定義となる演算の場合、big.FloatErrNaNを返します。これはパニックとして扱われるため、通常は事前にチェックするか、リカバリーメカニズムを考慮する必要があります。
  • SetInf()による無限大の表現: 必要に応じて、big.Float.SetInf(true) (正の無限大) または big.Float.SetInf(false) (負の無限大) を使用して無限大を明示的に表現できます。big.FloatにはIsInf()メソッドもあります。
  • big.Floatのゼロチェック: 除算を行う前に、除数となるbig.Floatがゼロでないことを確認します。Cmp(big.NewFloat(0.0)) == 0でゼロかどうかをチェックできます。
    divisor := big.NewFloat(0.0)
    numerator := big.NewFloat(1.0)
    
    if divisor.Cmp(big.NewFloat(0.0)) == 0 {
    	fmt.Println("Error: Division by zero!")
    	// エラーハンドリング(例: エラーを返す、特定の値を設定する)
    } else {
    	result := new(big.Float).Quo(numerator, divisor)
    	fmt.Println(result)
    }
    

Cmp()メソッドと等価性比較

エラーの原因: big.Floatはポインタ型であるため、f1 == f2のように直接比較すると、値ではなくアドレス(ポインタが指す場所)が比較されてしまいます。これにより、同じ値を保持していても比較がfalseになることがあります。

トラブルシューティング:

  • Cmp()メソッドの使用: big.Floatの値の比較には、必ずCmp()メソッドを使用します。
    • x.Cmp(y)は、x < yなら -1、x == yなら 0、x > yなら 1 を返します。 <!-- end list -->
    f1 := big.NewFloat(0.1).SetPrec(64)
    f2, _ := new(big.Float).SetString("0.1")
    f2.SetPrec(64)
    
    if f1.Cmp(f2) == 0 {
    	fmt.Println("f1 と f2 は等しい")
    } else {
    	fmt.Println("f1 と f2 は異なる")
    }
    
    (注: 上記の例で0.1float64リテラルで初期化すると、既に丸め誤差が含まれているため、SetString("0.1")で初期化したものとは異なる値になる可能性があります。この点も考慮が必要です。)

エラー/問題の原因: big.Floatは高精度計算のために設計されているため、通常のfloat64と比較してはるかに多くの計算リソース(CPU、メモリ)を消費します。不必要に高い精度を設定したり、大量のbig.Float演算をループ内で実行したりすると、アプリケーションのパフォーマンスが著しく低下する可能性があります。

トラブルシューティング:

  • 代替手段の検討: 高精度が絶対的に必要でない場合は、float64の利用や、固定小数点数(github.com/shopspring/decimalなどのパッケージ)の利用も検討します。固定小数点数は、金融計算などで丸め誤差を厳密に制御したい場合に有効な選択肢となります。
  • プロファイリング: パフォーマンスが問題となる場合は、Goのプロファイリングツール(pprofなど)を使用して、big.Float関連の処理がボトルネックになっているかどうかを確認します。
  • 必要な精度を見極める: 常に最大精度を設定するのではなく、アプリケーションが必要とする最小限の精度を設定するようにします。

big.Floatは、Goで高精度な浮動小数点計算を行うための強力な機能ですが、その特性を理解して適切に使用することが重要です。特に、初期化時の精度の喪失、精度の設定、ポインタの取り扱い、そしてパフォーマンスの考慮は、トラブルシューティングの際に念頭に置くべき主要な点です。



big.Floatの基本的な初期化と精度の設定

最も基本的なbig.Floatの作成方法と、重要な「精度」の設定方法です。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("--- 基本的な初期化と精度の設定 ---")

	// 1. デフォルト精度で初期化 (float64と同じ53ビット)
	f1 := new(big.Float) // 値は0.0
	fmt.Printf("f1 (デフォルト精度): %s, 精度: %dビット\n", f1.String(), f1.Prec())

	// 2. big.NewFloat() で float64 から初期化 (精度はデフォルトまたは指定)
	// 注意: この時点でfloat64の精度限界による丸めが発生する可能性あり
	f2 := big.NewFloat(0.12345678901234567) // float64リテラル
	fmt.Printf("f2 (float64から): %s, 精度: %dビット\n", f2.String(), f2.Prec())

	// 3. 文字列から初期化 (推奨される方法)
	// 高精度な値を失わずに設定できる
	f3, ok := new(big.Float).SetString("0.12345678901234567890123456789")
	if !ok {
		fmt.Println("f3: 文字列のパースに失敗しました")
	}
	fmt.Printf("f3 (文字列から): %s, 精度: %dビット\n", f3.String(), f3.Prec())

	// 4. 明示的な精度設定
	// 必要に応じて精度を高く設定する
	highPrecFloat := new(big.Float).SetPrec(128) // 128ビット (約38桁の10進数精度)
	highPrecFloat.SetString("1.0") // 値を設定
	fmt.Printf("highPrecFloat (128ビット精度): %s, 精度: %dビット\n", highPrecFloat.String(), highPrecFloat.Prec())

	// 精度は計算結果を受け取る側の big.Float に影響する
	pi := new(big.Float).SetPrec(53).SetString("3.14159265358979323846264338327950288419716939937510")
	fmt.Printf("Pi (53ビット精度): %.20f\n", pi) // 53ビット精度で丸められる

	piHighPrec := new(big.Float).SetPrec(100).SetString("3.14159265358979323846264338327950288419716939937510")
	fmt.Printf("Pi (100ビット精度): %.50f\n", piHighPrec) // 100ビット精度なのでより多くの桁が表示される
}

四則演算の例

big.Floatの基本的な四則演算(加算、減算、乗算、除算)の例です。結果はレシーバーに格納されます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 四則演算の例 ---")

	a := new(big.Float).SetPrec(100).SetString("10.0")
	b := new(big.Float).SetPrec(100).SetString("3.0")

	// 加算: result = a + b
	sum := new(big.Float).SetPrec(100).Add(a, b)
	fmt.Printf("%s + %s = %s\n", a.String(), b.String(), sum.String())

	// 減算: result = a - b
	diff := new(big.Float).SetPrec(100).Sub(a, b)
	fmt.Printf("%s - %s = %s\n", a.String(), b.String(), diff.String())

	// 乗算: result = a * b
	prod := new(big.Float).SetPrec(100).Mul(a, b)
	fmt.Printf("%s * %s = %s\n", a.String(), b.String(), prod.String())

	// 除算: result = a / b
	// 10/3 を高精度で計算
	quotient := new(big.Float).SetPrec(100).Quo(a, b)
	fmt.Printf("%s / %s = %.50f\n", a.String(), b.String(), quotient) // 50桁まで表示
}

比較と無限大/非数の扱い

big.Floatの比較 (Cmp()) や、無限大 (Inf) の扱いについてです。big.FloatNaN(非数)を直接サポートせず、未定義演算でパニックを起こします。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 比較と無限大の扱い ---")

	x := big.NewFloat(1.0).SetPrec(64)
	y := big.NewFloat(2.0).SetPrec(64)
	z := big.NewFloat(1.0).SetPrec(64) // xと同じ値

	// Cmp() を使った比較
	// x < y: -1
	// x == y: 0
	// x > y: 1
	fmt.Printf("%s と %s の比較: %d\n", x.String(), y.String(), x.Cmp(y))
	fmt.Printf("%s と %s の比較: %d\n", x.String(), z.String(), x.Cmp(z))
	fmt.Printf("%s と %s の比較: %d\n", y.String(), x.String(), y.Cmp(x))

	// 無限大の作成とチェック
	posInf := new(big.Float).SetInf(true)  // 正の無限大
	negInf := new(big.Float).SetInf(false) // 負の無限大

	fmt.Printf("正の無限大: %s (IsInf: %t)\n", posInf.String(), posInf.IsInf())
	fmt.Printf("負の無限大: %s (IsInf: %t)\n", negInf.String(), negInf.IsInf())

	// 無限大との比較
	fmt.Printf("%s と %s の比較: %d\n", x.String(), posInf.String(), x.Cmp(posInf)) // x < +Inf => -1
	fmt.Printf("%s と %s の比較: %d\n", negInf.String(), x.String(), negInf.Cmp(x)) // -Inf < x => -1

	// ゼロ除算の注意点 (panicが発生する)
	// divisor := big.NewFloat(0.0)
	// numerator := big.NewFloat(1.0)
	// result := new(big.Float).Quo(numerator, divisor) // ここでpanicが発生
	// fmt.Println(result)

	// ゼロ除算を避けるためのチェック
	divisor := big.NewFloat(0.0)
	if divisor.Cmp(big.NewFloat(0.0)) == 0 {
		fmt.Println("ゼロ除算を検知しました。計算をスキップします。")
	} else {
		// 計算処理
	}
}

より複雑な数学関数の利用(math/bigパッケージ外との連携)

math/bigパッケージ自体は基本的な四則演算と平方根(Sqrt)しか提供していません。より複雑な数学関数(sin, cos, expなど)をbig.Floatで計算したい場合は、通常、以下のようなアプローチを取ります。

  • 外部ライブラリの利用: 高精度な数学関数を提供するサードパーティライブラリを探します。(例: github.com/ncw/gmp など、gmpライブラリのGoバインディングを使うと、さらに多くの機能が利用できますが、C言語ライブラリへの依存が発生します)
  • テーラー展開などによる自作: 必要な精度で級数展開などを実装します。

ここでは、Sqrtの例と、自作でできることのヒントを示します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 数学関数の例 (Sqrt) ---")

	val := new(big.Float).SetPrec(100).SetString("2.0") // 2の平方根を計算
	sqrt2 := new(big.Float).SetPrec(100).Sqrt(val)

	fmt.Printf("2の平方根 (高精度): %.50f\n", sqrt2)

	// 検証: sqrt2 * sqrt2 が 2 に近いことを確認
	check := new(big.Float).SetPrec(100).Mul(sqrt2, sqrt2)
	fmt.Printf("検証 (sqrt2 * sqrt2): %.50f\n", check)

	// 高精度なPiを使った円の計算
	// Piを非常に高精度で定義
	piHighPrec, _ := new(big.Float).SetPrec(200).SetString("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679")
	radius := new(big.Float).SetPrec(200).SetString("1.234567890123456789")

	// 面積 = Pi * r^2
	area := new(big.Float).SetPrec(200).Mul(radius, radius).Mul(piHighPrec, new(big.Float).SetPrec(200).Mul(radius, radius))
	fmt.Printf("半径 %s の円の面積: %.100f\n", radius.String(), area)

	// その他の関数(例: 指数関数 exp(x))
	// これは big.Float に直接は実装されていないため、自分で実装するか外部ライブラリを使う必要がある
	// 例: exp(x) = 1 + x/1! + x^2/2! + x^3/3! + ...
	// func ExpBigFloat(x *big.Float, prec uint) *big.Float { ... }
}

big.Ratは分数を正確に表現できる型です。big.Floatbig.Ratから変換したり、big.Ratに変換したりできます。これは、循環小数などの正確な表現が必要な場合に役立ちます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- big.Rat との連携 ---")

	// big.Rat を作成: 1/3
	oneThirdRat := new(big.Rat).SetFrac64(1, 3)
	fmt.Printf("有理数 1/3: %s\n", oneThirdRat.String())

	// big.Rat を big.Float に変換
	// 精度は変換先の big.Float に依存する
	oneThirdFloat := new(big.Float).SetPrec(100).SetRat(oneThirdRat)
	fmt.Printf("big.Float (1/3, 100ビット精度): %.50f\n", oneThirdFloat)

	// big.Float を big.Rat に変換
	// big.Float は近似値なので、正確な有理数に変換できない場合がある
	// その場合、分母が非常に大きな数になる
	piFloat, _ := new(big.Float).SetPrec(64).SetString("3.1415926535")
	piRat := new(big.Rat).SetFloat64(0) // ダミーの初期値

	// big.Float を big.Rat に変換 (誤差を考慮した近似)
	piRat.SetF(piFloat)
	fmt.Printf("big.Float から変換した big.Rat (Pi近似): %s\n", piRat.String())

	// 高精度な big.Float から big.Rat への変換を試みると、分母が非常に大きくなる
	highPrecFloat, _ := new(big.Float).SetPrec(200).SetString("0.12345678901234567890123456789012345")
	ratFromHighPrec := new(big.Rat)
	ratFromHighPrec.SetF(highPrecFloat)
	fmt.Printf("高精度 big.Float から変換した big.Rat: %s\n", ratFromHighPrec.String())
}


float64 (標準の倍精度浮動小数点数)

最も一般的でパフォーマンスに優れる選択肢です。

  • 適切なユースケース:
    • ほとんどの科学計算、グラフィックス、ゲームなど、厳密な精度よりも速度が重視される場合。
    • 精度の要件が比較的緩い場合。
  • デメリット:
    • 精度限界: IEEE 754倍精度浮動小数点数の仕様上、約15〜17桁の10進数精度しか保証されません。これを超える精度が必要な場合、丸め誤差が蓄積します。
    • 浮動小数点数の特性: 0.1などの一部の10進数が正確に表現できないため、金融計算などで誤差が問題となることがあります。
  • メリット:
    • 高速: Go言語のハードウェアサポートを最大限に活用するため、計算速度が非常に速いです。
    • 低メモリ使用量: 固定の64ビット(8バイト)で表現されるため、メモリ効率が良いです。
    • シンプル: 組み込み型であり、特別なパッケージのインポートや複雑な初期化は不要です。

math/big.Rat (有理数)

big.Ratは、N/Dという形式の分数で数値を表現します。これにより、循環小数を含むすべての有理数を正確に表現・計算できます。

  • 適切なユースケース:
    • 通貨計算や会計システムなど、厳密な正確性が絶対に要求される金融アプリケーション。
    • 分数計算が自然な数学的問題。
    • 丸め誤差が許されないアルゴリズム。
  • デメリット:
    • パフォーマンス: big.Floatと同様に、float64に比べて計算が遅く、メモリ使用量も大きくなる可能性があります。特に分母と分子が巨大になった場合、その影響は顕著です。
    • 非有理数: 2​やπのような無理数を正確に表現できません。これらを扱う場合は、big.Floatに変換するか、特定のアルゴリズムで近似する必要があります。
    • 複雑性: 浮動小数点数に慣れている開発者には、分数を扱うロジックが直感的に理解しにくい場合があります。
  • メリット:
    • 無限精度: 有理数で表現できる限り、計算結果に丸め誤差は一切含まれません。
    • 正確性: 金融計算や、数値的な正確性が最優先される場合に理想的です。

固定小数点数ライブラリ

固定小数点数とは、小数点以下の桁数をあらかじめ固定しておくことで、浮動小数点数のような指数部を持たずに数値を表現する方法です。Go言語の標準ライブラリには含まれていませんが、サードパーティ製のライブラリがいくつか存在します。

  • 適切なユースケース:
    • 金融、会計、ECサイトでの金額計算など、10進数の正確性が要求されるが、無限精度までは不要な場合。
    • 浮動小数点数の誤差を避けたいが、big.Ratほど複雑な分数計算は不要な場合。
  • デメリット:
    • 精度限界: 設定した小数点以下の桁数を超える精度は表現できません。
    • オーバーフロー/アンダーフロー: 表現可能な値の範囲が限定されるため、計算結果が範囲外になる可能性があります。
    • ライブラリ依存: 外部ライブラリを導入する必要があります。
  • メリット:
    • 10進数での正確性: 特に通貨計算で問題となる0.1のような10進数の丸め誤差を回避できます。内部的には整数として扱われるため、浮動小数点数の不正確さがありません。
    • 制御可能な精度: 小数点以下の桁数をプログラマが明示的に設定できます。
    • big.Floatより高速な場合がある: 実装にもよりますが、特定の計算パターンではbig.Floatよりも効率が良いことがあります。
  • : github.com/shopspring/decimal

特定の非常に特殊な高精度計算が必要な場合、あるいは既存のライブラリが要件を満たさない場合、独自の数値型やアルゴリズムを実装することも考えられます。

  • 適切なユースケース:
    • 学術研究、新しい数値アルゴリズムの開発、非常にニッチな領域での特殊な計算など、既存のソリューションでは対応できない場合に限られます。
  • デメリット:
    • 開発コスト: ゼロから実装するため、開発時間と労力が非常に大きくなります。
    • バグのリスク: 数値計算は非常に複雑で、正確性の保証が難しくなります。
    • メンテナンス: 将来的な機能追加やバグ修正も自力で行う必要があります。
  • メリット:
    • 完全な制御: 特定のアルゴリズムやデータ構造に最適化された実装が可能です。
    • ニッチな要件に対応: 既存のライブラリでは対応できないような、非常に特殊な精度やパフォーマンスの要件に対応できます。

big.FloatはGoで任意精度の浮動小数点数計算を行うための標準的な方法ですが、代替手段は多数存在します。

方法精度パフォーマンスメモリ使用量特徴ユースケース
float64限定的(15-17桁)最速最小組み込み型一般的な科学計算、グラフィックス
math/big.Rat無限遅い大きい有理数を正確に表現金融計算、正確な分数計算
固定小数点数ライブラリ制御可能中程度中程度10進数の正確性金融計算、会計、ECサイトの金額計算
独自の数値型/アルゴリズム完全な制御可変可変特殊な要件に最適化非常にニッチな研究・開発