【Go言語】big.Float.SetFloat64() 徹底解説:よくある落とし穴と対策

2025-06-01

簡単に言うと、float64 の浮動小数点数を、より高い精度を持つ big.Float 型に変換して設定するために使われます。

big.Floatfloat64 の違い

  • big.Float: math/big パッケージで提供される型で、任意精度の浮動小数点数を扱えます。float64 のような固定されたビット数に制限されず、必要に応じて精度(有効桁数)を設定できるため、非常に高い精度が求められる計算や、浮動小数点誤差を極力避けたい場合に利用されます。

  • float64: Go言語の組み込み型で、IEEE 754 倍精度浮動小数点数(64ビット)で数値を表現します。多くのプログラミング言語で標準的に使われる浮動小数点数ですが、表現できる桁数には限りがあり、非常に大きい数や非常に小さい数、あるいは循環小数などを正確に表現できない場合があります。このため、計算によっては「浮動小数点誤差」と呼ばれるわずかな誤差が生じることがあります。

SetFloat64() の役割

SetFloat64(x float64) *Float メソッドは、レシーバーである *big.Float オブジェクトに、引数 x で与えられた float64 の値を設定します。

重要な点

  • 初期化や値の再設定に便利: big.Float オブジェクトを特定の float64 の値で初期化したり、途中で値を変更したりする際に便利です。
  • float64 の精度で設定される: SetFloat64() は、あくまで入力された float64 の値(すでに float64 の精度で表現されている値)を big.Float に設定します。float64 自体が持つ精度以上の情報が付加されるわけではありません。つまり、もし元の float64 の値にすでに丸め誤差が含まれていれば、その誤差も含めて big.Float に設定されます。

使用例

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// float64 の値
	f64Value := 0.1 + 0.2 // float64 の性質上、正確には 0.3 にならない場合がある

	// big.Float を作成
	bf := new(big.Float)

	// float64 の値を big.Float に設定
	bf.SetFloat64(f64Value)

	fmt.Printf("float64の値: %f\n", f64Value)
	fmt.Printf("big.Floatに設定した値: %s\n", bf.String())

	// 別の float64 の値で再設定
	bf.SetFloat64(1.2345678901234567) // float64 の精度を超える部分もある
	fmt.Printf("再設定後のbig.Floatの値: %s\n", bf.String())

	// SetFloat64() とは異なる初期化方法(文字列からの設定)
	// こちらの方が厳密な精度を維持しやすい
	bfPrecise, _, _ := new(big.Float).SetPrec(100).Parse("0.123456789012345678901234567890123456789", 10)
	fmt.Printf("文字列から設定したbig.Floatの値: %s\n", bfPrecise.String())
}

出力例

float64の値: 0.300000
big.Floatに設定した値: 0.30000000000000004
再設定後のbig.Floatの値: 1.2345678901234567
文字列から設定したbig.Floatの値: 0.123456789012345678901234567890123456789

この例からわかるように、0.1 + 0.2float64 の結果は 0.30000000000000004 のようにわずかな誤差を含みますが、SetFloat64() はその誤差を含んだ値をそのまま big.Float に設定します。

big.Float.SetFloat64() は、既存の float64 の値を math/big パッケージで計算するために big.Float に変換したい場合に利用します。



float64 の精度限界に起因する誤解 (最も一般的)

エラーや誤解の内容
SetFloat64() を使って float64 の値を big.Float に設定すると、まるでbig.Float がその値を「より正確」に表現してくれるかのように誤解しがちです。しかし、SetFloat64() は入力された float64 の値をそのまま big.Float にコピーするだけです。つまり、元の float64 が持っていた精度以上の情報が付加されることはありません。 float64 がすでに丸め誤差を含んでいる場合、その誤差も big.Float に引き継がれます。


package main

import (
	"fmt"
	"math/big"
)

func main() {
	f64Val := 0.1 + 0.2 // float64 の計算結果は正確に 0.3 ではないことが多い
	
	bf := new(big.Float)
	bf.SetFloat64(f64Val)

	fmt.Printf("float64: %f\n", f64Val)
	fmt.Printf("big.Float: %s\n", bf.String())
	// 期待: big.Float: 0.3
	// 実際: big.Float: 0.30000000000000004 (多くの場合)
}

トラブルシューティング/解決策

  • 厳密な精度が必要な場合
    • 文字列からの初期化
      big.Float.SetString() または big.Float.Parse() を使用して、数値を文字列として渡し、その文字列から big.Float を初期化します。これにより、float64 の中間的な精度を介さずに、より高い精度で値を設定できます。
      bfPrecise := new(big.Float).SetPrec(50) // 必要な精度を設定
      bfPrecise.SetString("0.3") // または Parse("0.3", 10)
      fmt.Printf("big.Float (from string): %s\n", bfPrecise.String()) // 0.3
      
    • big.Rat の利用
      有理数(分数)で値を表現できる big.Rat を利用し、そこから big.Float に変換することで、循環小数などを厳密に扱うことができます。
      r := new(big.Rat).SetFrac64(3, 10) // 3/10
      bfFromRat := new(big.Float).SetPrec(50).SetRat(r)
      fmt.Printf("big.Float (from big.Rat): %s\n", bfFromRat.String())
      
  • 根本的な理解
    SetFloat64() は「既存の float64 の値を big.Float の形式に変換する」ものであり、「float64 の精度問題を解決する」ものではないと理解してください。

big.Float の精度設定 (Prec) の影響

エラーや誤解の内容
big.Float は任意精度ですが、デフォルトの精度(約64ビット、float64 相当)で初期化されることが多いです。SetFloat64() を呼び出す前に SetPrec() で高い精度を設定していないと、計算結果が期待通りにならない場合があります。


float64 では表現できない桁数の数値を SetFloat64() で設定しても、事前に精度を設定していなければ、デフォルトの精度で丸められてしまいます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// float64 では表現しきれないほど長い小数
	longFloat64 := 1.234567890123456789 // 18桁目以降は float64 では失われる情報

	bfDefaultPrec := new(big.Float)
	bfDefaultPrec.SetFloat64(longFloat64)
	fmt.Printf("デフォルト精度 big.Float: %s\n", bfDefaultPrec.String()) // 1.2345678901234567 とかになる

	// 事前に精度を設定
	bfHighPrec := new(big.Float).SetPrec(100) // 100ビットの精度を設定
	bfHighPrec.SetFloat64(longFloat64) // ここで設定されるのは float64 の丸められた値
	fmt.Printf("高精度 big.Float (SetFloat64): %s\n", bfHighPrec.String()) // 同上
    
    // 精度を活かすには SetString などを使う
    bfHighPrecFromString := new(big.Float).SetPrec(100)
    bfHighPrecFromString.SetString("1.234567890123456789")
    fmt.Printf("高精度 big.Float (SetString): %s\n", bfHighPrecFromString.String()) // 1.234567890123456789 となる
}

トラブルシューティング/解決策

  • SetFloat64() は入力が float64 である限り限界がある
    繰り返しになりますが、SetFloat64() は入力が float64 であるため、高精度な big.Float に設定しても、元の float64 が持っていなかった情報は得られません。big.Float の高精度を本当に活かすには、文字列からのパース (SetStringParse) や、他の big.Floatbig.Rat からの変換を使用する必要があります。
  • 目的の精度を設定
    SetFloat64() を呼び出す前に、new(big.Float).SetPrec(n) のようにして、目的の精度 n を設定してください。

ポインタレシーバーの理解不足

エラーや誤解の内容
SetFloat64()*big.Float 型のレシーバーを持つメソッドです。これは、メソッドが元の big.Float オブジェクトの値を直接変更することを意味します。しかし、new(big.Float) を呼び出すことを忘れたり、値渡しで関数に渡してしまったりすると、期待通りの動作にならないことがあります。

例 (間違った使い方)

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 間違い: var bf big.Float はゼロ値の big.Float を作成するが、
	// メソッドを呼び出すにはポインタが必要
	// bf.SetFloat64(1.0) // これはコンパイルエラーになる可能性があるか、panicを起こす
    
    // 正しい使い方:
    var bf big.Float
    ptrBf := &bf // ポインタを取得してメソッドを呼び出す
    ptrBf.SetFloat64(1.0)
    fmt.Printf("正しい使い方: %s\n", ptrBf.String())

	// より一般的な正しい使い方
	bf2 := new(big.Float) // new() はポインタを返す
	bf2.SetFloat64(2.0)
	fmt.Printf("一般的な正しい使い方: %s\n", bf2.String())
}

トラブルシューティング/解決策

  • 常にポインタを使用
    big.Float のメソッドを呼び出す際は、new(big.Float) で作成したポインタ、または既存の big.Float のアドレス (&myFloat) を使用してください。

エラーや誤解の内容
big.Float のゼロ値は 0 です。SetFloat64(0.0) を呼び出すと、big.Float はゼロになります。これは当然の動作ですが、他の値で初期化するつもりだったのに誤って 0.0 を設定してしまい、意図しない結果になることがあります。

  • 意図しない初期化の確認
    変数に値を設定する際に、本当に 0.0 を設定したいのか、それとも他の値を設定したいのかを意識してください。


例1: float64 の値を big.Float に設定する基本

最も基本的な使い方です。float64 型の変数に格納された値を big.Float に変換して設定します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// float64 の値
	f64Value := 3.1415926535

	// big.Float を作成
	// new(big.Float) は big.Float のゼロ値へのポインタを返します
	bf := new(big.Float)

	// SetFloat64() を使って float64 の値を big.Float に設定
	bf.SetFloat64(f64Value)

	fmt.Printf("float64 の値: %f\n", f64Value)
	fmt.Printf("big.Float に設定した値: %s\n", bf.String())
	// %s を使うことで big.Float の正確な文字列表現が得られます
}

実行結果例

float64 の値: 3.141593
big.Float に設定した値: 3.1415926535

例2: float64 の精度限界と big.Float の関係

SetFloat64() は、元の float64 が持っていた精度以上の情報を提供するわけではありません。float64 の計算で生じる誤差もそのまま引き継がれます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// float64 で 0.1 + 0.2 を計算すると、わずかな誤差が生じることがあります
	f64Sum := 0.1 + 0.2

	// big.Float を作成
	bf := new(big.Float)

	// SetFloat64() で設定
	bf.SetFloat64(f64Sum)

	fmt.Printf("float64 (0.1 + 0.2) の結果: %f\n", f64Sum)
	fmt.Printf("big.Float に設定した値: %s\n", bf.String())

	// 参考: 厳密な 0.3 を big.Float に設定する方法
	bfPrecise := new(big.Float).SetPrec(50) // 精度を設定
	bfPrecise.SetString("0.3")             // 文字列から設定
	fmt.Printf("big.Float (厳密な0.3): %s\n", bfPrecise.String())
}

実行結果例

float64 (0.1 + 0.2) の結果: 0.300000
big.Float に設定した値: 0.30000000000000004 // float64 の誤差が引き継がれる
big.Float (厳密な0.3): 0.3

この例から、SetFloat64()float64 の値を big.Float に「変換」するものであり、float64 が持つ誤差を「修正」するものではないことがわかります。

例3: big.Float の精度設定と SetFloat64()

big.Float は任意精度ですが、SetFloat64() を使う場合は、設定される値が float64 の精度に依存するため、事前に高い精度を設定しても、float64 が持つ情報量を超える値を表現できるわけではありません。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// float64 で表現しきれない長い小数
	f64Long := 1.234567890123456789 // float64 の精度では、このうちの一部しか保持できない

	// 1. デフォルト精度 (約64ビット) の big.Float
	bfDefault := new(big.Float)
	bfDefault.SetFloat64(f64Long)
	fmt.Printf("デフォルト精度 big.Float (SetFloat64): %s\n", bfDefault.String())

	// 2. 高精度 (100ビット) の big.Float
	bfHighPrec := new(big.Float).SetPrec(100) // 精度を100ビットに設定
	bfHighPrec.SetFloat64(f64Long) // ここで設定されるのは、f64Long の丸められた値
	fmt.Printf("高精度 big.Float (SetFloat64): %s\n", bfHighPrec.String())

	// 3. 高精度を活かすために文字列から設定する場合
	bfHighPrecFromString := new(big.Float).SetPrec(100)
	bfHighPrecFromString.SetString("1.234567890123456789") // 文字列から直接高精度で設定
	fmt.Printf("高精度 big.Float (SetString): %s\n", bfHighPrecFromString.String())
}

実行結果例

デフォルト精度 big.Float (SetFloat64): 1.2345678901234567
高精度 big.Float (SetFloat64): 1.2345678901234567 // float64 の精度で設定される
高精度 big.Float (SetString): 1.234567890123456789 // 文字列から直接設定すると高精度が活かされる

この例は、SetFloat64() が入力の float64 の精度に制限されることを明確に示しています。big.Float の真の任意精度を活用するには、文字列からの初期化 (SetString) や、他の big.Floatbig.Rat からの変換がしばしば必要になります。

途中の計算結果を float64 で受け取り、それを big.Float に変換して後続の高精度計算に利用するようなケースです。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// float64 での初期計算
	f64Result := 10.0 / 3.0 // 3.3333... となるが float64 では丸められる

	fmt.Printf("float64 の初期計算結果: %f\n", f64Result)

	// big.Float に変換して、さらに高精度な計算を続ける
	bf1 := new(big.Float).SetPrec(100) // 高い精度を設定
	bf1.SetFloat64(f64Result)           // float64 の結果を設定

	// big.Float での追加計算 (例: 3を掛ける)
	bf3 := big.NewFloat(3.0) // big.Float の 3 を作成
	bfResult := new(big.Float).SetPrec(100)
	bfResult.Mul(bf1, bf3) // bf1 * bf3 を計算

	fmt.Printf("big.Float に変換後の追加計算結果: %s\n", bfResult.String())

	// 厳密な 10.0 / 3.0 から計算した場合
	bfStrict := new(big.Float).SetPrec(100)
	bfStrict.Quo(big.NewFloat(10.0), big.NewFloat(3.0)) // 10 / 3 を高精度で計算
	bfStrict.Mul(bfStrict, big.NewFloat(3.0))
	fmt.Printf("厳密な 10/3 から計算した場合の結果: %s\n", bfStrict.String())
}

実行結果例

float64 の初期計算結果: 3.333333
big.Float に変換後の追加計算結果: 9.999999999999999000000000000000000000000000000000000000000000000000000000000000000000000000000000000 // float64 の誤差が引き継がれる
厳密な 10/3 から計算した場合の結果: 10.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 // 厳密な計算

この例は、SetFloat64()float64 から big.Float への「ゲートウェイ」として機能することを示していますが、一度 float64 の精度に落ちた情報は、big.Float に変換してもそれ以上にはならないという重要な点を示しています。



big.Float.SetString(s string) (*Float, bool) / big.Float.Parse(s string, base int) (*Float, int, error)

特徴

  • SetPrec() と組み合わせる
    変換前に big.Float の精度を SetPrec() で設定することで、文字列が持つ情報量のうち、必要な桁数までを保持できます。
  • 文字列として数値を指定
    数値を文字列リテラルとして直接指定することで、コンパイラや float64 の丸め誤差を介さずに、意図した通りの精度で big.Float を初期化できます。
  • 最も推奨される代替手段
    ほとんどのケースで、float64 の精度問題を回避し、正確な任意精度の浮動小数点数を big.Float に設定するのに最適な方法です。

利用例

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// (1) SetString() の利用
	// 厳密な 0.3 を設定したい場合
	bf1 := new(big.Float).SetPrec(50) // 精度を50ビットに設定
	bf1.SetString("0.3")
	fmt.Printf("SetString(\"0.3\"): %s\n", bf1.String()) // 0.3

	// float64 では表現しきれない桁数を持つ数を設定
	bf2 := new(big.Float).SetPrec(100) // さらに高い精度を設定
	bf2.SetString("1.2345678901234567890123456789")
	fmt.Printf("SetString(\"long decimal\"): %s\n", bf2.String())

	// (2) Parse() の利用 (SetString よりも柔軟)
	// 基数(base)を指定できる
	bf3 := new(big.Float).SetPrec(50)
	strValue := "1.25e+3" // 指数表記
	_, _, err := bf3.Parse(strValue, 10) // 10進数でパース
	if err != nil {
		fmt.Println("Parse error:", err)
	}
	fmt.Printf("Parse(\"%s\"): %s\n", strValue, bf3.String()) // 1250

	bf4 := new(big.Float).SetPrec(50)
	hexValue := "0x1.8p+3" // 16進浮動小数点表記 (Go言語のfloat64でも使われる)
	_, _, err = bf4.Parse(hexValue, 0) // baseを0にするとGo言語の浮動小数点リテラル規則に従う
	if err != nil {
		fmt.Println("Parse error:", err)
	}
	fmt.Printf("Parse(\"%s\"): %s\n", hexValue, bf4.String()) // 12
}

big.Float.SetInt(i *big.Int) *Float / big.Float.SetInt64(i int64) *Float

特徴

  • int64 で足りない場合は *big.Int を使用します。
  • 整数からの変換
    整数(*big.Int または int64)を big.Float に変換します。整数は誤差なく浮動小数点数として表現できます。

利用例

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// (1) SetInt64() の利用
	i64Value := int64(123456789012345)
	bf1 := new(big.Float)
	bf1.SetInt64(i64Value)
	fmt.Printf("SetInt64(%d): %s\n", i64Value, bf1.String())

	// (2) SetInt() の利用 (より大きな整数に対応)
	biValue := new(big.Int)
	biValue.SetString("98765432109876543210", 10) // 非常に大きな整数
	bf2 := new(big.Float)
	bf2.SetInt(biValue)
	fmt.Printf("SetInt(%s): %s\n", biValue.String(), bf2.String())
}

big.Float.SetRat(r *big.Rat) *Float

特徴

  • 金融計算など、正確な分数表現が重要な場面で役立ちます。
  • 循環小数や厳密な分数
    big.Rat を利用すると、1/3 のような循環小数も厳密に表現でき、それを big.Float に変換する際に、設定された精度で可能な限り正確に丸められます。
  • 有理数からの変換
    math/big パッケージの big.Rat (有理数、分数) から big.Float に変換します。

利用例

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 1/3 を big.Rat で表現
	r := new(big.Rat).SetFrac64(1, 3) // 分子1、分母3
	fmt.Printf("big.Rat (1/3): %s\n", r.String())

	// big.Float に変換 (高精度で丸められる)
	bf := new(big.Float).SetPrec(100) // 精度を100ビットに設定
	bf.SetRat(r)
	fmt.Printf("SetRat(1/3) to big.Float: %s\n", bf.String())

	// 1/7 の例
	r2 := new(big.Rat).SetFrac64(1, 7)
	bf2 := new(big.Float).SetPrec(100)
	bf2.SetRat(r2)
	fmt.Printf("SetRat(1/7) to big.Float: %s\n", bf2.String())
}

実行結果例

big.Rat (1/3): 1/3
SetRat(1/3) to big.Float: 0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
big.Rat (1/7): 1/7
SetRat(1/7) to big.Float: 0.1428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571429

特徴

  • SetPrec() は含まれません。返される big.Float はデフォルトの精度(約64ビット)で初期化されます。
  • 新しい big.Float を作成して値を設定
    既存の big.Float に値を設定するのではなく、float64 の値を持つ新しい big.Float オブジェクトを返します。
  • これは big.Float.SetFloat64() のように見えるかもしれませんが、厳密には big.Float のメソッドではなく、math/big パッケージのヘルパー関数です。
package main

import (
	"fmt"
	"math/big"
)

func main() {
	f64Value := 123.456

	// big.NewFloat() で新しい big.Float を作成し、値を設定
	bf := big.NewFloat(f64Value)

	fmt.Printf("big.NewFloat(%f): %s\n", f64Value, bf.String())

	// デフォルト精度であるため、長い小数だと丸められる
	bfLong := big.NewFloat(1.234567890123456789)
	fmt.Printf("big.NewFloat(long): %s\n", bfLong.String())
}
メソッド/関数入力形式主な用途精度に関する注意点
SetFloat64()float64既存の big.Floatfloat64 値を設定float64 の精度限界を継承。より高精度にはならない。
SetString()string推奨: 厳密な数値表現、高精度な初期化SetPrec() と組み合わせることで任意精度を活かせる。
Parse()string, intSetString より柔軟な文字列パース (基数など)SetPrec() と組み合わせることで任意精度を活かせる。
SetInt() / SetInt64()*big.Int, int64整数を浮動小数点数として表現する整数は誤差なく表現できる。big.Float の精度で保持される。
SetRat()*big.Rat有理数からの変換、厳密な分数表現SetPrec() と組み合わせることで、分数を精度で丸められる。
big.NewFloat()float64float64 から新しい big.Float を作成デフォルト精度 (約64ビット) で作成される。