Go言語 big.Int.Text() の徹底解説:大きな整数を文字列に変換する方法

2025-06-01

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

  • 戻り値
    変換された整数を表す文字列と、エラー(通常は nil)を返します。
  • 基数指定
    変換後の文字列が何進数で表現されるかを指定できる。例えば、10進数、2進数、16進数など。
  • 目的
    big.Int 型の値を人間が読みやすい文字列形式に変換する。

メソッドのシグネチャ

func (z *Int) Text(base int) string
  • error: 通常は nil が返されます。基数が不正な範囲外の場合など、エラーが発生する可能性はありますが、一般的な使用法ではあまり意識する必要はありません。
  • string: 変換された整数を表す文字列が返されます。
  • (base int): 変換後の文字列の基数(進数)を指定する整数です。一般的に使用される基数は 2 (2進数), 8 (8進数), 10 (10進数), 16 (16進数) などです。基数が 2 から 36 の範囲外の場合、結果は予測できません。
  • (z *Int): このメソッドが適用される big.Int 型のポインタ z です。
package main

import (
	"fmt"
	"math/big"
)

func main() {
	n := new(big.Int)
	n.SetString("12345678901234567890", 10) // 10進数の文字列から big.Int を作成

	// 10進数で文字列化
	decimalString := n.Text(10)
	fmt.Println("10進数:", decimalString) // 出力: 10進数: 12345678901234567890

	// 2進数で文字列化
	binaryString := n.Text(2)
	fmt.Println("2進数:", binaryString) // 出力: 2進数: 101011001110001101010001011010001011100110100010110

	// 16進数で文字列化
	hexadecimalString := n.Text(16)
	fmt.Println("16進数:", hexadecimalString) // 出力: 16進数: 2bce3627b9d31a

	// 基数を変えてみる (例: 36進数)
	base36String := n.Text(36)
	fmt.Println("36進数:", base36String) // 出力例: 36進数: 21hjy41f7m
}


無効な基数 (Invalid Base)

  • トラブルシューティング
    • base 引数が 2 から 36 の範囲内であることを確認してください。
    • ユーザーからの入力や設定値を使用している場合は、範囲チェックを行うようにしてください。
  • 原因
    Text() メソッドに渡す base 引数が、許容される範囲 (通常は 2 から 36) 外の値である場合に発生します。
  • エラー
    panic (実行時エラー) が発生する可能性があります。Goのバージョンによっては、エラーとして返される場合もあります。


package main

import (
	"fmt"
	"math/big"
)

func main() {
	n := big.NewInt(100)
	invalidBase := 1 // 許容範囲外の基数

	// panic が発生する可能性 (Goのバージョンによる)
	// result := n.Text(invalidBase)
	// fmt.Println(result)

	// 適切な範囲の基数を使用する
	validBase := 16
	hexString := n.Text(validBase)
	fmt.Println("16進数:", hexString) // 出力: 16進数: 64
}

大きすぎる big.Int の文字列化によるメモリ消費

  • トラブルシューティング
    • 扱う big.Int の値が極端に大きくならないか、事前にサイズの見積もりを検討してください。
    • 必要以上に大きな数を文字列化しないように、処理のロジックを見直してください。
    • 大きな数をログ出力する場合などは、必要に応じて一部を切り詰めるなどの工夫を検討してください。
  • 問題
    非常に大きな big.Int の値を Text() で文字列化しようとすると、結果として非常に長い文字列が生成され、大量のメモリを消費する可能性があります。

文字列化された数の再変換時のエラー

  • トラブルシューティング
    • Text() で文字列化した際の基数を記録しておき、再変換時には同じ基数を SetString() に渡すようにしてください。
  • 問題
    Text() で得られた文字列を SetString() などで big.Int に再変換する際に、元の基数と異なる基数を指定すると、誤った値になったりエラーが発生したりする可能性があります。


package main

import (
	"fmt"
	"math/big"
)

func main() {
	n := new(big.Int)
	n.SetString("FF", 16) // 16進数の "FF" を big.Int に

	hexString := n.Text(16)
	fmt.Println("16進数 (文字列):", hexString) // 出力: 16進数 (文字列): ff

	// 正しい基数で再変換
	n2 := new(big.Int)
	_, ok := n2.SetString(hexString, 16)
	if ok {
		fmt.Println("再変換 (16進数):", n2) // 出力: 再変換 (16進数): 255
	}

	// 誤った基数で再変換 (エラーは発生しないが値が異なる)
	n3 := new(big.Int)
	_, ok = n3.SetString(hexString, 10)
	if ok {
		fmt.Println("再変換 (10進数 - 誤り):", n3) // 出力: 再変換 (10進数 - 誤り): 0
	} else {
		fmt.Println("再変換 (10進数 - 誤り): エラー発生")
	}
}

符号付き整数の扱い

  • トラブルシューティング
    • 文字列化された値を解析する際には、先頭のマイナス記号の有無を考慮する必要があります。
    • 再変換時に符号を正しく扱うように注意してください。
  • 注意点
    big.Int は符号付き整数を扱えます。Text() は、big.Int が負の値の場合、先頭にマイナス記号 "-" を付加した文字列を返します。
  • トラブルシューティング
    • パフォーマンスが重要な場面では、文字列化の頻度を減らす、あるいはより効率的な方法を検討する必要があるかもしれません。
    • 必要に応じて、文字列化する前に数の大きさをある程度制限するなどの対策を検討してください。
  • 問題
    極端に大きな big.Int の値を Text() で文字列化する処理は、計算コストが高くなる可能性があります。


例1: 基本的な基数変換

この例では、大きな整数を big.Int 型で扱い、それを異なる基数の文字列に変換して表示します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 10進数の文字列から big.Int を作成
	n := new(big.Int)
	n.SetString("98765432109876543210", 10)

	// 10進数で文字列化
	decimalString := n.Text(10)
	fmt.Println("10進数:", decimalString)

	// 2進数で文字列化
	binaryString := n.Text(2)
	fmt.Println("2進数:", binaryString)

	// 16進数で文字列化
	hexadecimalString := n.Text(16)
	fmt.Println("16進数:", hexadecimalString)

	// 36進数で文字列化
	base36String := n.Text(36)
	fmt.Println("36進数:", base36String)
}

このコードでは、まず SetString() を使って大きな10進数の文字列を big.Int 型の変数 n に格納しています。その後、n.Text() メソッドに異なる基数(10, 2, 16, 36)を指定して呼び出し、それぞれの基数で表現された文字列を fmt.Println() で出力しています。

例2: 負の数の文字列化

big.Int は負の数も扱えます。この例では、負の数を文字列化してみます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 負の数の big.Int を作成
	n := new(big.Int)
	n.SetString("-12345", 10)

	// 10進数で文字列化
	decimalString := n.Text(10)
	fmt.Println("10進数 (負の数):", decimalString)

	// 16進数で文字列化
	hexadecimalString := n.Text(16)
	fmt.Println("16進数 (負の数):", hexadecimalString)
}

負の数を Text() で文字列化すると、結果の文字列の先頭にマイナス記号 "-" が付加されることがわかります。

例3: 大きな数の演算結果を文字列で表示

big.Int は通常の整数型よりも大きな数を扱えるため、その演算結果を Text() で文字列として表示するのに役立ちます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 非常に大きな数を扱う
	a := new(big.Int)
	a.SetString("123456789012345678901234567890", 10)
	b := new(big.Int)
	b.SetString("987654321098765432109876543210", 10)

	// 掛け算を行う
	result := new(big.Int)
	result.Mul(a, b)

	// 結果を10進数の文字列として表示
	resultString := result.Text(10)
	fmt.Println("掛け算の結果:", resultString)
}

この例では、二つの大きな数を big.Int で定義し、Mul() メソッドで掛け算を行っています。その結果も big.Int 型で得られるため、Text(10) を使って10進数の文字列として表示しています。

例4: 特定のフォーマットでの出力 (応用)

Text() メソッドは単に数値を指定された基数の文字列に変換するだけですが、得られた文字列をさらに加工することで、特定のフォーマットで出力することも可能です。

package main

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

func main() {
	n := big.NewInt(255)
	hexString := n.Text(16)

	// 16進数2桁で表示 (必要に応じて0埋め)
	if len(hexString) == 1 {
		hexString = "0" + hexString
	}
	fmt.Println("16進数 (2桁):", strings.ToUpper(hexString)) // 出力: 16進数 (2桁): FF

	bigNumber := new(big.Int)
	bigNumber.SetString("10000000000", 10)
	decimalString := bigNumber.Text(10)

	// 3桁ごとにカンマを追加 (簡単な例)
	var formattedString string
	for i := len(decimalString) - 1; i >= 0; i-- {
		formattedString = string(decimalString[i]) + formattedString
		if (len(decimalString)-1-i)%3 == 0 && i != 0 {
			formattedString = "," + formattedString
		}
	}
	fmt.Println("カンマ区切り:", formattedString) // 出力: カンマ区切り: 10,000,000,000
}


fmt.Sprintf と書式指定子

fmt.Sprintf 関数は、書式指定子を使って様々な型の値を文字列に変換できます。big.Int 型の値も、いくつかの書式指定子に対応しています。

  • %x / %X
    16進数 (小文字/大文字、プレフィックス 0x は付きません)
  • %o
    8進数 (プレフィックス 0o は付きません)
  • %b
    2進数 (プレフィックス 0b は付きません)
  • %d
    10進数
package main

import (
	"fmt"
	"math/big"
)

func main() {
	n := big.NewInt(12345)

	decimalString := fmt.Sprintf("%d", n)
	fmt.Println("10進数 (Sprintf):", decimalString)

	binaryString := fmt.Sprintf("%b", n)
	fmt.Println("2進数 (Sprintf):", binaryString)

	octalString := fmt.Sprintf("%o", n)
	fmt.Println("8進数 (Sprintf):", octalString)

	hexadecimalStringLower := fmt.Sprintf("%x", n)
	fmt.Println("16進数 (Sprintf - 小文字):", hexadecimalStringLower)

	hexadecimalStringUpper := fmt.Sprintf("%X", n)
	fmt.Println("16進数 (Sprintf - 大文字):", hexadecimalStringUpper)
}

利点

  • 他の型と組み合わせてフォーマット出力する際に便利です。
  • より簡潔なコードで一般的な基数変換を行えます。

欠点

  • 基数変換のエラーハンドリングは fmt.Sprintf 自体にはありません。
  • Text() のように、任意の基数 (2 から 36) を直接指定することはできません。一般的な基数に限定されます。

Int.String() メソッド (10進数限定)

big.Int 型には String() メソッドも用意されています。これは、Text(10) と同様に、big.Int の値を10進数の文字列として返します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	n := big.NewInt(98765)

	decimalString := n.String()
	fmt.Println("10進数 (String()):", decimalString)
}

利点

  • デフォルトの文字列表現としてよく使われます。
  • 10進数への変換に特化しており、Text(10) より記述が短くなります。

欠点

  • 10進数以外の基数への変換はできません。

Int.Format() メソッド (より高度なフォーマット)

Int.Format() メソッドは、より詳細なフォーマット指定を可能にします。これを使うと、符号の扱い、幅、精度、および様々な基数での文字列化を制御できます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	n := big.NewInt(-12345)

	// 10進数
	decimalString := n.Format('d', 10)
	fmt.Println("10進数 (Format 'd'):", decimalString)

	// 16進数 (小文字)
	hexadecimalStringLower := n.Format('x', 16)
	fmt.Println("16進数 (Format 'x'):", hexadecimalStringLower)

	// 16進数 (大文字)
	hexadecimalStringUpper := n.Format('X', 16)
	fmt.Println("16進数 (Format 'X'):", hexadecimalStringUpper)

	// 2進数
	binaryString := n.Format('b', 2)
	fmt.Println("2進数 (Format 'b'):", binaryString)
}

Format() メソッドの第一引数はフォーマットを指定する rune 型の動詞で、第二引数は基数です。

利点

  • 符号の扱いなどをより細かく制御できます。
  • Text() と同様に、様々な基数を指定できます。

欠点

  • Text() ほど直接的ではありません。フォーマット指定子を理解する必要があります。
  • より高度なフォーマット制御 (符号、幅など) や様々な基数
    big.Int.Format() が強力な選択肢となります。
  • 10進数への変換のみ
    big.Int.String() が最も簡潔です。
  • 一般的な基数 (10, 2, 8, 16) での簡単な変換
    fmt.Sprintf が簡潔に記述できます。
  • 任意の基数 (2-36) でのシンプルな変換
    big.Int.Text() が最も直接的で使いやすいです。