Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14610] ファイルの概要

このコミットは、Go言語の標準ライブラリである math/big パッケージの *Int 型に、符号なし64ビット整数 (uint64) を設定および取得するための新しいメソッド SetUint64Uint64 を追加するものです。これにより、*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: *IntSetUint64 および Uint64 関数を追加

実装は、非負の int64SetInt64 に渡し、*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,615int64 の最大値は 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 を再構築する方法が変わってきます。

技術的詳細

このコミットで追加された SetUint64Uint64 メソッドは、*Int 型と uint64 型の間で値を変換するためのものです。

SetUint64(x uint64) *Int

このメソッドは、*Int レシーバ z を引数 x (uint64) の値に設定し、z 自身を返します。

  • z.abs = z.abs.setUint64(uint64(x)): ここが核心部分です。*Int の内部表現である z.abs (絶対値を示す Word スライス) に対して、uint64 値を設定する操作を行います。setUint64Word 型のメソッドで、uint64Word スライスに変換して格納します。
  • z.neg = false: uint64 は常に非負であるため、*Int の符号フラグ negfalse に設定します。

Uint64() uint64

このメソッドは、*Int レシーバ x の値を uint64 型として返します。ただし、xuint64 で表現できない場合(例えば、負の値である場合や、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 (絶対値のワードスライス) を、入力された uint64x に基づいて更新します。setUint64Word 型のメソッドであり、uint64Word スライスに適切に変換して格納する役割を担います。
    • 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) が含まれています。これにより、SetUint64Uint64 が様々な入力に対して正しく動作するかを確認できます。
  • TestUint64 関数:

    • uint64Tests の各値に対してループを実行します。
    • in.SetUint64(testVal): テスト値を *Int 型の in に設定します。
    • out := in.Uint64(): in から uint64 値を取得します。
    • if out != testVal: 設定した値と取得した値が一致するかを検証します。一致しない場合はエラーを報告します。
    • str := fmt.Sprint(testVal)strOut := in.String(): *IntString() メソッドが、元の uint64 値の文字列表現と一致するかどうかも検証しています。これは、値の正確な内部表現と、それが人間が読める形式に正しく変換されることを保証します。

これらのテストは、新しい SetUint64Uint64 メソッドが、uint64 の全範囲にわたって正確に機能することを保証するために不可欠です。

関連リンク

参考にした情報源リンク