Go Lang:big.Float から int64 への変換テクニックとサンプルコード

2025-06-01

もう少し詳しく見ていきましょう。

big.Float 型とは

まず、big.Float 型は、標準の float32float64 型よりもarbitrary-precision(任意精度)な浮動小数点数を扱うために設計されています。これは、非常に大きな数や非常に小さな数、あるいは高い精度が求められる計算を行う場合に便利です。

Int64() メソッドの役割

Int64() メソッドは、この任意精度の big.Float 型の値を、Go の基本的な整数型である int64 型の値に変換しようと試みます。変換の際には、以下の点に注意が必要です。

  • 精度
    big.Float は高い精度を持っていますが、int64 は整数型なので、小数点以下の情報は切り捨てられます。丸めが行われるのは、整数部分が int64 の範囲内にある場合に限ります。
  • オーバーフロー/アンダーフロー
    big.Float の値が int64 型の表現できる範囲(約 -9.2e18 から 9.2e18)を超えている場合、結果は保証されません。多くの場合、int64 の最大値または最小値に近い値が返される可能性がありますが、正確な動作は Go のバージョンや内部実装に依存する場合があります。
  • 丸め
    big.Float の値が正確に整数でない場合、Int64() は最も近い int64 の整数値に丸めを行います。Go のデフォルトの丸めモードは Round half to even (偶数への丸め、JIS丸めとも呼ばれます) です。例えば、1.5 は 2 に、2.5 は 2 に丸められます。

メソッドのシグネチャ

Int64() メソッドのシグネチャは以下の通りです。

func (z *Float) Int64() int64

これは、big.Float 型のポインタ (*Float) に対して呼び出され、int64 型の値を返すことを意味します。

使用例

簡単な使用例を見てみましょう。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	f1 := new(big.Float).SetString("10.7")
	i1 := f1.Int64()
	fmt.Println(i1) // 出力: 11 (10.7 は最も近い整数 11 に丸められます)

	f2 := new(big.Float).SetString("5.2")
	i2 := f2.Int64()
	fmt.Println(i2) // 出力: 5 (5.2 は最も近い整数 5 に丸められます)

	f3 := new(big.Float).SetString("9223372036854775807.9") // int64 の最大値に近い値
	i3 := f3.Int64()
	fmt.Println(i3) // 出力: 9223372036854775808 (丸められます)

	f4 := new(big.Float).SetString("-9223372036854775808.1") // int64 の最小値に近い値
	i4 := f4.Int64()
	fmt.Println(i4) // 出力: -9223372036854775808 (丸められます)

	f5 := new(big.Float).SetString("1.5")
	i5 := f5.Int64()
	fmt.Println(i5) // 出力: 2 (偶数への丸め)

	f6 := new(big.Float).SetString("2.5")
	i6 := f6.Int64()
	fmt.Println(i6) // 出力: 2 (偶数への丸め)
}

big.Float.Int64() メソッドは、任意精度の浮動小数点数である big.Float 型の値を、最も近い int64 型の整数値に丸めて返すための便利な機能です。ただし、丸めの挙動やオーバーフローの可能性に注意して使用する必要があります。



オーバーフロー (Overflow) およびアンダーフロー (Underflow)

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

    • 事前に範囲を確認する
      big.Float の値が int64 の範囲内にあるかどうかを、Cmp() メソッドなどを使って事前に確認することを検討してください。math.MaxInt64 および math.MinInt64 と比較できます。
    • エラーチェックを行う
      big.Float には、整数部分を正確に int64 に変換できるかどうかを示す Int64() の他に、Int64Exact() というメソッドがあります。Int64Exact() は、小数部分が存在する場合やオーバーフロー/アンダーフローが発生する場合は falseok 値を返します。正確な変換が必要な場合はこちらを使用し、返り値の ok を確認してください。
    f := new(big.Float).SetString("9223372036854775808.5")
    i, exact := f.Int64Exact()
    if !exact {
        fmt.Println("int64 の範囲を超えるか、正確な整数ではありません")
    } else {
        fmt.Println(i)
    }
    
  • エラー
    big.Float の値が int64 型の表現範囲を超えている場合、Int64() の結果は予測不能になる可能性があります。Go のランタイムエラーが発生するわけではありませんが、int64 の最大値や最小値に近い値が返されたり、意図しない値になることがあります。

精度損失 (Loss of Precision)

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

    • 丸め処理を検討する
      単純な切り捨てではなく、四捨五入などの丸め処理を行いたい場合は、big.FloatRound() メソッドなどを事前に使用することを検討してください。Round() メソッドは、指定した精度に丸めた新しい big.Float を返します。その後、Int64() を呼び出すことで、丸められた整数値を得ることができます。
    f := new(big.Float).SetString("10.7")
    roundedF := new(big.Float).Set(f)
    roundedF.Round(0) // 小数点以下 0 桁に丸める(最も近い整数へ)
    i := roundedF.Int64()
    fmt.Println(i) // 出力: 11
    

    Round() の丸めモードは SetMode() で設定できます(例: big.ToNearestEven など)。

  • エラー
    big.Float は高精度な浮動小数点数を扱えますが、Int64() は整数型への変換であるため、小数点以下の情報は完全に失われます(切り捨てられます)。これはエラーではありませんが、意図しない結果につながる可能性があります。

文字列からの変換エラー (Errors during string conversion)

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

    • 入力文字列を検証する
      SetString() の返り値(bool 型のエラーフラグ)を確認し、変換が成功したかどうかをチェックしてください。エラーが発生した場合は、入力文字列の形式が正しいか確認する必要があります。
    f := new(big.Float)
    _, ok := f.SetString("invalid-number")
    if !ok {
        fmt.Println("文字列の変換に失敗しました")
        return
    }
    i := f.Int64() // f の値は初期化されていないため、結果は予測不能
    fmt.Println(i)
    
  • エラー
    big.Float の値を SetString() などで文字列から初期化する際に、不正な形式の文字列を与えるとエラーが発生します。これは Int64() 自体のエラーではありませんが、その前の段階で値が正しく設定されていないため、Int64() の結果も意図しないものになる可能性があります。

nil レシーバ (Nil Receiver)

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

    • ポインタが nil でないことを確認する
      big.Float のポインタを使用する前に、new(big.Float) などで適切に初期化されていることを確認してください。
    var f *big.Float // f は nil
    // i := f.Int64() // ここでパニックが発生します
    if f != nil {
        i := f.Int64()
        fmt.Println(i)
    } else {
        fmt.Println("big.Float ポインタが nil です")
    }
    
  • エラー
    big.Float 型のポインタが nil の状態で Int64() メソッドを呼び出すと、ランタイムパニックが発生します。

異なる丸めモード (Unexpected Rounding)

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

    • 丸めモードを明示的に設定する
      big.Float 型の SetMode() メソッドを使用して、必要な丸めモード(例: big.AwayFromZero, big.ToZero, big.ToPositiveInf, big.ToNegativeInf)を設定してから Round() を呼び出し、その後 Int64() を使用します。
    f := new(big.Float).SetString("10.5")
    f.SetMode(big.AwayFromZero) // 0 から離れる方向へ丸める (四捨五入)
    roundedF := new(big.Float).Set(f)
    roundedF.Round(0)
    i := roundedF.Int64()
    fmt.Println(i) // 出力: 11
    
  • エラー
    デフォルトの丸めモード(偶数への丸め)が、期待する丸め処理と異なる場合があります。例えば、常に切り上げや切り捨てを行いたい場合などです。



例1: 基本的な使い方 - 文字列から big.Float を生成し Int64() で整数に変換する

この例では、文字列で表現された浮動小数点数を big.Float 型に変換し、Int64() メソッドを使って最も近い int64 型の整数値を取得します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	floatStr := "123.456"
	f := new(big.Float)

	// 文字列から big.Float を設定
	_, ok := f.SetString(floatStr)
	if !ok {
		fmt.Println("文字列の変換に失敗しました:", floatStr)
		return
	}

	// Int64() で int64 型に変換
	intValue := f.Int64()
	fmt.Printf("big.Float: %s, int64: %d\n", f.String(), intValue) // 出力: big.Float: 123.456, int64: 123
}

解説

  1. floatStr という文字列で浮動小数点数を定義します。
  2. new(big.Float) で新しい big.Float 型のポインタ f を作成します。
  3. f.SetString(floatStr) で文字列を big.Float の値に設定します。このメソッドは成功したかどうかを示す bool 値を返します。エラー処理として ok を確認しています。
  4. f.Int64() を呼び出すことで、big.Float の値に最も近い int64 型の整数値が intValue に格納されます。この例では、123.456 は最も近い整数である 123 に丸められます(デフォルトの丸めモードによる)。
  5. 結果を fmt.Printf で表示しています。

例2: 丸め処理を明示的に行う - Round() メソッドとの組み合わせ

この例では、Int64() を呼び出す前に big.FloatRound() メソッドを使って明示的に丸め処理を行います。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	floatStr1 := "3.7"
	f1 := new(big.Float)
	f1.SetString(floatStr1)
	roundedF1 := new(big.Float).Set(f1)
	roundedF1.Round(0) // 小数点以下 0 桁に丸める(デフォルトの丸めモード)
	intVal1 := roundedF1.Int64()
	fmt.Printf("big.Float: %s, 丸め後: %s, int64: %d\n", f1.String(), roundedF1.String(), intVal1) // 出力: big.Float: 3.7, 丸め後: 4, int64: 4

	floatStr2 := "3.2"
	f2 := new(big.Float)
	f2.SetString(floatStr2)
	roundedF2 := new(big.Float).Set(f2)
	roundedF2.Round(0)
	intVal2 := roundedF2.Int64()
	fmt.Printf("big.Float: %s, 丸め後: %s, int64: %d\n", f2.String(), roundedF2.String(), intVal2) // 出力: big.Float: 3.2, 丸め後: 3, int64: 3
}

解説

  1. それぞれの浮動小数点数に対して、big.Float を作成し、SetString() で値を設定します。
  2. roundedF := new(big.Float).Set(f) で元の big.Float のコピーを作成し、丸め処理はコピーに対して行います。
  3. roundedF.Round(0) は、小数点以下 0 桁に丸めることを意味します。デフォルトの丸めモード(偶数への丸め)が適用されます。
  4. 丸められた big.Float に対して Int64() を呼び出し、整数値を取得します。

例3: Int64Exact() を使用して正確な整数かどうかを確認する

この例では、Int64Exact() メソッドを使用して、big.Float の値が正確な整数であるかどうかを判定し、オーバーフローの可能性も考慮します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	floatStr1 := "100"
	f1 := new(big.Float)
	f1.SetString(floatStr1)
	intVal1, exact1 := f1.Int64Exact()
	fmt.Printf("big.Float: %s, int64: %d, exact: %t\n", f1.String(), intVal1, exact1) // 出力: big.Float: 100, int64: 100, exact: true

	floatStr2 := "100.5"
	f2 := new(big.Float)
	f2.SetString(floatStr2)
	intVal2, exact2 := f2.Int64Exact()
	fmt.Printf("big.Float: %s, int64: %d, exact: %t\n", f2.String(), intVal2, exact2) // 出力: big.Float: 100.5, int64: 0, exact: false (正確な整数ではない)

	floatStr3 := "9223372036854775808" // int64 の最大値 + 1 (オーバーフロー)
	f3 := new(big.Float)
	f3.SetString(floatStr3)
	intVal3, exact3 := f3.Int64Exact()
	fmt.Printf("big.Float: %s, int64: %d, exact: %t\n", f3.String(), intVal3, exact3) // 出力: big.Float: 9223372036854775808, int64: 0, exact: false (オーバーフロー)
}

解説

  1. Int64Exact() メソッドは、変換された int64 値と、変換が正確に行われたかどうかを示す bool 値を返します。
  2. 最初の例 ("100") は正確な整数なので、exact1true になります。
  3. 2番目の例 ("100.5") は小数部分を含むため、exact2false になります。intVal2 の値は 0 になります(Go のバージョンによって異なる挙動をする可能性があります)。
  4. 3番目の例は int64 の最大値を超えるため、オーバーフローが発生し、exact3false になります。intVal3 の値も 0 になります(同様に Go のバージョンによる可能性があります)。

例4: 異なる丸めモードを試す - SetMode()Round() の組み合わせ

この例では、big.Float の丸めモードを変更して Int64() の結果がどのように変わるかを示します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	floatStr := "2.5"
	f := new(big.Float)
	f.SetString(floatStr)
	roundedF := new(big.Float).Set(f)

	// デフォルトの丸めモード (ToNearestEven)
	roundedF.SetMode(big.ToNearestEven)
	roundedF.Round(0)
	intVal1 := roundedF.Int64()
	fmt.Printf("モード: ToNearestEven, big.Float: %s, 丸め後: %s, int64: %d\n", f.String(), roundedF.String(), intVal1) // 出力: モード: ToNearestEven, big.Float: 2.5, 丸め後: 2, int64: 2

	// 丸めモードを AwayFromZero に設定 (いわゆる四捨五入)
	roundedF.Set(f) // 元の値を再度セット
	roundedF.SetMode(big.AwayFromZero)
	roundedF.Round(0)
	intVal2 := roundedF.Int64()
	fmt.Printf("モード: AwayFromZero, big.Float: %s, 丸め後: %s, int64: %d\n", f.String(), roundedF.String(), intVal2) // 出力: モード: AwayFromZero, big.Float: 2.5, 丸め後: 3, int64: 3
}
  1. big.FloatSetMode() メソッドを使用して、丸めモードを設定します。
  2. big.ToNearestEven はデフォルトの「偶数への丸め」モードです。2.5 は最も近い偶数である 2 に丸められます。
  3. big.AwayFromZero は「0 から離れる方向への丸め」モードで、いわゆる一般的な四捨五入に近い動作をします。2.5 は 3 に丸められます。


Int() メソッドと型アサーション (int64 への変換)

big.Float 型には Int() というメソッドもあります。これは *big.Int 型の値を返すため、その後で int64 型に変換する必要があります。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	floatStr := "123.456"
	f := new(big.Float)
	f.SetString(floatStr)

	// Int() メソッドで *big.Int を取得
	bigIntValue := new(big.Int)
	bigIntValue = f.Int(bigIntValue) // レシーバに結果を格納

	// *big.Int を int64 に変換 (オーバーフローの可能性あり)
	intValue := bigIntValue.Int64()
	fmt.Printf("big.Float: %s, *big.Int: %s, int64: %d\n", f.String(), bigIntValue.String(), intValue) // 出力: big.Float: 123.456, *big.Int: 123, int64: 123

	floatStr2 := "9223372036854775808" // int64 の最大値 + 1
	f2 := new(big.Float)
	f2.SetString(floatStr2)
	bigIntValue2 := new(big.Int)
	bigIntValue2 = f2.Int(bigIntValue2)
	intValue2 := bigIntValue2.Int64()
	fmt.Printf("big.Float: %s, *big.Int: %s, int64: %d\n", f2.String(), bigIntValue2.String(), intValue2) // 出力 (Goのバージョンによる): big.Float: 9223372036854775808, *big.Int: 9223372036854775808, int64: -9223372036854775808 (オーバーフロー)
}

解説

  1. f.Int(bigIntValue) は、big.Float の整数部分を *big.Int 型として bigIntValue に格納します。このメソッドは、浮動小数点数をゼロ方向に丸めます(切り捨て)。
  2. bigIntValue.Int64() は、*big.Int 型の値を int64 型に変換します。この変換では、オーバーフローが発生した場合に値がラップアラウンドする可能性があります(例: 最大値より大きい値は最小値に近い負の値になる)。
  3. Int() は丸めモードを制御できません。常にゼロ方向への丸めとなります。

利点

  • *big.Int 型を介することで、より大きな整数値を扱うことができます(ただし、最終的に int64 に変換する際には範囲制限があります)。

欠点

  • オーバーフローのチェックを別途行う必要があります。
  • 常にゼロ方向への丸めとなるため、他の丸めモードが必要な場合には適していません。

ParseInt() と String() の組み合わせ (文字列経由の変換 - 非推奨)

big.Float を一旦文字列に変換し、その整数部分を strconv.ParseInt() で解析する方法も考えられますが、一般的には推奨されません。精度が失われたり、不要な文字列操作が発生したりする可能性があります。

package main

import (
	"fmt"
	"math/big"
	"strconv"
	"strings"
)

func main() {
	floatStr := "123.456"
	f := new(big.Float)
	f.SetString(floatStr)

	// big.Float を文字列に変換
	strVal := f.String()

	// 小数点より前の部分を抽出
	integerPart := strVal
	if dotIndex := strings.Index(strVal, "."); dotIndex != -1 {
		integerPart = strVal[:dotIndex]
	}

	// 文字列を int64 にパース
	intValue, err := strconv.ParseInt(integerPart, 10, 64)
	if err != nil {
		fmt.Println("int64 へのパースエラー:", err)
		return
	}

	fmt.Printf("big.Float: %s, 文字列 (整数部): %s, int64: %d\n", f.String(), integerPart, intValue) // 出力: big.Float: 123.456, 文字列 (整数部): 123, int64: 123
}

解説

  1. f.String()big.Float を文字列に変換します。
  2. 文字列内で小数点 (.) の位置を探し、それより前の部分を整数部分として抽出します。
  3. strconv.ParseInt() 関数を使って、抽出した文字列を int64 型にパースします。

欠点

  • 丸め処理を柔軟に行うことができません(常に切り捨てに近い動作になります)。
  • 文字列操作が煩雑で、効率も良くありません。
  • 精度が保証されません。big.Float の内部表現が文字列変換時に失われる可能性があります。

自前で丸め処理を実装する (複雑)

より複雑な方法として、big.Float の内部表現を操作したり、比較演算などを用いて自前で丸め処理を実装し、int64 に変換することも考えられます。しかし、これは非常に手間がかかり、エラーも起こりやすいため、特別な理由がない限り推奨されません。big.Float が提供する Round() メソッドや Int64() を利用する方が安全で効率的です。

big.Float の値を int64 型に変換する主な代替方法は、Int() メソッドを使用して *big.Int を経由する方法です。ただし、この方法は常にゼロ方向への丸めとなり、オーバーフローのチェックも別途必要です。

文字列経由での変換は、精度や効率の面から推奨されません。

通常は、big.FloatRound() メソッドで適切な丸め処理を行った後に Int64() を使用する方法が、最も柔軟で安全なアプローチと言えるでしょう。