big.Int.Bytes()だけじゃない!Go言語で多倍長整数を扱う代替メソッド総まとめ

2025-06-01

big.Int.Bytes() とは

big.Int.Bytes() は、*big.Int 型の多倍長整数(任意精度整数)の絶対値を、ビッグエンディアン形式のバイトスライスとして返します。

簡単に言うと:

  • バイトスライス ([]byte): 返される値はGoのバイトスライスです。
  • ビッグエンディアン (big-endian): 数値をバイト列に変換する際、最も上位のバイト(最も大きな位のバイト)が先頭に来る形式です。例えば、0x12345678 は、ビッグエンディアンでは [0x12, 0x34, 0x56, 0x78] となります。
  • 絶対値: 符号(プラスかマイナスか)は含まれず、数値そのものの大きさだけがバイト列として表現されます。
  • 多倍長整数 (arbitrary-precision integer): math/big パッケージは、通常の intint64 では表現できない非常に大きな整数を扱うために使われます。

使い方

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 正の数
	n1 := big.NewInt(123456)
	bytes1 := n1.Bytes()
	fmt.Printf("Number: %s, Bytes (big-endian): %#v\n", n1.String(), bytes1)
	// 出力例: Number: 123456, Bytes (big-endian): []byte{0x1, 0xe2, 0x40}
	// (123456 は 1 * 65536 + 226 * 256 + 64 = 0x01E240 となるため)

	// 負の数 (絶対値が返されることに注目)
	n2 := big.NewInt(-123456)
	bytes2 := n2.Bytes()
	fmt.Printf("Number: %s, Bytes (big-endian): %#v\n", n2.String(), bytes2)
	// 出力例: Number: -123456, Bytes (big-endian): []byte{0x1, 0xe2, 0x40}

	// 非常に大きな数
	n3 := new(big.Int)
	n3.SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16) // 256ビットの最大値
	bytes3 := n3.Bytes()
	fmt.Printf("Number: %s, Bytes (big-endian): %#v\n", n3.Text(16), bytes3)
	// 出力例: Number: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, Bytes (big-endian): []byte{0xff, 0xff, ...}
}

注意点

  1. 符号の欠落: Bytes() メソッドは、数値の絶対値のみをバイトスライスに変換します。元の数値が正か負かの情報は失われます。符号を保持したい場合は、big.Int.Sign() メソッドで符号を取得し、別途保存する必要があります。あるいは、GobEncode()MarshalText() などの別のシリアライズ方法を検討してください。
  2. 可変長: 返されるバイトスライスの長さは、数値の大きさに応じて変化します。例えば、big.NewInt(0) の場合は []byte{}big.NewInt(1) の場合は []byte{0x1} となります。固定長のバイトスライスが必要な場合は、FillBytes() メソッドを使用するか、自分でパディングを行う必要があります。
  3. SetBytes() との組み合わせ: Bytes() の逆の操作は big.Int.SetBytes([]byte) です。SetBytes もまた、符号を扱わず、バイトスライスを符号なしの多倍長整数として解釈します。

big.Int.Bytes() は、以下のようなシナリオでよく利用されます。

  • 暗号操作: 特定の暗号アルゴリズム(例:RSA、ECDSA)では、内部で大きな数値をバイト列として処理する必要があります。
  • 暗号通貨やブロックチェーン: ハッシュ値、秘密鍵、公開鍵、残高など、非常に大きな数値を扱う必要がある場面で、これらの値をバイト表現に変換するために使われます。
  • バイナリ形式でのデータ保存/転送: データベースやネットワークを通じて、大きな整数をバイナリデータとしてやり取りする場合。


    • 問題: Bytes() は数値の「絶対値」を返します。したがって、元の big.Int が負の数であったとしても、返されるバイトスライスからはその情報が失われます。
      n := big.NewInt(-123)
      b := n.Bytes() // b は []byte{0x7b} (123 のビッグエンディアン表現) となる
      fmt.Println(b)
      
    • トラブルシューティング:
      • 符号が必要な場合は、n.Sign() メソッドを使って別途符号を取得し、バイトスライスと一緒に保存/送信します。
      • または、符号も含めてシリアライズしたい場合は、big.Int が提供する GobEncode()MarshalText() などのメソッドの使用を検討してください。これらは符号情報も保持します。
      • 例:
        n := big.NewInt(-123)
        sign := n.Sign()
        bytes := n.Bytes() // 常に絶対値
        fmt.Printf("Value: %s, Sign: %d, Bytes: %#v\n", n.String(), sign, bytes)
        
        // 再構築する際
        reconstructedInt := new(big.Int)
        reconstructedInt.SetBytes(bytes)
        if sign == -1 {
            reconstructedInt.Neg(reconstructedInt) // 負の数だった場合、符号を戻す
        }
        fmt.Printf("Reconstructed: %s\n", reconstructedInt.String())
        
  1. ゼロ (0) の表現 (Representation of Zero)

    • 問題: big.NewInt(0).Bytes() は空のバイトスライス ([]byte{}) を返します。これは、0 を表現するためにバイトが不要であるためです。しかし、一部のプロトコルやシステムでは、0 を []byte{0x00} のように明示的な1バイトで表現することを期待する場合があります。
      zeroInt := big.NewInt(0)
      zeroBytes := zeroInt.Bytes() // zeroBytes は []byte{}
      fmt.Printf("0 as Bytes: %#v\n", zeroBytes)
      
    • トラブルシューティング:
      • 0 を []byte{0x00} として扱いたい場合は、Bytes() の結果が空であるかどうかをチェックし、必要に応じて []byte{0x00} を挿入するロジックを追加します。
      • 逆に、SetBytes([]byte{})0 を設定することに注意してください。
      func intToBytes(i *big.Int) []byte {
          if i.Cmp(big.NewInt(0)) == 0 {
              return []byte{0x00} // または、プロトコルに応じた固定長
          }
          return i.Bytes()
      }
      
      func bytesToInt(b []byte) *big.Int {
          if len(b) == 1 && b[0] == 0x00 {
              return big.NewInt(0)
          }
          return new(big.Int).SetBytes(b)
      }
      
      // 使用例
      fmt.Printf("0 as Bytes (custom): %#v\n", intToBytes(big.NewInt(0))) // []byte{0x00}
      fmt.Printf("123 as Bytes (custom): %#v\n", intToBytes(big.NewInt(123))) // []byte{0x7b}
      
  2. バイトエンディアンの誤解 (Endianness Misunderstanding)

    • 問題: Bytes() は常にビッグエンディアン形式でバイトスライスを返します。もし、データをリトルエンディアン形式で期待するシステムとやり取りする場合、バイト順序の不一致により誤った数値として解釈される可能性があります。
      • 例: 0x1234
        • ビッグエンディアン: [0x12, 0x34]
        • リトルエンディアン: [0x34, 0x12]
    • トラブルシューティング:
      • 相手側がリトルエンディアンを期待する場合は、Bytes() で得られたバイトスライスを反転させる必要があります。
      import "encoding/binary" // 必要であれば
      
      func reverseBytes(data []byte) []byte {
          for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
              data[i], data[j] = data[j], data[i]
          }
          return data
      }
      
      n := big.NewInt(0x1234)
      bigEndianBytes := n.Bytes() // [0x12, 0x34]
      littleEndianBytes := reverseBytes(append([]byte{}, bigEndianBytes...)) // [0x34, 0x12] (元のスライスを変更しないためにコピー)
      fmt.Printf("Big Endian: %#v, Little Endian: %#v\n", bigEndianBytes, littleEndianBytes)
      
      • 通常、ネットワーク通信ではビッグエンディアンが推奨されることが多いですが、特定のプロトコルやハードウェアではリトルエンディアンが使われることがあるため、仕様をよく確認することが重要です。
  3. 固定長パディングの必要性 (Need for Fixed-Length Padding)

    • 問題: Bytes() が返すバイトスライスの長さは、数値の大きさに応じて可変です。例えば、0x01 は []byte{0x01}、0x0100 は []byte{0x01, 0x00} となります。しかし、データベースの固定長フィールドや暗号化アルゴリズムなどで、常に特定のバイト長(例:256ビットの場合は32バイト)を要求される場合があります。
    • トラブルシューティング:
      • 必要な長さに合わせて、返されたバイトスライスの前(ビッグエンディアンの場合)にゼロバイトをパディングします。
      • big.Int には FillBytes(buf []byte) メソッドがあり、指定したバイトスライスを埋めることができます。これは、固定長のバッファに書き込みたい場合に便利です。
      // 例: 32バイトにパディング
      const desiredLength = 32
      n := big.NewInt(123456)
      
      // n.Bytes() を使用して手動でパディング
      rawBytes := n.Bytes()
      paddedBytes := make([]byte, desiredLength)
      copy(paddedBytes[desiredLength-len(rawBytes):], rawBytes)
      fmt.Printf("Padded Bytes (manual): %#v\n", paddedBytes)
      
      // FillBytes() を使用
      buf := make([]byte, desiredLength)
      n.FillBytes(buf) // buf はビッグエンディアンで埋められる
      fmt.Printf("Padded Bytes (FillBytes): %#v\n", buf)
      
      • FillBytes()buf を埋めるので、buf の長さが数値のバイト表現よりも短い場合、上位のバイトは切り捨てられます(下位のバイトで buf を埋め、残りは切り捨てられる)。
  4. SetBytes() との誤解 (Misunderstanding with SetBytes())

    • 問題: Bytes() が常に絶対値を返すのと同様に、SetBytes() もバイトスライスを「符号なし」の多倍長整数として解釈します。したがって、Bytes() で取得したバイトスライスをそのまま SetBytes() に渡すと、元の数値が負の数だった場合に正の数として解釈されてしまいます。
      original := big.NewInt(-10)
      b := original.Bytes() // b は []byte{0xa} (10 の絶対値)
      reconstructed := new(big.Int).SetBytes(b) // reconstructed は 10
      fmt.Printf("Original: %s, Reconstructed: %s\n", original.String(), reconstructed.String())
      
    • トラブルシューティング: 上記1.で説明したように、符号情報を別途管理し、SetBytes() で値を設定した後に符号を適用する必要があります。

big.Int.Bytes() を使う際は、以下の点に特に注意してください。

  • 可変長: 数値の大きさに応じてバイトスライスの長さが変わる。固定長が必要ならパディング(または FillBytes())を行う。
  • エンディアン: 常にビッグエンディアンで返される。リトルエンディアンが必要ならバイト列を反転させる。
  • ゼロの表現: 0 は空のバイトスライス []byte{} として返される。特定のプロトコルで []byte{0x00} が必要なら変換する。
  • 符号の扱い: Bytes() は絶対値のみを返す。負の数では符号情報が失われるため、必要なら別途扱う。


基本的な使用法と符号の挙動

big.Int.Bytes() は数値の絶対値をビッグエンディアン形式で返します。負の数の場合でも、絶対値が返されることに注意してください。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("--- 1. 基本的な使用法と符号の挙動 ---")

	// 正の整数
	numPos := big.NewInt(1234567890)
	bytesPos := numPos.Bytes()
	fmt.Printf("Positive Number: %s\n", numPos.String())
	fmt.Printf("Bytes (Big-Endian): %#v\n", bytesPos)
	// 1234567890 は 0x499602d2 なので、[]byte{0x49, 0x96, 0x02, 0xd2}

	// 負の整数 (絶対値が返される)
	numNeg := big.NewInt(-1234567890)
	bytesNeg := numNeg.Bytes()
	fmt.Printf("Negative Number: %s\n", numNeg.String())
	fmt.Printf("Bytes (Absolute Value, Big-Endian): %#v\n", bytesNeg)
	// bytesPos と bytesNeg は同じ結果になることに注目

	// ゼロ
	numZero := big.NewInt(0)
	bytesZero := numZero.Bytes()
	fmt.Printf("Zero: %s\n", numZero.String())
	fmt.Printf("Bytes (Empty slice for 0): %#v\n", bytesZero)
	// 0 は空のバイトスライス []byte{} となる
}

Bytes() と SetBytes() を使った数値の保存と復元(符号の考慮)

Bytes() で符号情報が失われるため、負の数を正確に復元するには追加の処理が必要です。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 2. `Bytes()` と `SetBytes()` を使った数値の保存と復元(符号の考慮) ---")

	// 保存する元の数値
	originalNum := big.NewInt(-9876543210)

	// 数値をバイトスライスに変換 (符号は含まれない)
	absBytes := originalNum.Bytes()
	// 符号情報を別途取得
	sign := originalNum.Sign() // -1: 負, 0: ゼロ, 1: 正

	fmt.Printf("Original Number: %s\n", originalNum.String())
	fmt.Printf("Absolute Value Bytes: %#v\n", absBytes)
	fmt.Printf("Sign: %d\n", sign)

	// バイトスライスから数値を復元
	reconstructedNum := new(big.Int).SetBytes(absBytes)

	// 符号を適用して元の数値に戻す
	if sign == -1 {
		reconstructedNum.Neg(reconstructedNum) // 負の数だった場合、符号を反転
	} else if sign == 0 && len(absBytes) == 0 {
		// 0 は Bytes() で空スライスになるため、SetBytes() で復元すると 0 になるが、
		// 意図的に 0x00 バイトなどを使う場合は注意が必要
		// この例では SetBytes([]) は 0 になるので特別な処理は不要
	}

	fmt.Printf("Reconstructed Number: %s\n", reconstructedNum.String())
	fmt.Printf("Original and Reconstructed are equal: %t\n", originalNum.Cmp(reconstructedNum) == 0)
}

固定長バイトスライスへのパディング

特定のプロトコル(例:暗号関連)では、数値を固定長のバイトスライスとして扱う必要があります。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 3. 固定長バイトスライスへのパディング ---")

	const desiredLength = 32 // 例: 256ビット (32バイト)

	// 小さい数
	numSmall := big.NewInt(42)
	bytesSmall := numSmall.Bytes()
	paddedSmall := make([]byte, desiredLength)
	copy(paddedSmall[desiredLength-len(bytesSmall):], bytesSmall) // 後ろからコピー(ビッグエンディアン)
	fmt.Printf("Number: %s\n", numSmall.String())
	fmt.Printf("Raw Bytes: %#v\n", bytesSmall)
	fmt.Printf("Padded to %d bytes: %#v\n", desiredLength, paddedSmall)

	// 大きい数 (32バイトを超える場合)
	// FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF (256ビットの最大値)
	numLarge := new(big.Int)
	numLarge.SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
	bytesLarge := numLarge.Bytes()
	paddedLarge := make([]byte, desiredLength) // この場合、サイズはぴったり
	copy(paddedLarge[desiredLength-len(bytesLarge):], bytesLarge)
	fmt.Printf("Number: %s\n", numLarge.Text(16))
	fmt.Printf("Raw Bytes: %#v\n", bytesLarge)
	fmt.Printf("Padded to %d bytes: %#v\n", desiredLength, paddedLarge)
	// この例ではぴったり32バイトになるため、パディングは行われない

	// `FillBytes()` を使うとより簡単
	numFill := big.NewInt(256)
	bufFill := make([]byte, desiredLength) // desiredLength のバッファを作成
	numFill.FillBytes(bufFill) // バッファにビッグエンディアンで埋める
	fmt.Printf("Number (FillBytes): %s\n", numFill.String())
	fmt.Printf("FillBytes result (%d bytes): %#v\n", desiredLength, bufFill)
	// 256 (0x0100) は []byte{0x01, 0x00}
	// FillBytes は bufFill の後方から埋めていくため、結果は []byte{0x00, ..., 0x00, 0x01, 0x00} となる
}

バイトエンディアンの変換(ビッグエンディアン ↔ リトルエンディアン)

Bytes() はビッグエンディアンなので、リトルエンディアンが必要な場合はバイト列を反転させる必要があります。

package main

import (
	"fmt"
	"math/big"
)

// reverseBytes はバイトスライスを反転させるヘルパー関数
func reverseBytes(data []byte) []byte {
	// 元のスライスをin-placeで反転させるが、
	// 呼び出し元が変更されるのを避けたい場合はコピーしてから反転する
	// この例ではコピーしてから反転する
	reversed := make([]byte, len(data))
	copy(reversed, data)

	for i, j := 0, len(reversed)-1; i < j; i, j = i+1, j-1 {
		reversed[i], reversed[j] = reversed[j], reversed[i]
	}
	return reversed
}

func main() {
	fmt.Println("\n--- 4. バイトエンディアンの変換 ---")

	num := big.NewInt(0x12345678) // 16進数で表現

	// big.Int.Bytes() はビッグエンディアンを返す
	bigEndianBytes := num.Bytes()
	fmt.Printf("Number: %#x\n", num.Uint64()) // Uint64() は uint64 で表現可能な場合のみ
	fmt.Printf("Big-Endian Bytes: %#v\n", bigEndianBytes)

	// リトルエンディアンに変換
	littleEndianBytes := reverseBytes(bigEndianBytes)
	fmt.Printf("Little-Endian Bytes: %#v\n", littleEndianBytes)

	// リトルエンディアンのバイトスライスから big.Int を復元する例 (reverseBytesしてからSetBytes)
	reconstructedFromLittleEndian := new(big.Int).SetBytes(reverseBytes(littleEndianBytes))
	fmt.Printf("Reconstructed from Little-Endian: %#x\n", reconstructedFromLittleEndian.Uint64())
	fmt.Printf("Original and Reconstructed from LE are equal: %t\n", num.Cmp(reconstructedFromLittleEndian) == 0)
}


big.Int.Bytes() は数値の絶対値ビッグエンディアンのバイトスライスとして返しますが、それ以外の要件がある場合に利用できる代替メソッドを説明します。

符号付きバイナリ表現: GobEncode() と GobDecode()

encoding/gob パッケージはGo独自のバイナリエンコーディング形式を提供します。big.IntGobEncoderGobDecoder インターフェースを実装しているため、これらを使って符号付きの数値をバイト列にシリアライズ・デシリアライズできます。

  • 用途:
    • Goプログラム間での big.Int の永続化やネットワーク転送。
    • データベースへの保存(Goで読み書きする場合)。
  • 特徴:
    • 符号情報が保持される: 負の数も正しくエンコード・デコードされます。
    • Goの標準的なシリアライズ形式: io.Writerio.Reader を通じて直接データのやり取りが可能です。
    • 可変長: バイト列の長さは数値の大きさに応じて変化します。
package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("--- 1. 符号付きバイナリ表現: GobEncode() と GobDecode() ---")

	original := big.NewInt(-123456789012345)

	// GobEncode を使ってバイト列に変換
	var buf bytes.Buffer
	encoder := gob.NewEncoder(&buf)
	err := encoder.Encode(original)
	if err != nil {
		fmt.Println("GobEncode error:", err)
		return
	}
	gobEncodedBytes := buf.Bytes()
	fmt.Printf("Original: %s\n", original.String())
	fmt.Printf("Gob Encoded Bytes: %#v\n", gobEncodedBytes)

	// GobDecode を使ってバイト列から復元
	decoder := gob.NewDecoder(&buf) // buf はすでにデータを保持
	reconstructed := new(big.Int)
	err = decoder.Decode(reconstructed)
	if err != nil {
		fmt.Println("GobDecode error:", err)
		return
	}
	fmt.Printf("Reconstructed: %s\n", reconstructed.String())
	fmt.Printf("Original and Reconstructed are equal: %t\n", original.Cmp(reconstructed) == 0)
}

固定長バイト列への書き込み: FillBytes(buf []byte)

FillBytes() は、指定されたバイトスライス buf の末尾から big.Int の絶対値をビッグエンディアン形式で書き込みます。Bytes() が新しいスライスを返すのに対し、FillBytes() は既存のスライスを埋めます。

  • 用途:
    • ハッシュ計算や暗号化における固定長の数値表現。
    • ブロックチェーンなど、固定長のデータ構造を扱うシステム。
  • 特徴:
    • 固定長バッファへの書き込み: あらかじめ確保されたバッファに書き込むため、固定長のデータが必要な場合に便利です。
    • 絶対値: Bytes() と同様に絶対値が書き込まれます。
    • ビッグエンディアン: Bytes() と同様にビッグエンディアンです。
    • オーバーフロー/アンダーフロー: buf が小さすぎる場合は上位ビットが切り捨てられ、大きすぎる場合は上位バイトがゼロで埋められます。
package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 2. 固定長バイト列への書き込み: FillBytes(buf []byte) ---")

	num := big.NewInt(65535) // 0xFFFF
	const bufferSize = 8    // 8バイトのバッファ

	// バッファを作成
	buf := make([]byte, bufferSize)

	// FillBytes を使ってバッファに書き込む
	// 65535 (0xFFFF) は 2バイトなので、残りの6バイトは0で埋められる
	// 結果は [0x00 0x00 0x00 0x00 0x00 0x00 0xFF 0xFF] となる
	filledBuf := num.FillBytes(buf) // FillBytes は引数で渡されたバッファを返す
	fmt.Printf("Number: %s\n", num.String())
	fmt.Printf("Filled Buffer (%d bytes): %#v\n", bufferSize, filledBuf)

	// バッファが小さすぎる場合 (上位ビットが切り捨てられる)
	numTooBig := big.NewInt(0x123456789ABCDEF0) // 8バイト
	smallBuf := make([]byte, 4)                // 4バイトバッファ
	filledSmallBuf := numTooBig.FillBytes(smallBuf)
	fmt.Printf("Number (Too Big): %#x\n", numTooBig.Uint64())
	fmt.Printf("Small Buffer (%d bytes, truncated): %#v\n", 4, filledSmallBuf)
	// 結果は [0x9A 0xBC 0xDE 0xF0] となり、上位の 0x12345678 は切り捨てられる
}

文字列表現: String(), Text(base int), SetString(s string, base int)

数値を人間が読める文字列形式に変換したり、文字列から数値に変換したりする場合に使用します。

  • 用途:
    • ユーザーインターフェースでの表示。
    • 設定ファイルやテキストベースのプロトコルでの数値のやり取り。
    • デバッグ。
  • 特徴:
    • 可読性: デバッグやログ出力に最適です。
    • 符号情報が保持される: String()Text() は符号を文字列として表現します。
    • 基数 (Base): 任意の基数(2進数、10進数、16進数など)で表現できます。
package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 3. 文字列表現: String(), Text(base int), SetString(s string, base int) ---")

	num := big.NewInt(-12345)

	// String() (10進数)
	str10 := num.String()
	fmt.Printf("String (base 10): %s\n", str10) // "-12345"

	// Text(base) (任意の基数)
	str16 := num.Text(16) // 16進数 (小文字のa-f)
	fmt.Printf("Text (base 16): %s\n", str16) // "-3039"

	str2 := num.Text(2) // 2進数
	fmt.Printf("Text (base 2): %s\n", str2) // "-1100000011001"

	// SetString(s string, base int) で文字列から復元
	reconstructed10, ok10 := new(big.Int).SetString("-987654321", 10)
	if ok10 {
		fmt.Printf("SetString (base 10): %s\n", reconstructed10.String())
	}

	reconstructed16, ok16 := new(big.Int).SetString("deadbeef", 16)
	if ok16 {
		fmt.Printf("SetString (base 16): %s\n", reconstructed16.Text(16))
	} else {
		fmt.Println("Failed to parse hex string.")
	}
}

固定長ビット数を持つ表現 (例: 暗号化ライブラリとの連携)

big.Int 自体には特定のビット長を強制するメソッドはありませんが、暗号化ライブラリなどと連携する場合、特定ビット長(例:256ビット)でのバイト表現が必要になることがあります。

  • Bytes()FillBytes() で得られたバイトスライスを、さらにパディングしたり、特定のビット数に合わせた処理(例:最上位ビットのゼロ削除)を行ったりします。
package main

import (
	"fmt"
	"math/big"
)

func main() {
	fmt.Println("\n--- 4. 固定長ビット数を持つ表現 (例: 暗号化ライブラリとの連携) ---")

	// 256ビット (32バイト) で表現したい場合
	const bitLength = 256
	const byteLength = bitLength / 8 // 32バイト

	// 256ビットより小さい数値
	numSmall := big.NewInt(12345) // 0x3039
	paddedBytesSmall := make([]byte, byteLength)
	// Bytes() で取得し、手動でパディングするか、FillBytes() を使う
	numSmall.FillBytes(paddedBytesSmall)
	fmt.Printf("Number: %s\n", numSmall.String())
	fmt.Printf("Padded to %d bytes (256-bit equivalent): %#v\n", byteLength, paddedBytesSmall)

	// 256ビットの最大値
	max256Bit := new(big.Int)
	max256Bit.SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
	paddedBytesMax := make([]byte, byteLength)
	max256Bit.FillBytes(paddedBytesMax)
	fmt.Printf("Number: %s\n", max256Bit.Text(16))
	fmt.Printf("Padded to %d bytes (256-bit equivalent): %#v\n", byteLength, paddedBytesMax)

	// 注意: 256ビットを超える数値を256ビットバッファに書き込むと切り捨てられる
	numTooBig := new(big.Int)
	numTooBig.SetString("10000000000000000000000000000000000000000000000000000000000000000", 16) // 257ビット目
	paddedBytesTooBig := make([]byte, byteLength)
	numTooBig.FillBytes(paddedBytesTooBig)
	fmt.Printf("Number: %s\n", numTooBig.Text(16))
	fmt.Printf("Padded to %d bytes (truncated): %#v\n", byteLength, paddedBytesTooBig)
	// 最上位の1が切り捨てられることに注目 (結果は全て0xffのバイト列になる)
}

big.Int.Bytes() は、big.Int絶対値ビッグエンディアンのバイトスライスとして取得する最も直接的な方法です。しかし、以下の要件がある場合は、代替メソッドの利用を検討してください。

  • 特定のビット長に厳密に合わせたい場合: FillBytes() とパディングの組み合わせ。
  • 人間が読める形式で表現したい場合: String()Text().
  • 固定長のバッファに書き込みたい場合: FillBytes().
  • 符号情報が必要な場合: GobEncode()/GobDecode() または符号を別途管理。