[インデックス 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