【Go言語】big.Int.Scan()を使いこなす:入力例とトラブルシューティング
簡単に言うと、big.Int.Scan()
は 文字列形式の数値を big.Int
型にパース(解析)して代入する ためのメソッドです。
big.Int.Scan()
の主な機能
- エラーハンドリング
パースに失敗した場合(例えば、不正な形式の数値が入力された場合)はエラーを返します。 - 基数の自動判別
入力文字列のプレフィックス(接頭辞)に基づいて、自動的に基数(進数)を判別します。0x
または0X
で始まる場合: 16進数0o
または0O
で始まる場合: 8進数 (Go 1.13以降)0b
または0B
で始まる場合: 2進数 (Go 1.13以降)- それ以外の場合: 10進数
- 入力の読み込み
fmt.Scan
やfmt.Sscan
などの関数と組み合わせて使用することで、標準入力や文字列から数値を読み込むことができます。
package main
import (
"fmt"
"math/big"
"strings"
)
func main() {
var i big.Int
// 1. 標準入力から読み込む例
// fmt.Print("整数を入力してください: ")
// _, err := fmt.Scan(&i)
// if err != nil {
// fmt.Println("エラー:", err)
// } else {
// fmt.Printf("読み込んだ値: %s\n", i.String())
// }
// 2. 文字列から読み込む例 (10進数)
reader := strings.NewReader("12345678901234567890")
_, err := fmt.Fscan(reader, &i)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("文字列から読み込んだ値 (10進数): %s\n", i.String())
}
// 3. 文字列から読み込む例 (16進数)
reader = strings.NewReader("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
_, err = fmt.Fscan(reader, &i)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("文字列から読み込んだ値 (16進数): %s\n", i.String())
}
// 4. 文字列から読み込む例 (不正な入力)
reader = strings.NewReader("abc")
_, err = fmt.Fscan(reader, &i)
if err != nil {
fmt.Println("エラー (不正な入力):", err)
} else {
fmt.Printf("文字列から読み込んだ値 (不正な入力): %s\n", i.String())
}
}
big.Int.Scan()
は fmt.Scanner
インターフェースを実装しているため、fmt.Scan()
、fmt.Sscan()
、fmt.Fscan()
など、fmt.Scanner
を引数に取る関数で使用できます。
無効な入力形式 (Invalid Input Format)
エラーの症状
big.Int.Scan()
は、数値として解釈できない文字列が入力された場合にエラーを返します。例えば、数字以外の文字が含まれていたり、基数指定(0x
など)が誤っていたりする場合です。
よくあるエラーメッセージの例
strconv.ParseInt: parsing "abc": invalid syntax
(実際には fmt.Scan
経由で呼び出されるため、より汎用的なエラーメッセージになることもあります。)
原因
- 意図しないプレフィックス(例: 10進数のつもりで "0x123" と入力)が付いている。
- 予期せぬ空白文字や改行文字が入力の先頭または末尾に存在している。
- 入力文字列に、その基数で許可されない文字が含まれている。
トラブルシューティング
- SetStringの利用
big.Int.Scan()
はfmt.Scanner
インターフェースを実装しているため、自動的に基数を判別します。しかし、明示的に基数を指定してパースしたい場合は、big.Int.SetString(s string, base int)
を使う方が安全で意図が明確になります。i := new(big.Int) s := "0xAF" // 16進数としてパースしたい _, ok := i.SetString(s, 0) // baseを0にするとScan()と同じく自動判別 // または _, ok := i.SetString(s, 16) // 明示的に16進数と指定 if !ok { fmt.Println("パース失敗") }
- 入力ソースの確認
- 標準入力からの場合、ユーザーが正しい形式で入力しているかを確認します。
- ファイルやネットワークからの読み込みの場合、データが破損していないか、エンコーディングが正しいかを確認します。
- エラーハンドリング
fmt.Scan
やfmt.Fscan
が返すerror
を必ずチェックし、エラーが発生した場合はユーザーに適切なフィードバックを返すようにします。 - 入力検証
big.Int.Scan()
を呼び出す前に、入力文字列が有効な数値形式であるかを正規表現などを使って事前に検証することを検討してください。
空の入力 (Empty Input)
エラーの症状
big.Int.Scan()
は、入力が空の場合にもエラーを返すことがあります。
原因
- ファイルから読み込む際に、空行やファイルの終端に達した。
- 標準入力で何も入力せずにEnterキーを押した。
トラブルシューティング
- 事前チェック
bufio.Reader
などを使って入力行を先に読み込み、空であるかをチェックしてからbig.Int.SetString()
を使うことも有効です。 - io.EOFのチェック
fmt.Scan
などは、入力ストリームの終端に達した場合にio.EOF
エラーを返すことがあります。これを適切に処理することで、プログラムが予期せず終了するのを防げます。_, err := fmt.Scan(&i) if err == io.EOF { fmt.Println("入力がありませんでした。") // または、デフォルト値を設定するなど } else if err != nil { fmt.Println("エラー:", err) }
big.Int の初期化忘れ (big.Int not initialized)
エラーの症状
これは Scan()
自体のエラーというより、Goのポインタレシーバのメソッドを使用する際の一般的な誤りです。big.Int
のメソッドは通常ポインタレシーバ *big.Int
で定義されています。
よくある間違い
var i big.Int // ポインタではない
fmt.Scan(i) // これはコンパイルエラーになるか、実行時パニックを引き起こす可能性があります
原因
fmt.Scan
は fmt.Scanner
インターフェースを実装する値(ポインタ)を期待しています。big.Int
のメソッドはポインタレシーバで定義されているため、Scan
メソッドを呼び出すには *big.Int
型の変数が必要です。
トラブルシューティング
- ポインタで宣言
big.Int
型の変数はポインタとして宣言し、new(big.Int)
で初期化するか、&
演算子でアドレスを渡します。var i big.Int // または i := new(big.Int) としても良い _, err := fmt.Scan(&i) // &i を渡す if err != nil { fmt.Println("エラー:", err) } else { fmt.Printf("読み込んだ値: %s\n", i.String()) }
エラーの症状
データベースから big.Int
型の値を読み込もうとした際に、unsupported Scan, storing driver.Value type X into type *big.Int
のようなエラーが発生することがあります。
原因
これは big.Int.Scan()
自体の問題ではなく、データベースドライバーが *big.Int
型を直接サポートしていないために発生します。データベースの数値型(NUMERIC
, DECIMAL
, BIGINT
など)がGoの big.Int
に直接マッピングされない場合、ドライバーは int64
や float64
、または []byte
(文字列形式) として値を返そうとします。
- ドライバーのドキュメント確認
使用しているデータベースドライバーがbig.Int
をサポートしているか、特別な設定が必要かを確認します。一部のドライバーは特定のデータ型に対してカスタムスキャンロジックを提供している場合があります。 - 中間的な型として string または []byte を使用
データベースから一旦文字列 (string
または[]byte
) として読み込み、その後big.Int.SetString()
を使ってbig.Int
に変換します。これが最も一般的な解決策です。var s string err := rows.Scan(&s) // データベースから文字列として読み込む if err != nil { // エラーハンドリング } var bigInt big.Int _, ok := bigInt.SetString(s, 10) // 10進数としてパース if !ok { fmt.Println("SetString 失敗:", s) }
基本的な使用例:標準入力からの読み込み
最も基本的な使い方です。ユーザーがコンソールから入力した数値を big.Int
として読み込みます。
package main
import (
"fmt"
"math/big"
"io" // io.EOF を使うためにインポート
)
func main() {
var bigInt big.Int // big.Int のポインタをScanに渡すため、&bigInt とします
fmt.Println("大きな整数を入力してください:")
// fmt.Scan を使用して標準入力から読み込む
// fmt.Scan は空白区切りで入力を読み取ります
n, err := fmt.Scan(&bigInt) // &bigInt を渡すことに注意
if err != nil {
if err == io.EOF {
fmt.Println("入力がありませんでした (EOF)。")
} else {
fmt.Printf("入力エラーが発生しました: %v\n", err)
}
return
}
if n == 0 {
fmt.Println("何も読み込まれませんでした。")
return
}
fmt.Printf("入力された数値: %s\n", bigInt.String())
fmt.Printf("型の確認: %T\n", bigInt) // big.Int 型であることがわかります
}
解説
- エラーチェックは重要です。特に
io.EOF
は、入力が完全に終了した場合(例えば、ファイルの終端に達した場合)に発生します。 fmt.Scan
は、読み込んだ要素の数とエラーを返します。fmt.Scan(&bigInt)
のように、&
を付けて変数のアドレスを渡す必要があります。これはScan
メソッドがポインタレシーバ (*big.Int
) を持つためです。var bigInt big.Int
でbig.Int
型の変数を宣言します。
文字列からの読み込み (strings.Reader と fmt.Fscan)
標準入力だけでなく、io.Reader
インターフェースを実装する任意のソースから読み込むことができます。ここでは strings.NewReader
を使って文字列から読み込む例を示します。
package main
import (
"fmt"
"math/big"
"strings" // strings.NewReader を使うためにインポート
)
func main() {
var num big.Int
// 10進数の文字列
decimalStr := "987654321098765432109876543210"
reader1 := strings.NewReader(decimalStr)
fmt.Printf("文字列 '%s' を読み込み中...\n", decimalStr)
_, err := fmt.Fscan(reader1, &num)
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("読み込んだ値: %s\n", num.String())
}
fmt.Println("---")
// 16進数の文字列 (0x プレフィックス付き)
hexStr := "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
reader2 := strings.NewReader(hexStr)
fmt.Printf("文字列 '%s' を読み込み中...\n", hexStr)
_, err = fmt.Fscan(reader2, &num) // 同じ big.Int 変数を再利用
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("読み込んだ値: %s (10進数表現)\n", num.String())
}
fmt.Println("---")
// 不正な形式の文字列
invalidStr := "not_a_number"
reader3 := strings.NewReader(invalidStr)
fmt.Printf("文字列 '%s' を読み込み中...\n", invalidStr)
_, err = fmt.Fscan(reader3, &num)
if err != nil {
fmt.Printf("エラー (期待通り): %v\n", err) // このエラーは期待通り
} else {
fmt.Printf("予期せず読み込んだ値: %s\n", num.String())
}
}
解説
- 不正な入力に対してはエラーが返されることを確認できます。
big.Int.Scan()
は、0x
(16進数)、0o
(8進数、Go 1.13+)、0b
(2進数、Go 1.13+) のプレフィックスを自動的に解釈します。プレフィックスがない場合は10進数と見なされます。fmt.Fscan(reader, &num)
は、指定されたreader
からnum
に値をスキャンします。strings.NewReader(s)
は、与えられた文字列s
をio.Reader
として扱うためのオブジェクトを作成します。
bufio.Scanner と big.Int.SetString を組み合わせる(より堅牢な方法)
fmt.Scan
は空白区切りで読み込みますが、1行全体を読み込みたい場合や、より細かい制御が必要な場合は、bufio.Scanner
を使って行を読み込み、その後 big.Int.SetString()
でパースする方が良い場合があります。この方法は、Scan()
が内部的に行っていることと似ていますが、エラーハンドリングや前処理を柔軟に行えます。
package main
import (
"bufio"
"fmt"
"math/big"
"os"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
var bigInt big.Int
fmt.Println("大きな整数を1行ずつ入力してください ('exit' で終了):")
for scanner.Scan() {
line := scanner.Text() // 1行全体を文字列として取得
line = strings.TrimSpace(line) // 前後の空白をトリム
if line == "exit" {
fmt.Println("終了します。")
break
}
if line == "" {
fmt.Println("空の行です。再入力してください。")
continue
}
// big.Int.SetString を使用して文字列からパース
// base が 0 の場合、Scan() と同じように自動的に基数を判別します
_, ok := bigInt.SetString(line, 0)
if !ok {
fmt.Printf("エラー: '%s' は有効な数値ではありません。再入力してください。\n", line)
continue
}
fmt.Printf("入力された数値: %s\n", bigInt.String())
}
if err := scanner.Err(); err != nil {
fmt.Printf("Scanner エラー: %v\n", err)
}
}
解説
SetString
は成功/失敗を示すブール値を返すため、if !ok
でエラーハンドリングを行います。bigInt.SetString(line, 0)
を使って、文字列line
をbig.Int
にパースします。第二引数の0
は、Scan()
と同様に、文字列のプレフィックスに基づいて基数を自動判別することを意味します。strings.TrimSpace(line)
で、入力文字列の先頭と末尾にある空白文字(スペース、タブ、改行など)を削除します。これにより、入力ミスによるパースエラーを防ぎやすくなります。scanner.Text()
で読み込んだ行を文字列として取得します。scanner.Scan()
で次の行を読み込みます。bufio.NewScanner(os.Stdin)
で標準入力を行単位で読み込むためのスキャナーを作成します。
これは big.Int.Scan()
の直接の例ではありませんが、データベースの Scan
メソッドで big.Int
を扱う際によく遭遇する問題とその解決策です。多くのデータベースドライバは直接 *big.Int
をサポートしていないため、中間的に文字列として読み込む必要があります。
package main
import (
"database/sql"
"fmt"
"math/big"
_ "github.com/mattn/go-sqlite3" // 例としてSQLite3ドライバを使用
)
// これは実際にデータベースに接続するダミー関数です。
// 実際のアプリケーションでは、適切なデータベース設定とクエリを使用してください。
func simulateDBScan(value string) (*big.Int, error) {
// 実際には sql.DB.QueryRow().Scan(&dbValue) などが行われます
// ここでは文字列として受け取ったものをそのまま利用します
var bigInt big.Int
_, ok := bigInt.SetString(value, 10) // 10進数としてパースする例
if !ok {
return nil, fmt.Errorf("データベースからの値 '%s' のパースに失敗しました", value)
}
return &bigInt, nil
}
func main() {
// SQLite3 データベースをインメモリで作成 (テスト用)
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
defer db.Close()
// テーブル作成とデータの挿入
_, err = db.Exec(`CREATE TABLE numbers (id INTEGER PRIMARY KEY, large_num TEXT);`)
if err != nil {
panic(err)
}
largeNumStr := "1234567890123456789012345678901234567890"
_, err = db.Exec(`INSERT INTO numbers (large_num) VALUES (?);`, largeNumStr)
if err != nil {
panic(err)
}
// データベースから読み込む
var storedLargeNumStr string // 一旦文字列として受け取る
var storedID int
row := db.QueryRow(`SELECT id, large_num FROM numbers WHERE id = 1;`)
err = row.Scan(&storedID, &storedLargeNumStr) // データベースドライバーは文字列として返す
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("データが見つかりません。")
} else {
fmt.Printf("DBスキャンエラー: %v\n", err)
}
return
}
// 読み込んだ文字列を big.Int に変換
var bigIntFromDB big.Int
_, ok := bigIntFromDB.SetString(storedLargeNumStr, 10) // 10進数としてパース
if !ok {
fmt.Printf("DBからの文字列 '%s' の big.Int への変換に失敗しました。\n", storedLargeNumStr)
return
}
fmt.Printf("DBから読み込んだID: %d\n", storedID)
fmt.Printf("DBから読み込んだ大きな数値: %s\n", bigIntFromDB.String())
}
- この例では、
go-sqlite3
ドライバーを使用しています。実際のデータベース(PostgreSQL, MySQLなど)でも同様のアプローチが一般的です。 - 解決策
データベースの数値列を文字列 (TEXT
またはVARCHAR
) として定義し、Go側でstring
または[]byte
として読み込みます。その後、big.Int.SetString()
を使ってbig.Int
に変換します。 - データベースドライバーは
big.Int
を直接サポートしないことが多いため、sql.Rows.Scan
やsql.Row.Scan
メソッドに*big.Int
を渡してもエラーになります。
big.Int.Scan()
の代替方法
主な代替方法は、big.Int
型の以下のメソッドを利用することです。
big.Int.SetString(s string, base int) (*big.Int, bool)
big.Int.SetBytes(buf []byte) *big.Int
(またはbig.Int.SetBytes([]byte)
のバリアント)big.Int.SetUint64(x uint64) *big.Int
/big.Int.SetInt64(x int64) *big.Int
これらのメソッドは、それぞれ異なる種類の入力から big.Int
の値を設定するために使われます。
big.Int.SetString(s string, base int)
これが big.Int.Scan()
の最も直接的で一般的な代替方法です。特に、すでに文字列として数値を持っている場合に非常に便利です。
特徴
- エラー処理の容易さ
エラーメッセージがScan()
よりも具体的になる傾向があります。 - 成功/失敗の明示
bool
値を返すため、パースが成功したか失敗したかを明確に判断できます。 - 明示的な基数指定
base
引数で明示的に基数(2進数、8進数、10進数、16進数など)を指定できます。base
が0
の場合、big.Int.Scan()
と同様に、文字列のプレフィックス (0x
,0o
,0b
) に基づいて自動的に基数を判別します。プレフィックスがない場合は10進数と見なします。base
が2
から62
の範囲で指定された場合、その基数で文字列を解釈します。
使用例
package main
import (
"fmt"
"math/big"
)
func main() {
var i big.Int
// 1. 10進数の文字列から設定
decimalStr := "123456789012345678901234567890"
fmt.Printf("10進数 '%s' を SetString...\n", decimalStr)
_, ok := i.SetString(decimalStr, 10) // 10進数として明示的に指定
if !ok {
fmt.Printf("エラー: 10進数のパースに失敗しました。\n")
} else {
fmt.Printf("結果: %s\n", i.String())
}
fmt.Println("---")
// 2. 16進数の文字列から設定 (0x プレフィックスなし)
hexStrNoPrefix := "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
fmt.Printf("16進数 '%s' を SetString...\n", hexStrNoPrefix)
_, ok = i.SetString(hexStrNoPrefix, 16) // 16進数として明示的に指定
if !ok {
fmt.Printf("エラー: 16進数のパースに失敗しました。\n")
} else {
fmt.Printf("結果: %s\n", i.String())
}
fmt.Println("---")
// 3. プレフィックス付きで自動判別 (base = 0)
autoDetectHexStr := "0xABCDEF1234567890"
fmt.Printf("自動判別 '%s' (base=0) を SetString...\n", autoDetectHexStr)
_, ok = i.SetString(autoDetectHexStr, 0) // 自動判別
if !ok {
fmt.Printf("エラー: 自動判別のパースに失敗しました。\n")
} else {
fmt.Printf("結果: %s\n", i.String())
}
fmt.Println("---")
// 4. 不正な文字列の例
invalidStr := "hello_world"
fmt.Printf("不正な文字列 '%s' を SetString...\n", invalidStr)
_, ok = i.SetString(invalidStr, 10)
if !ok {
fmt.Printf("エラー: '%s' は有効な数値ではありません。(期待通り)\n", invalidStr)
} else {
fmt.Printf("結果: %s\n", i.String())
}
}
SetString を使うべきシナリオ
fmt.Scan
のような「空白区切り」ではなく、特定の文字列全体をパースしたい場合。- より厳密に基数を指定したい場合。
- ネットワーク経由で文字列として送られてきた数値をパースする場合。
- 設定ファイルやコマンドライン引数で受け取った数値が文字列形式の場合。
- データベースから
TEXT
型として読み込んだ値をbig.Int
に変換したい場合。
big.Int.SetBytes(buf []byte)
このメソッドは、バイトスライスとして表現された大きな数値を big.Int
に設定するために使用されます。通常、ネットワークプロトコルやファイルフォーマットで数値をバイト列として扱う場合に非常に有用です。
特徴
- 符号なし整数として扱われます(負の値を直接設定することはできません。別途符号を扱う必要があります)。
buf
はビッグエンディアン形式のバイト列として解釈されます。
使用例
package main
import (
"fmt"
"math/big"
"encoding/binary" // バイト列の変換に便利
)
func main() {
var i big.Int
// 1. シンプルなバイト列 (10進数の 256)
// [1, 0] はビッグエンディアンで 256
bytes1 := []byte{1, 0}
fmt.Printf("バイト列 %v を SetBytes...\n", bytes1)
i.SetBytes(bytes1)
fmt.Printf("結果: %s\n", i.String()) // 256
fmt.Println("---")
// 2. より大きなバイト列 (例: 64ビット整数をビッグエンディアンで)
var largeUint64 uint64 = 12345678901234567890 // uint64 の最大に近い値
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, largeUint64) // uint64 をビッグエンディアンバイト列に変換
fmt.Printf("バイト列 %v (uint64 %d) を SetBytes...\n", buf, largeUint64)
i.SetBytes(buf)
fmt.Printf("結果: %s\n", i.String())
fmt.Println("---")
// 3. SetString で得られたバイト列を再度 SetBytes
// i.Bytes() はビッグエンディアンのバイト列を返す
originalBigInt := new(big.Int)
originalBigInt.SetString("987654321098765432109876543210", 10)
bytesFromBigInt := originalBigInt.Bytes()
fmt.Printf("元々の big.Int: %s\n", originalBigInt.String())
fmt.Printf("そのバイト列: %v\n", bytesFromBigInt)
var newBigInt big.Int
newBigInt.SetBytes(bytesFromBigInt)
fmt.Printf("バイト列から SetBytes した結果: %s\n", newBigInt.String())
}
SetBytes を使うべきシナリオ
- 既存の
big.Int
のBytes()
メソッドで取得したバイト列を復元する場合。 - バイナリファイルや特定のプロトコルから数値データを読み込む場合。
- 暗号化処理やハッシュ計算など、バイトレベルで数値を扱う必要がある場合。
これらのメソッドは、Goの標準の組み込み型である uint64
や int64
の値を big.Int
に変換するために使用されます。
特徴
uint64
やint64
の範囲内の数値であれば、常に正確に変換されます。- 非常にシンプルで直接的な変換です。
使用例
package main
import (
"fmt"
"math/big"
)
func main() {
var i big.Int
// 1. uint64 から設定
var uVal uint64 = 18446744073709551615 // uint64 の最大値
fmt.Printf("uint64 %d を SetUint64...\n", uVal)
i.SetUint64(uVal)
fmt.Printf("結果: %s\n", i.String())
fmt.Println("---")
// 2. int64 から設定
var iVal int64 = -9223372036854775808 // int64 の最小値
fmt.Printf("int64 %d を SetInt64...\n", iVal)
i.SetInt64(iVal)
fmt.Printf("結果: %s\n", i.String())
}
- APIの引数や戻り値が
uint64
やint64
であるが、内部でbig.Int
として処理する必要がある場合。 - Goの組み込み整数型で計算された結果を、さらに大きな範囲で扱いたい場合。
-
big.Int.SetUint64(x uint64) / big.Int.SetInt64(x int64)
- Goの組み込み整数型から
big.Int
への直接変換が必要な場合。 - 通常、
big.Int
の計算の開始点として使われることが多い。
- Goの組み込み整数型から
-
big.Int.SetBytes(buf []byte)
- バイト列から数値を再構築したい場合に特化。
- バイナリデータ処理や暗号化関連のタスクで必要となる。
-
big.Int.SetString(s string, base int)
- 最も一般的な代替方法。
- 文字列として数値が手元にある場合に、最も柔軟で信頼性の高い方法。
- 明示的な基数指定や、成功/失敗の明確な判定が必要な場合に最適。
- データベースからの読み込みや、設定ファイルのパースなど、様々なIOで役立つ。
-
big.Int.Scan()
fmt.Scan
のような高レベルな入力関数と連携して、スペース区切りの入力(主に標準入力や簡単なファイル入力)から自動的に基数を判別して読み込みたい場合に便利。- しかし、エラー処理や入力の柔軟性では
SetString
に劣る場合がある。