Go言語で巨大数値を扱う:big.Int.Cmp()の活用と注意点

2025-06-01

big.Int.Cmp() とは

big.Int型は、Go言語で非常に大きな整数(任意精度整数)を扱うための型です。通常のintint64型では表現できないような桁数の大きな数値を計算する際に使用されます。

big.Int.Cmp()メソッドは、このbig.Int型の2つの値を比較するために使われます。通常の数値型(intなど)で使える==<>といった比較演算子は、big.Int型では直接使えません。そのため、比較にはこのCmp()メソッドを利用する必要があります。

メソッドのシグネチャ

func (x *Int) Cmp(y *Int) (r int)

このメソッドは、xというbig.Int型のポインタレシーバが、引数ybig.Int型の値と比較されます。戻り値はint型で、以下のいずれかの値が返されます。

  • +1: xyより大きい場合 (x > y)
  • 0: xyと等しい場合 (x == y)
  • -1: xyより小さい場合 (x < y)

以下に具体的な使用例を示します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 2つの大きな整数を作成
	a := big.NewInt(1234567890123456789)
	b := big.NewInt(9876543210987654321)
	c := big.NewInt(1234567890123456789)
	d := big.NewInt(500)

	fmt.Printf("a = %s\n", a.String())
	fmt.Printf("b = %s\n", b.String())
	fmt.Printf("c = %s\n", c.String())
	fmt.Printf("d = %s\n", d.String())
	fmt.Println("--------------------")

	// aとbの比較
	resultAB := a.Cmp(b)
	if resultAB < 0 {
		fmt.Printf("a (%s) は b (%s) より小さいです。\n", a, b)
	} else if resultAB == 0 {
		fmt.Printf("a (%s) と b (%s) は等しいです。\n", a, b)
	} else {
		fmt.Printf("a (%s) は b (%s) より大きいです。\n", a, b)
	}

	// aとcの比較
	resultAC := a.Cmp(c)
	if resultAC < 0 {
		fmt.Printf("a (%s) は c (%s) より小さいです。\n", a, c)
	} else if resultAC == 0 {
		fmt.Printf("a (%s) と c (%s) は等しいです。\n", a, c)
	} else {
		fmt.Printf("a (%s) は c (%s) より大きいです。\n", a, c)
	}

	// bとdの比較
	resultBD := b.Cmp(d)
	if resultBD < 0 {
		fmt.Printf("b (%s) は d (%s) より小さいです。\n", b, d)
	} else if resultBD == 0 {
		fmt.Printf("b (%s) と d (%s) は等しいです。\n", b, d)
	} else {
		fmt.Printf("b (%s) は d (%s) より大きいです。\n", b, d)
	}

	// 負の数の比較
	negOne := big.NewInt(-1)
	posOne := big.NewInt(1)
	zero := big.NewInt(0)

	fmt.Println("--------------------")
	fmt.Printf("-1 と 0 の比較: %d\n", negOne.Cmp(zero))  // -1 (negOne < zero)
	fmt.Printf("0 と 1 の比較: %d\n", zero.Cmp(posOne))  // -1 (zero < posOne)
	fmt.Printf("1 と -1 の比較: %d\n", posOne.Cmp(negOne)) // 1 (posOne > negOne)
	fmt.Printf("0 と 0 の比較: %d\n", zero.Cmp(zero))   // 0 (zero == zero)
}

出力例

a = 1234567890123456789
b = 9876543210987654321
c = 1234567890123456789
d = 500
--------------------
a (1234567890123456789) は b (9876543210987654321) より小さいです。
a (1234567890123456789) と c (1234567890123456789) は等しいです。
b (9876543210987654321) は d (500) より大きいです。
--------------------
-1 と 0 の比較: -1
0 と 1 の比較: -1
1 と -1 の比較: 1
0 と 0 の比較: 0


Go言語のbig.Int.Cmp()メソッドは非常にシンプルで直感的なため、直接的なエラーが発生することは稀です。しかし、big.Int型全体の扱いや、Cmp()の結果の解釈に関して、いくつかのよくある間違いや注意点があります。

big.Intの値の比較に == 演算子を使ってしまう

よくある間違い
big.Int型の変数を通常の数値型のように==演算子で比較しようとすること。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(123)
	b := big.NewInt(123)

	// これは間違い! ポインタの比較になる
	if a == b {
		fmt.Println("aとbは等しい (間違った比較)")
	} else {
		fmt.Println("aとbは等しくない (間違った比較)") // こちらが出力される
	}
}

理由
big.Intは構造体であり、big.NewInt()*big.Int(ポインタ)を返します。a == bは、aが指すメモリのアドレスとbが指すメモリのアドレスが同じであるかを比較します。通常、big.NewInt()を呼び出すたびに新しいメモリが割り当てられるため、たとえ値が同じでもアドレスは異なるため、結果はfalseになります。

トラブルシューティング/解決策
値の比較には必ずCmp()メソッドを使用します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(123)
	b := big.NewInt(123)

	// 正しい比較方法
	if a.Cmp(b) == 0 {
		fmt.Println("aとbは等しい (正しい比較)") // こちらが出力される
	} else {
		fmt.Println("aとbは等しくない (正しい比較)")
	}
}

Cmp()の戻り値の解釈ミス

よくある間違い
Cmp()の戻り値が-1, 0, +1であることを理解せず、特定の条件分岐で誤ったロジックを組んでしまう。例えば、Cmp()の結果が0以外であれば等しくないと判断できるにもかかわらず、!= 0ではなく、> 0< 0のみを考慮してしまうなど。

理由
Cmp()は3つの異なる結果を返すため、条件分岐の際にはそれぞれの意味を正しく理解して利用する必要があります。

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

  • x >= y をチェックしたい場合: x.Cmp(y) >= 0 (または x.Cmp(y) != -1)
  • x <= y をチェックしたい場合: x.Cmp(y) <= 0 (または x.Cmp(y) != 1)
  • x > y をチェックしたい場合: x.Cmp(y) > 0
  • x < y をチェックしたい場合: x.Cmp(y) < 0
  • x == y をチェックしたい場合: x.Cmp(y) == 0

これらの比較演算子とCmp()の組み合わせを適切に使用してください。

nilポインタの扱い

よくある間違い
big.Intのポインタがnilである可能性があるにもかかわらず、Cmp()を呼び出してしまう。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	var a *big.Int // nil
	b := big.NewInt(100)

	// nilポインタに対するメソッド呼び出しはパニック (runtime error) になる
	// if a.Cmp(b) < 0 { // これを実行するとパニック
	// 	fmt.Println("aはbより小さい")
	// }
	fmt.Println(a) // <nil> と表示される
	fmt.Println(b) // 100 と表示される
}

理由
Goでは、nilポインタに対してメソッドを呼び出すと、実行時パニック(panic: runtime error: invalid memory address or nil pointer dereference)が発生します。

トラブルシューティング/解決策
Cmp()を呼び出す前に、比較対象のbig.Intポインタがnilでないことを確認します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	var a *big.Int // nil
	b := big.NewInt(100)

	if a == nil && b == nil {
		fmt.Println("aもbもnilです。")
	} else if a == nil {
		fmt.Println("aはnilです。")
	} else if b == nil {
		fmt.Println("bはnilです。")
	} else {
		// 両方nilでない場合のみCmpを呼び出す
		if a.Cmp(b) < 0 {
			fmt.Printf("a (%s) は b (%s) より小さいです。\n", a, b)
		} else if a.Cmp(b) == 0 {
			fmt.Printf("a (%s) と b (%s) は等しいです。\n", a, b)
		} else {
			fmt.Printf("a (%s) は b (%s) より大きいです。\n", a, b)
		}
	}

	// 別の例
	x := big.NewInt(200)
	var y *big.Int // nil
	z := big.NewInt(200)

	// nilとの比較は注意が必要。
	// math/bigパッケージの内部実装では、nilは0として扱われることが多いですが、
	// 常にそうであると仮定するのは危険です。
	// 明示的にnilチェックを行うのが安全です。
	if y == nil {
		fmt.Println("yはnilです。")
	} else {
		if x.Cmp(y) == 0 {
			fmt.Println("xとyは等しいです。")
		}
	}
	// x.Cmp(y) はパニックになるため、上記のようにnilチェックが必須。

	// Go 1.15以降では、*big.IntのSetメソッドなどでnilを受け付ける場合がありますが、
	// Cmpにおいてはnilを直接比較引数として渡すのは避けるべきです。
	// 常に有効な big.Int ポインタを渡すように心がけてください。
	// 例えば、以下のようなケース:
	var nilInt *big.Int // nil
	nonNilInt := big.NewInt(0)
	fmt.Printf("nonNilInt.Cmp(nilInt) の結果 (推奨されない): %d\n", nonNilInt.Cmp(nilInt)) // これは -1 を返すことがある (0 < nil)
	// しかし、これは Go の内部的な挙動であり、nilInt.Cmp(nonNilInt) はパニックになるため、
	// 対称的な比較ができません。nilチェックがやはり最良です。
}

big.Intの初期化忘れ

package main

import (
	"fmt"
	"math/big"
)

func main() {
	var a big.Int // ポインタではないのでnilではないが、値はゼロ値の big.Int{}
	b := big.NewInt(100)

	// これはパニックにはならないが、意図しない結果になる可能性
	// big.Int{} は値としては 0 を表す
	fmt.Printf("a.Cmp(b) = %d\n", a.Cmp(b)) // -1 (0 < 100)
	fmt.Printf("a: %+v\n", a) // {neg:false abs:[]} と表示される(内部表現)
}

理由
var a big.Intと宣言した場合、abig.Int構造体のゼロ値で初期化されます。これは内部的には値が0big.Intインスタンスに相当します。Cmp()メソッドはこのゼロ値のインスタンスに対して呼び出されるため、パニックにはなりませんが、プログラマが意図した初期値でない場合、論理エラーにつながることがあります。

トラブルシューティング/解決策
big.Intを扱う際は、常にbig.NewInt()SetString()などの初期化メソッドを使って、明確な値で初期化する習慣をつけることが重要です。

package main

import (
	"fmt"
	"math/big"
)
func main() {
	// 常にNewInt()などで初期化する
	a := big.NewInt(0) // 明示的に0で初期化
	b := big.NewInt(100)

	fmt.Printf("a.Cmp(b) = %d\n", a.Cmp(b)) // -1
}


big.Int.Cmp(y *Int) int メソッドは、レシーバxと引数yを比較し、以下のいずれかのint値を返します。

  • +1: x > y
  • 0: x == y
  • -1: x < y

この戻り値を使って、さまざまな比較条件を実装できます。

例1: 基本的な比較

2つのbig.Intの大小関係を調べます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("--- 例1: 基本的な比較 ---")

	// 非常に大きな整数を定義
	num1 := new(big.Int) // num1 = 0
	num1.SetString("123456789012345678901234567890", 10) // 10進数で設定

	num2 := big.NewInt(0) // num2 = 0
	num2.SetString("987654321098765432109876543210", 10) // 10進数で設定

	num3 := new(big.Int).Set(num1) // num3 = num1 (同じ値のコピー)

	fmt.Printf("num1: %s\n", num1.String())
	fmt.Printf("num2: %s\n", num2.String())
	fmt.Printf("num3: %s\n", num3.String())
	fmt.Println("--------------------")

	// num1 と num2 の比較
	cmp1_2 := num1.Cmp(num2)
	if cmp1_2 < 0 {
		fmt.Printf("%s < %s\n", num1, num2)
	} else if cmp1_2 == 0 {
		fmt.Printf("%s == %s\n", num1, num2)
	} else {
		fmt.Printf("%s > %s\n", num1, num2)
	}

	// num1 と num3 の比較
	cmp1_3 := num1.Cmp(num3)
	if cmp1_3 < 0 {
		fmt.Printf("%s < %s\n", num1, num3)
	} else if cmp1_3 == 0 {
		fmt.Printf("%s == %s\n", num1, num3)
	} else {
		fmt.Printf("%s > %s\n", num1, num3)
	}

	// num2 と num3 の比較
	cmp2_3 := num2.Cmp(num3)
	if cmp2_3 < 0 {
		fmt.Printf("%s < %s\n", num2, num3)
	} else if cmp2_3 == 0 {
		fmt.Printf("%s == %s\n", num2, num3)
	} else {
		fmt.Printf("%s > %s\n", num2, num3)
	}
}

出力例

--- 例1: 基本的な比較 ---
num1: 123456789012345678901234567890
num2: 987654321098765432109876543210
num3: 123456789012345678901234567890
--------------------
123456789012345678901234567890 < 987654321098765432109876543210
123456789012345678901234567890 == 123456789012345678901234567890
987654321098765432109876543210 > 123456789012345678901234567890

例2: 条件分岐での活用

Cmp()の結果を直接条件式に組み込むことで、より簡潔なコードを書くことができます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 例2: 条件分岐での活用 ---")

	x := big.NewInt(100)
	y := big.NewInt(200)
	z := big.NewInt(100)
	n := big.NewInt(-50)
	m := big.NewInt(-100)

	fmt.Printf("x: %s, y: %s, z: %s, n: %s, m: %s\n", x, y, z, n, m)
	fmt.Println("--------------------")

	// x == z の場合
	if x.Cmp(z) == 0 {
		fmt.Printf("%s は %s と等しい\n", x, z)
	}

	// x < y の場合
	if x.Cmp(y) < 0 {
		fmt.Printf("%s は %s より小さい\n", x, y)
	}

	// y > x の場合
	if y.Cmp(x) > 0 {
		fmt.Printf("%s は %s より大きい\n", y, x)
	}

	// n <= z の場合
	if n.Cmp(z) <= 0 {
		fmt.Printf("%s は %s 以下\n", n, z)
	}

	// m >= n の場合
	if m.Cmp(n) >= 0 { // -100 >= -50 は偽 (0.Cmp(-50) は 1)
		fmt.Printf("%s は %s 以上\n", m, n)
	} else {
		fmt.Printf("%s は %s 未満\n", m, n)
	}

	// n と m の入れ替わった比較
	if n.Cmp(m) >= 0 { // -50 >= -100 は真 (0.Cmp(-100) は 1)
		fmt.Printf("%s は %s 以上\n", n, m)
	}
}

出力例

--- 例2: 条件分岐での活用 ---
x: 100, y: 200, z: 100, n: -50, m: -100
--------------------
100 は 100 と等しい
100 は 200 より小さい
200 は 100 より大きい
-50 は 100 以下
-100 は -50 未満
-50 は -100 以上

例3: ソートや最小・最大値の探索

Cmp()メソッドは、big.Intのスライスをソートしたり、最小値や最大値を探索したりするのに非常に役立ちます。

package main

import (
	"fmt"
	"math/big"
	"sort" // Goの標準ソートパッケージ
)

// bigIntSlice は big.Int のスライス
type bigIntSlice []*big.Int

// sort.Interface を実装するために必要なメソッド
func (s bigIntSlice) Len() int {
	return len(s)
}

func (s bigIntSlice) Less(i, j int) bool {
	// Cmp() を使用して比較
	return s[i].Cmp(s[j]) < 0
}

func (s bigIntSlice) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func main() {
	fmt.Println("\n--- 例3: ソートと最小・最大値の探索 ---")

	numbers := bigIntSlice{
		big.NewInt(500),
		big.NewInt(-100),
		big.NewInt(250),
		big.NewInt(0),
		big.NewInt(big.MaxInt64), // 非常に大きな正の数
		big.NewInt(-200),
		new(big.Int).SetString("98765432109876543210", 10), // さらに大きな数
	}

	fmt.Println("ソート前:", numbers)

	// スライスをソート
	sort.Sort(numbers)

	fmt.Println("ソート後:", numbers)

	// 最小値と最大値の探索
	if len(numbers) > 0 {
		minVal := numbers[0] // ソート済みのスライスの最初の要素が最小
		maxVal := numbers[0] // ソート済みのスライスの最後の要素が最大

		// もしソートしない場合は、ループでCmpを使って探索
		// minVal := new(big.Int).Set(numbers[0])
		// maxVal := new(big.Int).Set(numbers[0])
		// for i := 1; i < len(numbers); i++ {
		// 	if numbers[i].Cmp(minVal) < 0 {
		// 		minVal.Set(numbers[i])
		// 	}
		// 	if numbers[i].Cmp(maxVal) > 0 {
		// 		maxVal.Set(numbers[i])
		// 	}
		// }

		fmt.Printf("最小値: %s\n", minVal)
		fmt.Printf("最大値: %s\n", maxVal)
	}
}

出力例

--- 例3: ソートと最小・最大値の探索 ---
ソート前: [500 -100 250 0 9223372036854775807 -200 98765432109876543210]
ソート後: [-200 -100 0 250 500 9223372036854775807 98765432109876543210]
最小値: -200
最大値: 98765432109876543210

例4: 範囲チェック

特定の範囲内にbig.Int値があるかを確認します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 例4: 範囲チェック ---")

	lowerBound := big.NewInt(1000)
	upperBound := big.NewInt(5000)

	testValue1 := big.NewInt(2500)
	testValue2 := big.NewInt(500)
	testValue3 := big.NewInt(6000)
	testValue4 := big.NewInt(1000) // 境界値
	testValue5 := big.NewInt(5000) // 境界値

	fmt.Printf("範囲: [%s, %s]\n", lowerBound, upperBound)
	fmt.Println("--------------------")

	checkRange := func(val *big.Int) {
		// val >= lowerBound かつ val <= upperBound
		if val.Cmp(lowerBound) >= 0 && val.Cmp(upperBound) <= 0 {
			fmt.Printf("%s は範囲内にあります。\n", val)
		} else {
			fmt.Printf("%s は範囲外です。\n", val)
		}
	}

	checkRange(testValue1)
	checkRange(testValue2)
	checkRange(testValue3)
	checkRange(testValue4)
	checkRange(testValue5)
}
--- 例4: 範囲チェック ---
範囲: [1000, 5000]
--------------------
2500 は範囲内にあります。
500 は範囲外です。
6000 は範囲外です。
1000 は範囲内にあります。
5000 は範囲内にあります。


Go言語のbig.Int.Cmp()は、math/bigパッケージが提供する任意精度整数big.Int公式かつ推奨される比較方法です。パフォーマンスと正確性を両立させるために設計されており、通常、これに代わる明確な「代替メソッド」は存在しません。

しかし、文脈によっては、Cmp()を使わずにbig.Intの比較を行う**「代替手段」**と呼べるアプローチがいくつか考えられます。これらは特定のユースケースや、パフォーマンスを極端に重視する(ただし注意が必要な)場合に考慮されることがあります。

big.Intを別の数値型に変換して比較する (非推奨/限定的)

もしbig.Intの値が、Goの組み込み数値型(int64uint64など)の範囲内に収まることが確実に分かっている場合、それらの型に変換してから比較することができます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("--- 1. 別の数値型に変換して比較 (非推奨/限定的) ---")

	bigA := big.NewInt(100)
	bigB := big.NewInt(50)
	bigC := big.NewInt(100)

	// big.Int.Int64() を使用
	// 注意: big.Int の値が int64 の範囲を超える場合、結果は未定義になる
	// big.Int.IsInt64() で事前にチェックするのが安全
	if bigA.IsInt64() && bigB.IsInt64() {
		intA := bigA.Int64()
		intB := bigB.Int64()

		if intA > intB {
			fmt.Printf("%s (int64: %d) は %s (int64: %d) より大きい\n", bigA, intA, bigB, intB)
		} else if intA < intB {
			fmt.Printf("%s (int64: %d) は %s (int64: %d) より小さい\n", bigA, intA, bigB, intB)
		} else {
			fmt.Printf("%s (int64: %d) と %s (int64: %d) は等しい\n", bigA, intA, bigB, intB)
		}
	} else {
		fmt.Println("比較対象の big.Int が int64 の範囲を超えています。")
	}

	// 等値チェックの場合
	if bigA.IsInt64() && bigC.IsInt64() && bigA.Int64() == bigC.Int64() {
		fmt.Printf("%s と %s は int64 として等しい\n", bigA, bigC)
	}

	// 範囲外の例 (実際に実行すると問題が出る可能性)
	// bigMaxInt64 := big.NewInt(big.MaxInt64)
	// bigTooBig := new(big.Int).Add(bigMaxInt64, big.NewInt(1))
	// if bigTooBig.IsInt64() { // これは false になる
	// 	fmt.Println("これは実行されないはずです。")
	// }
}

注意点

  • 汎用性
    big.Intの最大のメリットである任意精度を損なうため、一般的な代替手段とは言えません。
  • パフォーマンス
    小さな数値であればCmp()より速い可能性がありますが、big.Intの存在意義は大きな数値を扱うことにあるため、この方法は限定的です。
  • 範囲の確認
    big.Int.IsInt64()big.Int.IsUint64() を使って、変換前に値が対象の組み込み型の範囲に収まっていることを必ず確認してください。範囲外の値を無理に変換すると、オーバーフローによって意図しない結果になる可能性があります。

big.Intの符号 (Sign()) と絶対値 (CmpAbs(), Bits(), Bytes()) を利用する

特定の比較条件(例えば、ゼロとの比較、絶対値の比較など)においては、Cmp()を使わずにbig.Intの他のメソッドを利用できる場合があります。

  • Bits() または Bytes() を使ったゼロチェック (パフォーマンス特化) big.Int0であるかどうかのチェックは、Cmp(big.NewInt(0)) == 0が最も一般的ですが、一部のパフォーマンスセンシティブなコードでは、内部表現を利用することがあります。

    big.Intのゼロ値は、内部のビットスライス(absフィールド)が空(nilまたは長さ0)であるという特性を持っています。

    package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	fmt.Println("\n--- 2.c Bits()/Bytes() を使ったゼロチェック (パフォーマンス特化) ---")
    
    	zeroVal := big.NewInt(0)
    	nonZeroVal := big.NewInt(123)
    	negZeroVal := big.NewInt(-0) // big.Int(-0) は big.Int(0) と同じ
    
    	// Option 1: len(i.Bits()) == 0 (推奨されがち)
    	// big.Int.Bits() は内部の絶対値のビットスライスを返す。
    	// 0 の場合、このスライスの長さは 0 になる。
    	if len(zeroVal.Bits()) == 0 {
    		fmt.Printf("%s は len(Bits()) == 0 です\n", zeroVal)
    	}
    	if len(nonZeroVal.Bits()) == 0 { // これは実行されない
    		fmt.Printf("%s は len(Bits()) == 0 です (間違い)\n", nonZeroVal)
    	}
    
    	// Option 2: i.BitLen() == 0
    	// big.Int.BitLen() は、数値のビット長を返す。0 のビット長は 0。
    	if zeroVal.BitLen() == 0 {
    		fmt.Printf("%s は BitLen() == 0 です\n", zeroVal)
    	}
    
    	// Option 3: i.Sign() == 0 (最も一般的で安全)
    	if negZeroVal.Sign() == 0 {
    		fmt.Printf("%s は Sign() == 0 です\n", negZeroVal)
    	}
    }
    

    注意点

    • これらの方法はゼロとの比較にのみ有効です。他の数値との比較には使えません。
    • Sign() == 0が最も安全で一般的です。Bits()BitLen()を使ったチェックは、より低レベルの最適化であり、通常は不要です。可読性もSign()の方が優れています。
    • Bits()は絶対値のビット列を返すため、負の数の場合も正の数としてビット列が表現されます。符号情報が失われるため、他の数値との比較には適しません。
  • CmpAbs() メソッド (Go 1.15+)
    これは2つのbig.Int絶対値を比較します。

    package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	fmt.Println("\n--- 2.b CmpAbs() を利用する ---")
    
    	a := big.NewInt(-100)
    	b := big.NewInt(50)
    	c := big.NewInt(100)
    
    	// |a| vs |b|
    	// CmpAbs returns -1 if |x| < |y|, 0 if |x| == |y|, +1 if |x| > |y|
    	if a.CmpAbs(b) > 0 { // |-100| > |50| -> 100 > 50
    		fmt.Printf("|%s| > |%s|\n", a, b)
    	}
    
    	// |a| vs |c|
    	if a.CmpAbs(c) == 0 { // |-100| == |100| -> 100 == 100
    		fmt.Printf("|%s| == |%s|\n", a, c)
    	}
    }
    

    注意点
    CmpAbs()は絶対値の比較に特化しており、通常の数値比較(符号を含む)の代替にはなりません。

  • Sign() メソッド
    big.Int.Sign()は、数値の符号を返します。

    • -1: 負の数
    • 0: ゼロ
    • +1: 正の数

    これを使って、ゼロとの比較はCmp()を使わずにできます。

    package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	fmt.Println("\n--- 2. Sign() と絶対値を利用する ---")
    
    	val1 := big.NewInt(100)
    	val2 := big.NewInt(0)
    	val3 := big.NewInt(-200)
    
    	if val1.Sign() > 0 {
    		fmt.Printf("%s は正の数です\n", val1)
    	}
    	if val2.Sign() == 0 {
    		fmt.Printf("%s はゼロです\n", val2)
    	}
    	if val3.Sign() < 0 {
    		fmt.Printf("%s は負の数です\n", val3)
    	}
    }
    

文字列化して比較する (String() + string比較) (非推奨/パフォーマンス低下)

理論的には、big.Intの値を文字列に変換し、その文字列を比較することも可能ですが、これは非常に非効率的で非推奨です。

package main

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

func main() {
	fmt.Println("\n--- 3. 文字列化して比較 (非推奨/パフォーマンス低下) ---")

	a := big.NewInt(1234567890123456789)
	b := big.NewInt(9876543210987654321)
	c := big.NewInt(1234567890123456789)

	// String() メソッドで文字列に変換
	strA := a.String()
	strB := b.String()
	strC := c.String()

	fmt.Printf("strA: %s, strB: %s, strC: %s\n", strA, strB, strC)

	// 文字列として比較 (辞書順比較になるので注意)
	if strA == strC {
		fmt.Printf("文字列として %s == %s (これは等しい)\n", strA, strC)
	} else {
		fmt.Printf("文字列として %s != %s (これは等しくない)\n", strA, strC)
	}

	// 辞書順比較の罠
	strTen := big.NewInt(10).String()  // "10"
	strOneHundred := big.NewInt(100).String() // "100"

	// 数値としては 10 < 100 だが、文字列としては "100" < "10" となる!
	if strings.Compare(strTen, strOneHundred) < 0 {
		fmt.Printf("文字列として \"%s\" < \"%s\" (数値としては逆)\n", strTen, strOneHundred)
	} else {
		fmt.Printf("文字列として \"%s\" >= \"%s\" (数値としては逆)\n", strTen, strOneHundred)
	}
}

注意点

  • 絶対に使わないでください。 どんな場合でもCmp()を使うべきです。
  • 正確性の問題 (辞書順)
    文字列としての比較は辞書順で行われるため、数値としての大小関係とは異なる結果になる場合があります(例: "100"は"20"よりも小さいと判断される)。負の数の扱いも複雑になります。
  • パフォーマンスの著しい低下
    big.Intを文字列に変換する処理は、非常にコストがかかります。特に大きな数値では、その桁数に比例して処理時間が増大します。
  • 安全性
    nilポインタの扱いに注意すれば、ランタイムパニックを避けることができます。
  • パフォーマンス
    内部的に最適化されたアルゴリズムを使用しており、巨大な数値でも効率的に比較できます。
  • 正確性
    任意精度の整数としての正確な比較を行います。