big.Int.Mod()
通常のGoの組み込み型(int
, int64
など)では表現できないような大きな数を扱う際に、math/big
パッケージのbig.Int
型を使用します。
func (z *Int) Mod(x, y *Int) *Int
このメソッドは、以下のように動作します。
x
をy
で割ったときの剰余を計算します。- その結果をレシーバーである
z
に格納します。 - そして
z
自身を返します。
つまり、z = x % y
と同じ計算を行います。
重要な点
- 戻り値の符号
Goの組み込みの%
演算子と同様に、Mod
メソッドによって返される剰余の符号は、被除数x
の符号と同じになります。- 例:
10 Mod 3
は1
-10 Mod 3
は-1
10 Mod -3
は1
-10 Mod -3
は-1
- 例:
- yはゼロであってはならない
y
がゼロの場合、ランタイムパニック(division by zero
)が発生します。 - レシーバーへの格納
結果はメソッドを呼び出したbig.Int
オブジェクト(z
)に直接格納されます。新しいbig.Int
オブジェクトが生成されるわけではありません。 - 多倍長整数
x
,y
,z
はすべて*big.Int
型(big.Int
へのポインタ)である必要があります。
使用例
package main
import (
"fmt"
"math/big"
)
func main() {
// big.Int のインスタンスを生成
a := new(big.Int)
b := new(big.Int)
result := new(big.Int)
// 値を設定
a.SetString("123456789012345678901234567890", 10) // 非常に大きな数
b.SetInt64(7) // 7 で割る
// Mod() メソッドを呼び出す
// result = a % b
result.Mod(a, b)
fmt.Printf("a: %s\n", a.String())
fmt.Printf("b: %s\n", b.String())
fmt.Printf("a %% b (Mod): %s\n", result.String())
fmt.Println("--- 負の数の例 ---")
negA := new(big.Int)
negA.SetString("-12345", 10)
posB := new(big.Int)
posB.SetInt64(7)
result2 := new(big.Int)
result2.Mod(negA, posB)
fmt.Printf("%s %% %s (Mod): %s\n", negA.String(), posB.String(), result2.String()) // -12345 % 7 は -3 (Goの仕様)
posA := new(big.Int)
posA.SetString("12345", 10)
negB := new(big.Int)
negB.SetInt64(-7)
result3 := new(big.Int)
result3.Mod(posA, negB)
fmt.Printf("%s %% %s (Mod): %s\n", posA.String(), negB.String(), result3.String()) // 12345 % -7 は 3 (Goの仕様)
}
出力例
a: 123456789012345678901234567890
b: 7
a % b (Mod): 3
--- 負の数の例 ---
-12345 % 7 (Mod): -3
12345 % -7 (Mod): 3
Go言語の組み込み整数型(int
, int64
など)は、固定のビット幅(32ビットまたは64ビット)で表現できる数値の範囲に制限があります。例えば、int64
で扱える最大値は約9×1018です。
しかし、暗号技術(RSAなど)や科学計算など、非常に大きな整数(何百桁にも及ぶ数)を扱う必要がある場合があります。このような場合に、math/big
パッケージのbig.Int
型が利用されます。big.Int
は、メモリが許す限り任意の精度の整数を表現できます。
y(除数)がゼロの場合のパニック (Panic when y is zero)
エラー
big.Int.Mod(x, y)
において、y
がゼロである場合、runtime panic: division by zero
が発生します。通常の整数の割り算と同様に、ゼロ除算は許可されていません。
トラブルシューティング
Mod()
を呼び出す前に、必ずy
がゼロでないことを確認してください。y.Cmp(big.NewInt(0)) != 0
のようにしてゼロと比較することができます。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(10)
b := big.NewInt(0) // 除数をゼロにする
result := new(big.Int)
// bがゼロでないかチェックする
if b.Cmp(big.NewInt(0)) == 0 {
fmt.Println("エラー: 除数がゼロです!")
// エラーハンドリングのロジックを追加
return
}
result.Mod(a, b) // ここでパニックが発生する
fmt.Printf("結果: %s\n", result.String())
}
ポインタとしての引数 (*big.Int)
エラー
big.Int
のメソッドは、多くの場合*big.Int
(ポインタ)を引数として受け取ります。big.Int
の値を直接渡そうとするとコンパイルエラーになります。
package main
import (
"math/big"
)
func main() {
a := big.NewInt(10)
b := big.NewInt(3)
// 間違い: result.Mod(a, *b) や result.Mod(*a, b) のように値で渡そうとする
// result.Mod(*a, *b) // これはコンパイルエラー
}
トラブルシューティング
常にbig.Int
のポインタ(&
演算子で取得するか、new(big.Int)
やbig.NewInt()
で作成したもの)を渡すようにしてください。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(10)
b := big.NewInt(3)
result := new(big.Int) // 結果を格納する新しいbig.Intを生成
result.Mod(a, b) // ポインタを渡す
fmt.Printf("10 mod 3 = %s\n", result.String())
}
シャローコピーによる意図しない値の変更 (Unintended value changes due to shallow copy)
エラー
big.Int
は内部的にスライス([]uint
)を使って大きな数を表現しています。*big.Int
をコピーする際に、単純な代入(例: copyOfInt := *originalInt
)を行うと、これはシャローコピーになります。つまり、新しいbig.Int
は作成されますが、内部のスライスは元のものと共有されます。このため、コピーした値に対して演算を行うと、元の値も意図せず変更されてしまうことがあります。
これはMod()
に限らず、big.Int
の他の演算メソッドでも発生しうる一般的なエラーです。
package main
import (
"fmt"
"math/big"
)
func main() {
originalA := big.NewInt(100)
divisor := big.NewInt(7)
// 間違い: シャローコピー
// `copyOfA := *originalA` は、originalAの指す underlying dataをcopyOfAも指すことになる
// そのため、copyOfAへの操作がoriginalAにも影響する
copyOfA := *originalA
// Mod演算を実行 (resultは新しいbig.Intにするが、copyOfA自体が変更される可能性がある)
// この場合、Modはレシーバーを変更するが、copyOfAの基になるoriginalAの内部データが共有されているため問題になる
result := new(big.Int)
result.Mod(©OfA, divisor) // copyOfAのModを計算
fmt.Printf("Original A: %s\n", originalA.String())
fmt.Printf("Copied A after Mod: %s\n", copyOfA.String())
fmt.Printf("Result: %s\n", result.String())
}
上記の例では、copyOfA
は新しいbig.Int
オブジェクトですが、その内部のabs []uint
スライスはoriginalA
と同じものを参照しています。そのため、result.Mod(©OfA, divisor)
の呼び出しによってcopyOfA
の内部データが変更されると、originalA
も同じデータを見ているため、originalA
の値も変わってしまいます。
トラブルシューティング
big.Int
の値をコピーする場合は、必ずSet()
メソッドを使用してディープコピーを行ってください。
package main
import (
"fmt"
"math/big"
)
func main() {
originalA := big.NewInt(100)
divisor := big.NewInt(7)
// 正しい方法: Set() メソッドでディープコピー
copyOfA := new(big.Int).Set(originalA)
result := new(big.Int)
result.Mod(copyOfA, divisor) // copyOfAに対してModを計算
fmt.Printf("Original A: %s\n", originalA.String())
fmt.Printf("Copied A after Mod (should be same as original): %s\n", copyOfA.String())
fmt.Printf("Result: %s\n", result.String())
}
この場合、copyOfA.Mod(copyOfA, divisor)
のように、copyOfA
自体をレシーバーにして演算を行っても、originalA
には影響がありません。
組み込み型との混合 (Mixing with built-in types)
エラー
big.Int
と通常のint
やint64
などの組み込み型を直接演算することはできません。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(100)
b := 7 // int型
// 間違い: big.Int と int を直接演算
// result := new(big.Int).Mod(a, b) // コンパイルエラー
// 間違い: big.Int のメソッドに int を渡す
// result := new(big.Int)
// result.Mod(a, b) // コンパイルエラー
}
トラブルシューティング
組み込み型の値をbig.Int
に変換してから演算を行う必要があります。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(100)
b := big.NewInt(7) // int を big.Int に変換
result := new(big.Int)
result.Mod(a, b)
fmt.Printf("%s mod %s = %s\n", a.String(), b.String(), result.String())
// または、一時的に変換することも可能
var val int64 = 7
result.Mod(a, big.NewInt(val))
fmt.Printf("%s mod %d = %s\n", a.String(), val, result.String())
}
注意点
big.Int.Mod(x, y)
の剰余の符号は、Goの組み込みの%
演算子と同様に、被除数x
の符号と同じになります。これは数学的なモジュロ演算(結果が常に非負になる)とは異なる場合があります。
例えば、数学では-10 mod 3 = 2
とされることがありますが、Goでは-10 % 3 = -1
となります。big.Int.Mod()
もこれに従います。
トラブルシューティング
もし常に非負の剰余が必要な場合は、結果が負になった場合に除数y
を足すなどの追加の処理が必要になります。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(-10)
b := big.NewInt(3)
result := new(big.Int)
result.Mod(a, b)
fmt.Printf("%s mod %s (Go's Mod) = %s\n", a.String(), b.String(), result.String()) // -1
// 数学的なモジュロ(常に非負)が必要な場合
if result.Sign() < 0 { // 結果が負の場合
result.Add(result, b) // 除数を足す
}
fmt.Printf("%s mod %s (Mathematical Mod) = %s\n", a.String(), b.String(), result.String()) // 2
}
基本的な剰余計算
これは最も基本的な使用例で、大きな整数同士の剰余を計算します。
package main
import (
"fmt"
"math/big" // big.Int を使うためにインポート
)
func main() {
// 非常に大きな数
numStr := "987654321098765432109876543210"
divisorInt64 := 7 // 割る数
// big.Int のインスタンスを生成
// new(big.Int) は、big.Int型のゼロ値へのポインタを返します
a := new(big.Int)
b := new(big.Int)
result := new(big.Int)
// 文字列から big.Int に値をセット
// SetString(s string, base int) (*Int, bool)
// 第1引数: 数値の文字列
// 第2引数: 基数(10進数の場合は10)
// 戻り値: *big.Int と、成功したかどうかのbool値
a.SetString(numStr, 10)
// int64 から big.Int に値をセット
// SetInt64(x int64) *Int
b.SetInt64(divisorInt64)
fmt.Printf("a: %s\n", a.String())
fmt.Printf("b: %s\n", b.String())
// Mod() メソッドを呼び出し
// result = a % b
// func (z *Int) Mod(x, y *Int) *Int
// 結果はレシーバー (z、この場合はresult) に格納され、z自身が返される
result.Mod(a, b)
fmt.Printf("a %% b (Mod): %s\n", result.String()) // 結果は3
}
解説
String()
メソッドはbig.Int
の値を文字列として返します。result.Mod(a, b)
でa
をb
で割った剰余を計算し、その結果をresult
に格納します。SetString()
メソッドで文字列から、SetInt64()
メソッドでint64
から、それぞれbig.Int
に値をセットします。new(big.Int)
を使ってbig.Int
の新しいインスタンス(へのポインタ)を作成します。math/big
パッケージをインポートします。
負の数と剰余の符号
Go言語の%
演算子と同様に、big.Int.Mod()
の結果の符号は、被除数(割られる数)の符号と同じになります。数学的なモジュロ演算(結果が常に非負)とは異なることに注意が必要です。
package main
import (
"fmt"
"math/big"
)
func main() {
// ケース1: 被除数が負、除数が正
a1 := big.NewInt(-10)
b1 := big.NewInt(3)
result1 := new(big.Int)
result1.Mod(a1, b1)
fmt.Printf("%s mod %s (Go's Mod) = %s\n", a1.String(), b1.String(), result1.String()) // 出力: -1
// ケース2: 被除数が正、除数が負
a2 := big.NewInt(10)
b2 := big.NewInt(-3)
result2 := new(big.Int)
result2.Mod(a2, b2)
fmt.Printf("%s mod %s (Go's Mod) = %s\n", a2.String(), b2.String(), result2.String()) // 出力: 1
// ケース3: 両方負
a3 := big.NewInt(-10)
b3 := big.NewInt(-3)
result3 := new(big.Int)
result3.Mod(a3, b3)
fmt.Printf("%s mod %s (Go's Mod) = %s\n", a3.String(), b3.String(), result3.String()) // 出力: -1
fmt.Println("\n--- 数学的なモジュロ(結果を常に非負にする) ---")
// 数学的なモジュロが必要な場合の処理
mathModResult := new(big.Int)
mathModResult.Mod(a1, b1) // まずGoのModで計算
if mathModResult.Sign() < 0 { // 結果が負の場合
// 除数b1を足す
// big.NewInt(0).Abs(b1) は b1 の絶対値を取得 (b1が負の場合も考慮)
// しかし、GoのModの結果はxの符号と同じなので、b1の符号は気にせずb1を足せばよい
mathModResult.Add(mathModResult, b1)
}
fmt.Printf("%s mod %s (Mathematical Mod) = %s\n", a1.String(), b1.String(), mathModResult.String()) // -10 mod 3 = 2
}
解説
- 数学的なモジュロ演算(結果が常に非負)が必要な場合は、
Mod()
の結果が負だった場合に、除数(y
)をその結果に加算することで対応できます。result.Sign() < 0
で負かどうかを判定します。 - Goの
Mod()
は被除数(x
)の符号に結果の符号を合わせます。
ゼロ除算の回避
big.Int.Mod()
にゼロを渡すとパニックが発生するため、事前にチェックが必要です。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(123)
b := big.NewInt(0) // ゼロ
result := new(big.Int)
// 除数bがゼロでないことを確認
// b.Cmp(big.NewInt(0)) == 0 は b が 0 と等しいことを意味する
if b.Cmp(big.NewInt(0)) == 0 {
fmt.Println("エラー: 除数がゼロです。Mod() を実行できません。")
return // 処理を終了
}
result.Mod(a, b) // この行は実行されない
fmt.Printf("結果: %s\n", result.String())
}
解説
- この比較結果が
0
であればb
がゼロなので、Mod()
を呼び出す前にエラーメッセージを表示して処理を中断します。 b.Cmp(big.NewInt(0))
は、b
とbig.NewInt(0)
を比較します。b < 0
なら-1
b == 0
なら0
b > 0
なら1
big.Int
にはMod()
の他に、冪剰余を計算するExp()
メソッドがあります。これは(base^exponent) % modulus
を効率的に計算するために使用されます。Mod()
と混同しないように注意が必要です。
package main
import (
"fmt"
"math/big"
)
func main() {
// 例: (2^100) % 7 を計算する
base := big.NewInt(2)
exponent := big.NewInt(100)
modulus := big.NewInt(7)
// big.Int.Exp() を使用
// func (z *Int) Exp(x, y, m *Int) *Int
// z = (x^y) mod m
// mがnilの場合、(x^y)を計算
expResult := new(big.Int)
expResult.Exp(base, exponent, modulus)
fmt.Printf("(%s^%s) %% %s (Exp) = %s\n", base.String(), exponent.String(), modulus.String(), expResult.String())
// 参考: 単純に Mod() を使う場合
// まず (2^100) を計算
powResult := new(big.Int)
powResult.Exp(base, exponent, nil) // modulusをnilにすると単なる冪乗
fmt.Printf("2^100 = %s\n", powResult.String())
// その後で Mod()
modResult := new(big.Int)
modResult.Mod(powResult, modulus)
fmt.Printf("2^100 %% 7 (Mod after Exp) = %s\n", modResult.String())
// powResultが非常に大きな数になるため、Exp(x, y, m) の方が効率的
}
- そのため、冪乗と剰余を同時に行いたい場合は、
Exp()
メソッドの第三引数(modulus)を使用するのが一般的です。 Mod()
は単なる剰余演算であり、$ \text{base}^{\text{exponent}} $ の結果が巨大な値になる場合、先に$ \text{base}^{\text{exponent}} $ を計算してからMod()
を適用するとメモリや計算時間が非効率になる可能性があります。Exp(base, exponent, modulus)
は、$ \text{base}^{\text{exponent}} \pmod{\text{modulus}} $ を計算します。これはRSA暗号などで非常に重要です。
主な代替・関連手法としては、以下の点が挙げられます。
big.Int.ModInverse() (Modular Multiplicative Inverse)
これは単純な剰余計算とは異なりますが、**モジュラ逆数(Modular Multiplicative Inverse)**を計算する際に使用します。 (a⋅x)(modm)=1 となる x を見つける操作です。これは公開鍵暗号(RSAなど)で非常に重要です。
big.Int.Mod()
の目的とは異なりますが、モジュロ演算に関連する重要な機能です。
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(7)
m := big.NewInt(26) // mod 26
// 7 の mod 26 でのモジュラ逆数を計算
// func (z *Int) ModInverse(x, m *Int) *Int
// z = x^-1 mod m
// mは正でなければならない。gcd(x, m) == 1 (xとmが互いに素)でないと結果はnil
inv := new(big.Int)
inv.ModInverse(a, m)
if inv != nil {
fmt.Printf("%s の mod %s における逆数: %s\n", a.String(), m.String(), inv.String())
// 検算: (7 * 15) % 26 = 105 % 26 = 1
check := new(big.Int).Mul(a, inv)
check.Mod(check, m)
fmt.Printf("検算: (%s * %s) %% %s = %s\n", a.String(), inv.String(), m.String(), check.String())
} else {
fmt.Printf("%s の mod %s における逆数は存在しません。\n", a.String(), m.String())
}
// 互いに素でない場合 (例: 2 と 26)
a2 := big.NewInt(2)
inv2 := new(big.Int)
inv2.ModInverse(a2, m)
if inv2 != nil {
fmt.Printf("%s の mod %s における逆数: %s\n", a2.String(), m.String(), inv2.String())
} else {
fmt.Printf("%s の mod %s における逆数は存在しません。(互いに素ではない)\n", a2.String(), m.String())
}
}
解説
- これは
Mod()
のように一般的な剰余を求めるものではなく、特定の数学的目的に使用されます。 - x と m が互いに素でない場合、逆数は存在せず、
nil
が返されます。 ModInverse(x, m)
は、$ x \pmod m $ の逆数を計算します。
big.Int.Exp() (Modular Exponentiation - 冪剰余)
これは前に少し触れましたが、特定の大きな数($ \text{base}^{\text{exponent}} $)の剰余を計算する際に、big.Int.Mod()
を直接使うよりもはるかに効率的な方法です。
big.Int.Exp(x, y, m)
は (xy)(modm) を計算します。
もしあなたが大きな数の冪乗の剰余を求めたいのであれば、big.Int.Mod()
の代替というよりは、より適切なアプローチとしてbig.Int.Exp()
を使うべきです。
package main
import (
"fmt"
"math/big"
)
func main() {
base := big.NewInt(3)
exponent := big.NewInt(1000) // 非常に大きな指数
modulus := big.NewInt(101) // 割る数
// 方法1: Exp() を使用 (推奨される効率的な方法)
// z = (x^y) mod m
resultExp := new(big.Int)
resultExp.Exp(base, exponent, modulus)
fmt.Printf("(%s^%s) %% %s using Exp(): %s\n",
base.String(), exponent.String(), modulus.String(), resultExp.String())
// 方法2: 最初に冪乗を計算し、その後で Mod() を使用 (非効率)
// exponent が大きい場合、powResult は非常に大きくなる
powResult := new(big.Int)
powResult.Exp(base, exponent, nil) // modulus を nil にすると単純な冪乗
// fmt.Printf("powResult (too big to print reliably): %s\n", powResult.String()) // 非常に長くなる可能性あり
resultMod := new(big.Int)
resultMod.Mod(powResult, modulus)
fmt.Printf("(%s^%s) %% %s using Mod() after Exp(nil): %s\n",
base.String(), exponent.String(), modulus.String(), resultMod.String())
}
解説
- したがって、冪剰余が目的であれば
Exp()
の第三引数を利用するのが常に正しい選択です。 - もし
modulus
がnil
の場合、単純に冪乗(base^exponent
)を計算します。その後でMod()
を使うことも可能ですが、base^exponent
が非常に大きくなるとメモリを大量に消費したり、計算が非効率になったりします。 Exp(base, exponent, modulus)
は、モジュラスm
が指定されている場合、途中の計算で数が爆発的に大きくなるのを防ぎながら剰余を計算します。
これはbig.Int
の代替ではありませんが、もし扱う数値がint64
の範囲に収まるのであれば、math/big
パッケージを使う必要はなく、Goの組み込みの%
演算子で十分です。
package main
import "fmt"
func main() {
var a int64 = 1234567890
var b int64 = 7
result := a % b
fmt.Printf("%d %% %d = %d\n", a, b, result) // 剰余計算
}
- ほとんどの一般的な整数演算では、組み込み型と
%
演算子で十分であり、big.Int
を使うとオーバーヘッドが発生します。 - 扱いたい数が多倍長整数である場合にのみ
math/big
を使用します。
- 範囲が収まる場合: 組み込みの
int64
型と%
演算子を使用します。 - 関連するが異なる目的:
big.Int.ModInverse()
: モジュラ逆数を計算する。big.Int.Exp()
: 冪剰余を効率的に計算する。これは、$ (x^y) \pmod m $ の形式の計算が必要な場合に、Mod()
の「代替」としてではなく「より適切な選択」として使われます。
big.Int.Mod()
の直接的な代替: 基本的には存在しません。これは多倍長整数の一般的な剰余計算のための標準的なメソッドです。