【Go言語】big.Int.Add() の使い方:サンプルコードとエラー対策

2025-06-01

具体的には、次のような働きをします。

メソッドのシグネチャ

func (z *Int) Add(x, y *Int) *Int

説明

  • *Int: このメソッドは、演算結果が格納されたレシーバー z へのポインターを返します。これはメソッドチェーンを可能にするためです。
  • (y *Int): 加算するもう一方の big.Int 型の値へのポインターです。
  • (x *Int): 加算する一方の big.Int 型の値へのポインターです。
  • (z *Int): これはレシーバーと呼ばれる部分で、演算結果を格納する big.Int 型のポインターです。Add() メソッドを呼び出す際には、結果を格納したい big.Int 型の変数(のアドレス)に対して呼び出します。

動作

big.Int.Add(x, y) は、big.Int 型の値 xy を加算し、その結果をレシーバーである big.Int 型の値 z に格納します。つまり、数学的には以下の演算を行います。

z=x+y

重要な点

  • 引数 xybig.Int 型へのポインターである必要があるため、通常は & 演算子を使って変数のアドレスを渡します。
  • Add() メソッドは、レシーバー z の値を変更します。もし元の z の値を保持しておきたい場合は、事前にコピーを作成する必要があります。
  • big.Int 型は、通常の整数型のように直接 + 演算子を使って加算することはできません。必ず Add() メソッドを使用する必要があります。

使用例

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(1000000000000000000) // 非常に大きな整数
	b := big.NewInt(2000000000000000000) // 非常に大きな整数
	result := new(big.Int)              // 結果を格納する新しい big.Int 型の変数

	result.Add(a, b) // a と b を加算し、結果を result に格納

	fmt.Println("a:", a)
	fmt.Println("b:", b)
	fmt.Println("a + b =", result)
}

この例では、二つの大きな整数 abbig.NewInt() で作成し、result.Add(a, b) によってそれらの和を計算して result に格納しています。そして、その結果を fmt.Println() で出力しています。



nil ポインターの参照 (Panic: runtime error: invalid memory address or nil pointer dereference)

  • 解決策
    big.NewInt() を使用して big.Int 型の変数を適切に初期化するか、new(big.Int) でポインターを割り当ててから使用します。上記の例であれば、var a *big.Inta := big.NewInt(0) のように修正します。

  • package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	var a *big.Int // 初期化されていないポインター
    	b := big.NewInt(10)
    	result := new(big.Int)
    
    	result.Add(a, b) // a が nil なので panic が発生する可能性
    	fmt.Println(result)
    }
    
  • 原因
    big.Int 型のポインターが nil の状態で Add() メソッドを呼び出そうとすると発生します。これは、big.Int 型の変数を初期化せずに使用した場合によく起こります。

引数が nil の場合 (Panic: runtime error: invalid memory address or nil pointer dereference)

  • 解決策
    Add() メソッドに渡す big.Int 型のポインターが nil でないことを事前に確認するか、適切に初期化された big.Int 型の変数を渡します。

  • package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	a := big.NewInt(5)
    	var b *big.Int // nil ポインター
    	result := new(big.Int)
    
    	result.Add(a, b) // b が nil なので panic が発生する可能性
    	fmt.Println(result)
    }
    
  • 原因
    Add() メソッドに渡す引数 (x または y) が nil ポインターである場合にも、同様のランタイムエラーが発生します。

結果の格納先 (z) が意図しない値になっている

  • 解決策
    結果を格納する新しい big.Int 型の変数を作成するか、既存の値を保持したい場合は .Set() メソッドなどを使ってコピーを作成してから Add() を呼び出します。

  • package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	initialValue := big.NewInt(100)
    	a := big.NewInt(50)
    	b := big.NewInt(20)
    
    	result := initialValue // result は initialValue と同じポインターを参照
    	result.Add(a, b)
    
    	fmt.Println("initialValue:", initialValue) // initialValue も変更される
    	fmt.Println("result:", result)
    }
    
  • 原因
    Add() メソッドはレシーバー (z) の値を直接変更します。もし、z が既存の値を持っており、それを保持したい場合は、Add() を呼び出す前にコピーを作成する必要があります。

型の不一致 (コンパイルエラー)

  • 解決策
    加算する整数が通常の整数型の場合は、big.NewInt() を使用して big.Int 型の値に変換してから Add() メソッドに渡します。

  • package main
    
    import (
    	"fmt"
    	"math/big"
    )
    
    func main() {
    	a := big.NewInt(5)
    	b := 10 // int 型
    	result := new(big.Int)
    
    	// result.Add(a, b) // コンパイルエラー: cannot use b (variable of type int) as *big.Int value in argument to (*big.Int).Add
    	result.Add(a, big.NewInt(int64(b))) // 正しい方法
    	fmt.Println(result)
    }
    
  • 原因
    Add() メソッドの引数は *big.Int 型である必要があります。通常の int 型や int64 型の値を直接渡すと、コンパイルエラーが発生します。

大きすぎる数の扱い (パフォーマンス)

  • 解決策
    可能な範囲で標準の整数型を使用するか、アルゴリズムを見直して演算回数を減らすなどの工夫が必要になる場合があります。big.Int の操作は、通常の整数演算よりも計算コストが高いことを意識しておきましょう。
  • 原因
    big.Int 型は非常に大きな数を扱えますが、あまりにも巨大な数の演算はパフォーマンスに影響を与える可能性があります。
  • Go のドキュメントを参照する
    math/big パッケージの公式ドキュメントには、各メソッドの詳細な説明や使用例が記載されています。
  • ログ出力
    中間的な変数の値や処理の流れをログ出力することで、どこで問題が発生しているかを確認できます。
  • 簡単な例で試す
    問題が複雑な場合に、最小限のコードでエラーを再現できるかどうか試してみると、原因の特定が容易になります。
  • エラーメッセージをよく読む
    コンパイラやランタイムが出力するエラーメッセージは、問題の原因を特定するための重要な情報源です。


基本的な加算

これは先ほども示した基本的な例です。二つの big.Int 型の変数を加算し、結果を別の big.Int 型の変数に格納します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(100)
	b := big.NewInt(200)
	result := new(big.Int)

	result.Add(a, b)
	fmt.Println("a + b =", result) // 出力: a + b = 300
}

既存の big.Int 型の変数への加算

レシーバーとして使用する big.Int 型の変数が既に値を持っている場合、Add() メソッドはその値を上書きします。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	sum := big.NewInt(50)
	addend := big.NewInt(100)

	sum.Add(sum, addend) // sum に addend を加算し、結果を sum に格納
	fmt.Println("sum =", sum)    // 出力: sum = 150
}

複数の加算を連鎖させる

Add() メソッドはレシーバーへのポインターを返すため、メソッドチェーンを使って複数の加算を連続して行うことができます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(10)
	b := big.NewInt(20)
	c := big.NewInt(30)
	result := new(big.Int)

	result.Add(a, b).Add(result, c) // (a + b) + c の順に計算
	fmt.Println("a + b + c =", result) // 出力: a + b + c = 60
}

標準の整数型との組み合わせ (変換が必要)

big.Int 型と標準の int 型や int64 型の値を直接加算することはできません。big.NewInt() を使って big.Int 型に変換する必要があります。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	bigNum := big.NewInt(1000)
	 обычноеЧисло := int64(500)
	result := new(big.Int)

	result.Add(bigNum, big.NewInt(обычноеЧисло))
	fmt.Println("bigNum + обычноеЧисло =", result) // 出力: bigNum + обычноеЧисло = 1500
}

大きな数の加算

big.Int 型の真価を発揮する例です。標準の整数型では扱えないような大きな数を加算できます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	largeNum1, ok := new(big.Int).SetString("123456789012345678901234567890", 10)
	if !ok {
		fmt.Println("Error setting string to big.Int")
		return
	}
	largeNum2, ok := new(big.Int).SetString("987654321098765432109876543210", 10)
	if !ok {
		fmt.Println("Error setting string to big.Int")
		return
	}
	sum := new(big.Int)

	sum.Add(largeNum1, largeNum2)
	fmt.Println("largeNum1 + largeNum2 =", sum)
	// 出力例: largeNum1 + largeNum2 = 111111111011111111111111111100
}

エラー処理 (SetString の戻り値)

上記の例で SetString() を使用しているのは、文字列から big.Int 型への変換が失敗する可能性があるためです。通常、Add() 自体はエラーを返しませんが、関連する操作でエラーが発生する可能性があります。

加算結果による条件分岐

加算結果の big.Int 型の値を他のメソッド(例えば Cmp() で比較)と組み合わせて、条件分岐を行うことができます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(50)
	b := big.NewInt(60)
	sum := new(big.Int)

	sum.Add(a, b)

	if sum.Cmp(big.NewInt(100)) > 0 {
		fmt.Println("合計は 100 より大きいです:", sum) // 出力: 合計は 100 より大きいです: 110
	} else if sum.Cmp(big.NewInt(100)) == 0 {
		fmt.Println("合計は 100 です:", sum)
	} else {
		fmt.Println("合計は 100 より小さいです:", sum)
	}
}


big.Int.Set() と big.Int.Add() の組み合わせ (既存の変数を変更しない場合)

Add() メソッドはレシーバーの値を直接変更しますが、元の値を保持したい場合は、事前にコピーを作成し、そのコピーに対して加算を行うことができます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(100)
	b := big.NewInt(200)
	originalA := new(big.Int).Set(a) // a のコピーを作成
	result := new(big.Int)

	result.Add(originalA, b) // コピーに対して加算
	fmt.Println("originalA:", originalA) // 出力: originalA: 100 (元の値は変わらない)
	fmt.Println("result:", result)     // 出力: result: 300
}

この方法は、元の big.Int 型の変数の値を変更せずに、加算結果を新しい変数に格納したい場合に有効です。

big.Int.Add() を用いた関数化 (再利用性の向上)

複数の場所で big.Int の加算を行う場合、加算処理を関数として定義することで、コードの再利用性と可読性を高めることができます。

package main

import (
	"fmt"
	"math/big"
)

func addBigInts(x, y *big.Int) *big.Int {
	result := new(big.Int)
	result.Add(x, y)
	return result
}

func main() {
	num1 := big.NewInt(50)
	num2 := big.NewInt(75)
	sum := addBigInts(num1, num2)
	fmt.Println("num1 + num2 =", sum) // 出力: num1 + num2 = 125

	num3 := big.NewInt(1000)
	num4 := big.NewInt(2000)
	anotherSum := addBigInts(num3, num4)
	fmt.Println("num3 + num4 =", anotherSum) // 出力: num3 + num4 = 3000
}

big.Int のメソッドチェーンの活用 (簡潔な記述)

複数の演算を連続して行う場合に、メソッドチェーンを利用することでコードを簡潔に記述できます。Add() 自体もレシーバーへのポインターを返すため、他の big.Int のメソッドと組み合わせて使用できます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	a := big.NewInt(10)
	b := big.NewInt(20)
	c := big.NewInt(30)
	result := new(big.Int)

	result.Add(a, b).Add(result, c) // (a + b) + c
	fmt.Println("result =", result)    // 出力: result = 60
}

ライブラリの利用 (特殊な用途)

標準の math/big パッケージ以外にも、特定の用途に特化した外部ライブラリが存在する可能性があります。例えば、暗号処理に関連する大きな整数の演算などでは、専用のライブラリが提供されている場合があります。しかし、一般的な加算処理においては math/big パッケージで十分です。

並行処理 (非常に大規模な演算)

もし非常に大規模な big.Int の加算を多数行う必要がある場合、Go の並行処理の仕組み(goroutine や channel)を利用して処理を並列化することで、全体の処理時間を短縮できる可能性があります。ただし、単純な加算処理ではオーバーヘッドが大きくなる可能性もあるため、慎重な検討が必要です。

package main

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

func concurrentAdd(a, b *big.Int, result *big.Int, wg *sync.WaitGroup) {
	defer wg.Done()
	result.Add(a, b)
}

func main() {
	num1 := big.NewInt(1e10)
	num2 := big.NewInt(2e10)
	sum := new(big.Int)
	var wg sync.WaitGroup

	wg.Add(1)
	go concurrentAdd(num1, num2, sum, &wg)
	wg.Wait()

	fmt.Println("Sum (concurrently):", sum) // 出力例: Sum (concurrently): 30000000000
}
  • big.Int パッケージには、加算以外にも減算 (Sub)、乗算 (Mul)、除算 (Div) など、様々な算術演算を行うメソッドが提供されています。これらのメソッドも Add() と同様の考え方で使用できます。
  • big.Int 型の演算は、標準の整数型に比べて計算コストが高くなる傾向があります。パフォーマンスが重要な場面では、可能な限り標準の整数型を使用することを検討してください。