[インデックス 14610] ファイルの概要
このコミットは、Go言語の標準ライブラリである math/big パッケージの *Int 型に、符号なし64ビット整数 (uint64) を設定および取得するための新しいメソッド SetUint64 と Uint64 を追加するものです。これにより、*Int 型と uint64 型間の変換がより直接的かつ効率的に行えるようになります。
コミット
commit f4fc163d1773b54f422df91680df9efda583d67a
Author: Luit van Drongelen <luitvd@gmail.com>
Date: Tue Dec 11 12:19:10 2012 -0500
math/big: add SetUint64 and Uint64 functions to *Int
Implementation is mostly identical to passing a non-negative int64 to
SetInt64, and calling Int64 with a non-negative value in the *Int.
Fixes #4389.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6929048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f4fc163d1773b54f422df91680df9efda583d67a
元コミット内容
math/big: *Int に SetUint64 および Uint64 関数を追加
実装は、非負の int64 を SetInt64 に渡し、*Int 内の非負の値で Int64 を呼び出すのとほぼ同じです。
Issue #4389 を修正します。
変更の背景
この変更は、Go言語の math/big パッケージにおける *Int 型の機能拡張を目的としています。math/big パッケージは、標準の組み込み整数型 (int, int64, uint64 など) では表現できない非常に大きな整数を扱うための任意精度演算を提供します。
既存の *Int 型には SetInt64 および Int64 メソッドがありましたが、これらは符号付き64ビット整数 (int64) に対応していました。しかし、符号なし64ビット整数 (uint64) は int64 よりも大きな正の値を表現できます(uint64 の最大値は 18,446,744,073,709,551,615、int64 の最大値は 9,223,372,036,854,775,807)。
Issue #4389 は、uint64 型の値を *Int に設定したり、*Int の値を uint64 型として取得したりする直接的な方法がないという問題提起でした。このコミットは、このギャップを埋め、uint64 と *Int 間の変換をより自然かつ効率的に行うための専用メソッドを提供することで、ユーザーエクスペリエンスとコードの明確性を向上させます。特に、uint64 の全範囲を正確に扱う必要がある場合に重要となります。
前提知識の解説
Go言語の math/big パッケージ
math/big パッケージは、Go言語で任意精度の算術演算を行うための機能を提供します。これは、標準の組み込み型では表現できない非常に大きな(または非常に小さな)数値を扱う必要がある場合に不可欠です。
*Int型: 任意精度の整数を表す型です。内部的には、数値は符号と、基数_W(通常は32または64) の「ワード」のシーケンスとして表現されます。- 任意精度: 数の大きさに制限がなく、利用可能なメモリが許す限り、どんなに大きな整数でも正確に表現し、演算することができます。
Go言語の整数型 (int64, uint64)
Go言語には、固定サイズの整数型がいくつかあります。
int64: 符号付き64ビット整数型です。約-9 * 10^18から+9 * 10^18までの範囲の整数を表現できます。uint64: 符号なし64ビット整数型です。0から約+1.8 * 10^19までの範囲の非負整数を表現できます。int64の約2倍の正の範囲を持ちます。
ワード (_W)
math/big パッケージの内部実装では、大きな整数は「ワード」の配列として格納されます。_W は、これらのワードのビット幅を示します。これはコンパイル時のアーキテクチャに依存し、通常は32ビットまたは64ビットです。この _W の値によって、uint64 の値をワードに分解する方法や、ワードから uint64 を再構築する方法が変わってきます。
技術的詳細
このコミットで追加された SetUint64 と Uint64 メソッドは、*Int 型と uint64 型の間で値を変換するためのものです。
SetUint64(x uint64) *Int
このメソッドは、*Int レシーバ z を引数 x (uint64) の値に設定し、z 自身を返します。
z.abs = z.abs.setUint64(uint64(x)): ここが核心部分です。*Intの内部表現であるz.abs(絶対値を示すWordスライス) に対して、uint64値を設定する操作を行います。setUint64はWord型のメソッドで、uint64をWordスライスに変換して格納します。z.neg = false:uint64は常に非負であるため、*Intの符号フラグnegをfalseに設定します。
Uint64() uint64
このメソッドは、*Int レシーバ x の値を uint64 型として返します。ただし、x が uint64 で表現できない場合(例えば、負の値である場合や、uint64 の最大値を超える場合)、結果は未定義となります。これは、Int64() メソッドと同様の挙動です。
if len(x.abs) == 0 { return 0 }:*Intが0を表す場合(absスライスが空の場合)、0を返します。v := uint64(x.abs[0]):*Intの絶対値のワードスライスx.absの最初のワードをuint64にキャストしてvに代入します。if _W == 32 && len(x.abs) > 1 { v |= uint64(x.abs[1]) << 32 }: ここが重要な部分です。もしワードサイズ_Wが32ビットの場合、uint64は2つの32ビットワードで構成されます。したがって、x.absの2番目のワードも取得し、それを32ビット左シフトして最初のワードとビットOR演算 (|) することで、完全な64ビット値を再構築します。_Wが64ビットの場合は、1つのワードでuint64全体を表現できるため、この処理は不要です。
これらの実装は、既存の SetInt64 および Int64 のロジックと類似しており、特に非負の値の扱いにおいて共通のパターンが適用されています。
コアとなるコードの変更箇所
src/pkg/math/big/int.go
// SetUint64 sets z to x and returns z.
func (z *Int) SetUint64(x uint64) *Int {
z.abs = z.abs.setUint64(uint64(x))
z.neg = false
return z
}
// Uint64 returns the int64 representation of x.
// If x cannot be represented in an uint64, the result is undefined.
func (x *Int) Uint64() uint64 {
if len(x.abs) == 0 {
return 0
}
v := uint64(x.abs[0])
if _W == 32 && len(x.abs) > 1 {
v |= uint64(x.abs[1]) << 32
}
return v
}
src/pkg/math/big/int_test.go
var uint64Tests = []uint64{
0,
1,
4294967295, // 2^32 - 1
4294967296, // 2^32
8589934591, // 2^33 - 1
8589934592, // 2^33
9223372036854775807, // Max int64
9223372036854775808, // Max int64 + 1 (Min uint64 > Max int64)
18446744073709551615, // 1<<64 - 1 (Max uint64)
}
func TestUint64(t *testing.T) {
in := new(Int)
for i, testVal := range uint64Tests {
in.SetUint64(testVal)
out := in.Uint64()
if out != testVal {
t.Errorf("#%d got %d want %d", i, out, testVal)
}
str := fmt.Sprint(testVal)
strOut := in.String()
if strOut != str {
t.Errorf("#%d.String got %s want %s", i, strOut, str)
}
}
}
コアとなるコードの解説
int.go の変更
-
SetUint64関数:z.abs = z.abs.setUint64(uint64(x)):*Intの内部表現であるabs(絶対値のワードスライス) を、入力されたuint64値xに基づいて更新します。setUint64はWord型のメソッドであり、uint64をWordスライスに適切に変換して格納する役割を担います。z.neg = false:uint64は常に非負であるため、*Intの符号を負ではない (false) と明示的に設定します。
-
Uint64関数:if len(x.abs) == 0 { return 0 }:*Intが0を表す場合(内部のabsスライスが空の場合)は、0を返します。v := uint64(x.abs[0]):*Intの絶対値のワードスライスx.absの最初のワードをuint64にキャストしてvに代入します。これは、uint64の下位ビットまたは全体(_Wが64の場合)を構成します。if _W == 32 && len(x.abs) > 1 { v |= uint64(x.abs[1]) << 32 }: この条件は、システムが32ビットワード (_W == 32) を使用しており、かつ*Intが複数のワードを必要とするuint64値(つまり、2^32以上の値)を保持している場合に実行されます。この場合、x.absの2番目のワード(上位32ビット)をuint64にキャストし、それを32ビット左シフト (<< 32) して、既存のvとビットOR演算 (|) することで、完全な64ビット値を再構築します。これにより、32ビットシステムでもuint64の全範囲を正しく扱えるようになります。
int_test.go の変更
-
uint64Tests変数:uint64の重要な境界値や代表的な値を網羅したテストケースの配列です。これには、0、1、2^32 - 1(32ビットワードの最大値)、2^32(32ビットワードを超える最小値)、int64の最大値、int64の最大値+1(uint64でのみ表現可能な値)、そしてuint64の最大値 (1<<64 - 1) が含まれています。これにより、SetUint64とUint64が様々な入力に対して正しく動作するかを確認できます。
-
TestUint64関数:uint64Testsの各値に対してループを実行します。in.SetUint64(testVal): テスト値を*Int型のinに設定します。out := in.Uint64():inからuint64値を取得します。if out != testVal: 設定した値と取得した値が一致するかを検証します。一致しない場合はエラーを報告します。str := fmt.Sprint(testVal)とstrOut := in.String():*IntのString()メソッドが、元のuint64値の文字列表現と一致するかどうかも検証しています。これは、値の正確な内部表現と、それが人間が読める形式に正しく変換されることを保証します。
これらのテストは、新しい SetUint64 と Uint64 メソッドが、uint64 の全範囲にわたって正確に機能することを保証するために不可欠です。
関連リンク
- Go Issue #4389: https://github.com/golang/go/issues/4389
- Go CL 6929048: https://golang.org/cl/6929048
参考にした情報源リンク
- Go言語
math/bigパッケージ公式ドキュメント: https://pkg.go.dev/math/big - Go言語の整数型に関する公式ドキュメント: https://go.dev/ref/spec#Numeric_types