【Go】math/big パッケージ big.Int.Sign() の使い方と注意点

2025-06-01

  • 1
    big.Int の値が正の数の場合
  • 0
    big.Int の値がゼロの場合
  • -1
    big.Int の値が負の数の場合

つまり、Sign() メソッドを呼び出すことで、その big.Int 変数がプラス、マイナス、またはゼロのいずれであるかを簡単に判別できます。


package main

import (
	"fmt"
	"math/big"
)

func main() {
	positive := big.NewInt(123)
	negative := big.NewInt(-456)
	zero := big.NewInt(0)

	fmt.Println(positive.Sign()) // 出力: 1
	fmt.Println(negative.Sign()) // 出力: -1
	fmt.Println(zero.Sign())     // 出力: 0
}

この例では、それぞれ正の数、負の数、ゼロを表す big.Int 型の変数を宣言し、それぞれの Sign() メソッドの結果を出力しています。出力結果から、Sign() メソッドが期待通りに符号を返していることがわかります。



nil レシーバでの呼び出し

  • トラブルシューティング
    • big.Int 型の変数を使用する前に、必ず初期化を行っているか確認してください。
    • 関数などで big.Int のポインタを受け取る場合、nil チェックを行い、適切なエラー処理を追加することを検討してください。
  • 原因
    big.Int 型の変数を宣言しただけで、明示的に初期化(new(big.Int)big.NewInt(value) など)を行っていない場合に起こります。
  • エラー
    big.Int 型のポインタが nil の状態で Sign() メソッドを呼び出すと、ランタイムパニックが発生します。

例(エラーとなるケース)

package main

import (
	"fmt"
	"math/big"
)

func main() {
	var num *big.Int // 初期化していない
	sign := num.Sign() // ここでパニックが発生する可能性
	fmt.Println(sign)
}

例(修正)

package main

import (
	"fmt"
	"math/big"
)

func main() {
	num := new(big.Int) // 初期化
	sign := num.Sign()
	fmt.Println(sign) // 出力: 0 (初期値はゼロ)

	num2 := big.NewInt(100) // 値を指定して初期化
	sign2 := num2.Sign()
	fmt.Println(sign2) // 出力: 1
}

Sign() の戻り値の誤解

  • トラブルシューティング
    • Sign() メソッドの戻り値は -1, 0, 1 のいずれかの整数であることを正しく理解してください。
    • 符号の判定には、戻り値をこれらの値と比較する必要があります。
  • 原因
    ドキュメントをよく読んでいない、または他の言語の符号関連の関数と混同している。
  • 誤解
    Sign() メソッドは真偽値(true / false)を返すと思っている。

例(誤った使い方)

package main

import (
	"fmt"
	"math/big"
)

func main() {
	num := big.NewInt(-5)
	if num.Sign() == true { // これは常に false になる
		fmt.Println("正の数")
	} else if num.Sign() == false { // これも常に false になる
		fmt.Println("負の数またはゼロ")
	}
}

例(正しい使い方)

package main

import (
	"fmt"
	"math/big"
)

func main() {
	num := big.NewInt(-5)
	sign := num.Sign()
	if sign > 0 {
		fmt.Println("正の数")
	} else if sign < 0 {
		fmt.Println("負の数")
	} else {
		fmt.Println("ゼロ")
	}
}

関連する処理での間違い

  • トラブルシューティング
    • Sign() の戻り値(-1, 0, 1)が、その後の処理でどのように使われているかを確認してください。
    • 符号による条件分岐 (if, else if, else など)が意図した通りに動作するか、テストを十分に行なってください。
  • 原因
    論理的な誤りや、符号の条件分岐が正しく記述されていない。
  • エラーの可能性
    Sign() の結果に基づいて行う後続の処理で、符号の判定を誤っている。

大きな数の比較における注意

  • トラブルシューティング
    • 絶対値の比較が必要な場合は、big.Int.Cmp() メソッドを利用してください。
  • 誤解
    Sign() の結果が大きいほど、絶対値も大きいと考えてしまう。
  • 注意点
    Sign() は符号のみを返します。絶対値の大小を比較したい場合は、Cmp() メソッドを使用する必要があります。
package main

import (
	"fmt"
	"math/big"
)

func main() {
	num1 := big.NewInt(100)
	num2 := big.NewInt(-200)

	if num1.Sign() > num2.Sign() {
		fmt.Println("num1 の符号は num2 の符号より大きい") // これは符号の比較
	}

	if num1.CmpAbs(num2) > 0 {
		fmt.Println("num1 の絶対値は num2 の絶対値より大きい")
	} else if num1.CmpAbs(num2) < 0 {
		fmt.Println("num1 の絶対値は num2 の絶対値より小さい")
	} else {
		fmt.Println("num1 の絶対値は num2 の絶対値と等しい")
	}
}


基本的な使い方

package main

import (
	"fmt"
	"math/big"
)

func main() {
	positive := big.NewInt(123)
	negative := big.NewInt(-456)
	zero := big.NewInt(0)

	fmt.Printf("%d の符号: %d\n", positive, positive.Sign()) // 出力: 123 の符号: 1
	fmt.Printf("%d の符号: %d\n", negative, negative.Sign()) // 出力: -456 の符号: -1
	fmt.Printf("%d の符号: %d\n", zero, zero.Sign())     // 出力: 0 の符号: 0
}

この例では、正の数、負の数、ゼロを表す big.Int 変数を作成し、それぞれの Sign() メソッドを呼び出して符号を表示しています。

符号による条件分岐

package main

import (
	"fmt"
	"math/big"
)

func checkSign(n *big.Int) {
	sign := n.Sign()
	if sign > 0 {
		fmt.Printf("%d は正の数です。\n", n)
	} else if sign < 0 {
		fmt.Printf("%d は負の数です。\n", n)
	} else {
		fmt.Printf("%d はゼロです。\n", n)
	}
}

func main() {
	num1 := big.NewInt(1000)
	num2 := big.NewInt(-500)
	num3 := big.NewInt(0)

	checkSign(num1) // 出力: 1000 は正の数です。
	checkSign(num2) // 出力: -500 は負の数です。
	checkSign(num3) // 出力: 0 はゼロです。
}

この例では、Sign() メソッドの結果に基づいて、big.Int 変数が正の数、負の数、ゼロのいずれであるかを判定する関数 checkSign を定義しています。

関数の戻り値としての利用

package main

import (
	"fmt"
	"math/big"
)

func getSign(n *big.Int) int {
	return n.Sign()
}

func main() {
	number := big.NewInt(-99)
	signOfNumber := getSign(number)
	fmt.Printf("%d の符号: %d\n", number, signOfNumber) // 出力: -99 の符号: -1
}

この例では、Sign() メソッドの結果を関数の戻り値として利用しています。

絶対値を計算する関数 (Sign() の活用)

package main

import (
	"fmt"
	"math/big"
)

func absBigInt(n *big.Int) *big.Int {
	if n.Sign() < 0 {
		return new(big.Int).Neg(n) // 負の数の場合は符号を反転
	}
	return new(big.Int).Set(n) // 正の数またはゼロの場合はそのまま返す
}

func main() {
	num1 := big.NewInt(-250)
	absNum1 := absBigInt(num1)
	fmt.Printf("%d の絶対値: %d\n", num1, absNum1) // 出力: -250 の絶対値: 250

	num2 := big.NewInt(300)
	absNum2 := absBigInt(num2)
	fmt.Printf("%d の絶対値: %d\n", num2, absNum2) // 出力: 300 の絶対値: 300

	num3 := big.NewInt(0)
	absNum3 := absBigInt(num3)
	fmt.Printf("%d の絶対値: %d\n", num3, absNum3) // 出力: 0 の絶対値: 0
}

この例では、Sign() メソッドを利用して big.Int 型の絶対値を計算する関数 absBigInt を実装しています。負の数の場合にのみ符号を反転させています。

大きな整数の比較 (符号と絶対値)

package main

import (
	"fmt"
	"math/big"
)

func compareBigInts(a, b *big.Int) {
	signA := a.Sign()
	signB := b.Sign()

	fmt.Printf("%d の符号: %d, %d の符号: %d\n", a, signA, b, signB)

	if signA > signB {
		fmt.Printf("%d は %d より符号が大きい (正 > 負 or ゼロ)\n", a, b)
	} else if signA < signB {
		fmt.Printf("%d は %d より符号が小さい (負 < ゼロ or 正)\n", a, b)
	} else {
		fmt.Printf("%d と %d は同じ符号です\n", a, b)
		if signA != 0 { // ゼロでない場合、絶対値を比較
			if a.CmpAbs(b) > 0 {
				fmt.Printf("%d の絶対値は %d の絶対値より大きいです\n", a, b)
			} else if a.CmpAbs(b) < 0 {
				fmt.Printf("%d の絶対値は %d の絶対値より小さいです\n", a, b)
			} else {
				fmt.Printf("%d の絶対値は %d の絶対値と等しいです\n", a, b)
			}
		}
	}
}

func main() {
	num1 := big.NewInt(500)
	num2 := big.NewInt(-1000)
	num3 := big.NewInt(500)
	num4 := big.NewInt(-500)
	num5 := big.NewInt(0)

	compareBigInts(num1, num2)
	compareBigInts(num1, num3)
	compareBigInts(num2, num4)
	compareBigInts(num1, num5)
}

この例では、二つの big.Int 変数の符号を比較し、符号が同じ場合は絶対値を比較しています。Sign()CmpAbs() を組み合わせて使うことで、より詳細な比較を行うことができます。



ゼロとの比較 (Cmp() メソッド)

big.Int 型は、別の big.Int 型の値と比較するための Cmp() メソッドを提供しています。これを利用して、対象の big.Int がゼロより大きいか、小さいか、等しいかを判定することで、符号を知ることができます。

package main

import (
	"fmt"
	"math/big"
)

func checkSignAlternative(n *big.Int) {
	zero := big.NewInt(0)
	comparisonResult := n.Cmp(zero)

	if comparisonResult > 0 {
		fmt.Printf("%d は正の数です (Cmp 使用)。\n", n)
	} else if comparisonResult < 0 {
		fmt.Printf("%d は負の数です (Cmp 使用)。\n", n)
	} else {
		fmt.Printf("%d はゼロです (Cmp 使用)。\n", n)
	}
}

func main() {
	num1 := big.NewInt(200)
	num2 := big.NewInt(-300)
	num3 := big.NewInt(0)

	checkSignAlternative(num1) // 出力: 200 は正の数です (Cmp 使用)。
	checkSignAlternative(num2) // 出力: -300 は負の数です (Cmp 使用)。
	checkSignAlternative(num3) // 出力: 0 はゼロです (Cmp 使用)。
}

この例では、Cmp(zero) の結果が 1 であれば正の数、-1 であれば負の数、0 であればゼロであることを利用して符号を判定しています。

文字列変換による判定 (非効率な場合あり)

big.Int 型を文字列に変換し、その最初の文字を調べることで符号を判定する方法も考えられます。ただし、これは数値演算としては非効率であり、通常は推奨されません。

package main

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

func checkSignByString(n *big.Int) {
	str := n.String()
	if strings.HasPrefix(str, "-") {
		fmt.Printf("%s は負の数です (文字列変換使用)。\n", str)
	} else if str == "0" {
		fmt.Printf("%s はゼロです (文字列変換使用)。\n", str)
	} else {
		fmt.Printf("%s は正の数です (文字列変換使用)。\n", str)
	}
}

func main() {
	num1 := big.NewInt(400)
	num2 := big.NewInt(-500)
	num3 := big.NewInt(0)

	checkSignByString(num1) // 出力: 400 は正の数です (文字列変換使用)。
	checkSignByString(num2) // 出力: -500 は負の数です (文字列変換使用)。
	checkSignByString(num3) // 出力: 0 はゼロです (文字列変換使用)。
}

この方法は、文字列操作のオーバーヘッドがあるため、パフォーマンスが重要な場面では避けるべきです。主にデバッグやログ出力など、数値としての符号を人間が理解しやすい形で確認したい場合に限定的に使用されることがあります。

絶対値との比較 (ゼロとの比較の特殊なケース)

ある big.Int がゼロであるかどうかを判定する場合、その絶対値がゼロであるかどうかで判断できます。Abs() メソッドで絶対値を取得し、それをゼロと比較します。

package main

import (
	"fmt"
	"math/big"
)

func isZeroAlternative(n *big.Int) bool {
	absN := new(big.Int).Abs(n)
	zero := big.NewInt(0)
	return absN.Cmp(zero) == 0
}

func main() {
	num1 := big.NewInt(100)
	num2 := big.NewInt(0)

	fmt.Printf("%d はゼロですか? %t\n", num1, isZeroAlternative(num1)) // 出力: 100 はゼロですか? false
	fmt.Printf("%d はゼロですか? %t\n", num2, isZeroAlternative(num2)) // 出力: 0 はゼロですか? true
}

これは、直接 Cmp(zero) == 0 を行うよりも少し冗長ですが、絶対値に関連する処理の中でゼロ判定を行いたい場合に考えられる方法です。

推奨される方法

通常、符号の判定には big.Int.Sign() メソッドを使用するのが最も直接的で効率的です。代替方法としては、ゼロとの比較に big.Int.Cmp() メソッドを使用するのが一般的です。文字列変換による方法は、パフォーマンスの観点から特別な理由がない限り避けるべきです。