CUBE SUGAR CONTAINER

技術系のこと書きます。

Golang: math/big で大きい数や細かい数を扱う

Golang の数値型はビット数が決められているため、扱える数の大きさや精度に限界がある。

整数の大きさの限界

例えば 2 を 64 ビット左シフトした整数を作ってみる。

package main

import "fmt"

func main() {
    fmt.Println(2 << 64)
}

上記を実行しようとすると int 型で扱える範囲を越えているとコンパイラに怒られる。

$ go run overflow.go
# command-line-arguments
./overflow.go:6: constant 36893488147419103232 overflows int

整数で最も大きな数を扱える型は int64 なので 64 ビットで表現できる範囲に限られる。

より大きな数を扱う

じゃあ、それよりも大きな数を扱うにはどうしたら良いかというと多倍長整数を使えば良い。 Golang には math/lang パッケージに多倍長整数の Int がある。

多倍長整数を使って、先ほどと同じ内容の処理を書いてみよう。 Int 型のインスタンス x と y を作って Exp() メソッドで累乗を計算している。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewInt(2)
    y := big.NewInt(64 + 1)
    n := new(big.Int).Exp(x, y, nil)
    fmt.Println(n)
}

見たとおり計算には演算子を使う代わりにメソッドを使うことになる。

上記を実行してみる。

$ go run bigint.go
36893488147419103232

今度はオーバーフローしていない。

浮動小数点の精度の限界

同じように浮動小数点型には精度の限界がある。 例えば 1.0 を 3.0 で割ってみよう。

package main

import (
    "fmt"
)

func main() {
    x := 1.0
    y := 3.0
    f := x / y
    fmt.Println(f)
}

当然 1 を 3 で割り切ることはできないので、どこかで表現しきれなくなる。

$ go run floatprec.go 
0.3333333333333333

浮動小数点型は符号 * 仮数 * 基数 ^ 指数で表現される。 IEEE 754 では倍精度浮動小数点数は符号が 1 ビット、仮数が 52 ビット、指数が 11 ビットと決まっている。 つまり上記は 52 ビットの仮数で表現できる限界ということになる。 ちなみに基数は 2 で固定なのでビットを使うことはない。

より細かい数を扱う

浮動小数点でもっと精度の高い計算をしたいときは math/big に用意されている Float を使う。 この Float は仮数のビット数を任意に設定できる。

試しに先ほどの 1 / 3 の計算を 1024 ビットでやってみよう。 Float#SetPrec() が仮数を指定するためのメソッドになっている。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewFloat(1)
    y := big.NewFloat(3)
    f := new(big.Float).SetPrec(1024).Quo(x, y)
    fmt.Println(f)
}

実行すると先ほどよりなっがーーーい結果が表示される。

$ go run bigfloat.go
0.333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333334

分数 (有理数) を扱う

先ほど見た通り浮動小数点で割り切れない数を扱おうとすると Float を使って高精度にはできたとしても限界はある。

そんなとき Rat を使えば分数を分数としてそのまま扱うことができる。 試しに (1 / 3) * (2 / 4) を計算してみよう。

package main

import (
    "fmt"
    "math/big"
)

func main() {
    x := big.NewRat(1, 3)
    y := big.NewRat(2, 4)
    r := new(big.Rat).Mul(x, y)
    fmt.Println(r)
}

上記の結果は 2 / 12 で通分すると 1 / 6 になるはず。 実行すると、その通りの結果が得られる。

$ go run bigrat.go
1/6

まとめ

Golang で大きな数や細かい数や分数を扱いたいときは math/big パッケージを使おう。