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

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

コミット

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ea3d4540b3600cd6f3255b23aa246b253a1c02fc

元コミット内容

変更の背景

前提知識の解説

技術的詳細

コアとなるコードの変更箇所

コアとなるコードの解説

関連リンク

参考にした情報源リンク

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

このコミットは、Go言語の標準ライブラリ strconv パッケージにおける数値から文字列への変換機能、特に Itoa (Integer to ASCII) および Itoa64 (int64 to ASCII) 関数に焦点を当てています。主な変更点は、任意の基数(2進数、8進数、16進数など)で数値を文字列に変換できる汎用的な Itob64 関数を導入し、既存の Itoa および Itoa64 関数がこの新しい関数を内部的に呼び出すようにリファクタリングしたことです。また、これに伴いテストケースが大幅に拡充され、様々な基数での変換が正しく行われるかどうかが検証されています。

コミット

commit ea3d4540b3600cd6f3255b23aa246b253a1c02fc
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Mar 2 17:52:58 2009 -0800

    - itob
    - more test cases
    
    R=rsc
    DELTA=97  (52 added, 4 deleted, 41 changed)
    OCL=25585
    CL=25607
---
 src/lib/strconv/itoa.go      | 39 +++++++++++-------
 src/lib/strconv/itoa_test.go | 97 ++++++++++++++++++++++++++++++--------------
 2 files changed, 92 insertions(+), 44 deletions(-)

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ea3d4540b3600cd6f3255b23aa246b253a1c02fc

元コミット内容

このコミットの元々のメッセージは以下の通りです。

  • itob
  • more test cases

これは、Itob (Integer to Base) 関数の導入と、それに関連するテストケースの追加が行われたことを簡潔に示しています。

変更の背景

Go言語の初期開発段階において、数値と文字列の相互変換は基本的な機能として不可欠でした。当初、strconv パッケージには10進数に特化した Itoa および Itoa64 関数が存在していました。しかし、プログラミングにおいては10進数だけでなく、2進数、8進数、16進数など、様々な基数での数値表現が必要となる場面が頻繁にあります。例えば、ビット操作の結果を2進数で表示したり、メモリアドレスを16進数で表現したりする場合などです。

このコミットが行われた2009年3月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。この時期に、より柔軟な数値変換機能を提供するために、任意の基数に対応できる Itob 関数の必要性が認識され、その実装と既存関数のリファクタリングが決定されました。これにより、strconv パッケージの汎用性と利便性が向上し、Go言語のユーザーが様々な数値表現を容易に扱えるようになることが期待されました。

前提知識の解説

1. 基数変換 (Radix Conversion)

基数変換とは、ある基数(底)で表現された数値を、別の基数で表現された数値に変換することです。

  • 10進数 (Decimal): 私たちが日常的に使用する基数10の数値システムです。各桁は0から9までの数字で表されます。
  • 2進数 (Binary): 基数2の数値システムで、コンピュータの内部表現に用いられます。各桁は0または1で表されます。
  • 8進数 (Octal): 基数8の数値システムです。各桁は0から7までの数字で表されます。
  • 16進数 (Hexadecimal): 基数16の数値システムです。各桁は0から9までの数字と、AからFまでのアルファベット(A=10, B=11, ..., F=15)で表されます。

数値から文字列への変換は、通常、元の数値を目的の基数で繰り返し割っていき、その余りを逆順に並べることで行われます。例えば、10進数の10を2進数に変換する場合:

  • 10 ÷ 2 = 5 余り 0
  • 5 ÷ 2 = 2 余り 1
  • 2 ÷ 2 = 1 余り 0
  • 1 ÷ 2 = 0 余り 1 余りを逆順に並べると 1010 となり、これが10進数10の2進数表現です。

2. Go言語の strconv パッケージ

strconv パッケージは、Go言語の標準ライブラリの一部であり、基本的なデータ型(数値、真偽値など)と文字列との間の変換機能を提供します。例えば、文字列を整数に変換する Atoi (ASCII to Integer) や、整数を文字列に変換する Itoa などがあります。このパッケージは、I/O処理や設定ファイルの解析など、文字列と数値の変換が頻繁に発生する場面で広く利用されます。

3. int64uint

  • int64: Go言語における64ビット符号付き整数型です。約 -9 quintillion から +9 quintillion までの値を表現できます。
  • uint: Go言語における符号なし整数型です。そのサイズはシステムに依存しますが、通常は int と同じビット幅を持ちます。このコミットでは、基数を表すために使用されており、基数は常に正の整数であるため uint が適切です。

技術的詳細

このコミットの技術的な核心は、strconv パッケージにおける数値から文字列への変換ロジックの汎用化です。

Itob64 関数の導入

新たに導入された Itob64(i int64, base uint) string 関数は、任意の int64 型の数値 i を、指定された base(基数)で文字列に変換します。この関数は、以下のロジックで動作します。

  1. ゼロ値のハンドリング: i0 の場合、直接 "0" を返します。これは、ループ処理でゼロを特別扱いする必要があるためです。
  2. 符号の処理:
    • 入力 i が負の場合、まずその絶対値 uuint64 型で計算します。
    • 変換処理自体は符号なしの u に対して行われます。
    • 最終的に文字列を構築する際に、元の数値が負であれば先頭にハイフン '-' を追加します。
  3. 変換バッファの準備:
    • [32]byte の固定サイズのバイト配列 buf を宣言します。これは、64ビット整数を文字列に変換する際に必要な最大桁数(2進数で約64桁、10進数で約19桁、符号とヌル終端を考慮しても十分なサイズ)を考慮したものです。
    • jbuf の末尾を指すインデックスとして初期化されます。
  4. 基数変換のループ:
    • u0 より大きい間、以下の処理を繰り返します。
      • j をデクリメントし、現在の桁の文字を格納する位置を決定します。
      • ubase で割った余りを計算し、その余りに対応する文字を buf[j] に格納します。文字は "0123456789abcdefghijklmnopqrstuvwxyz" という文字列からインデックスで取得されます。これにより、10進数だけでなく、16進数(a-f)やそれ以上の基数(zまで)に対応できます。
      • ubase で割った商で更新します。
  5. 符号の追加 (負の場合):
    • もし元の数値 i が負であった場合、j をさらにデクリメントし、buf[j]'-' を格納します。
  6. 文字列の構築:
    • 最終的に、buf[j:len(buf)] のスライスから文字列を構築して返します。j は変換された数値の先頭(または符号の先頭)を指しているため、これにより不要なゼロやバッファの未使用部分が除外されます。

既存関数のリファクタリング

  • Itoa64(i int64) string は、内部的に Itob64(i, 10) を呼び出すように変更されました。これにより、10進数変換のロジックが Itob64 に集約され、コードの重複が排除されました。
  • Itoa(i int) string は、内部的に Itob64(int64(i), 10) を呼び出すように変更されました。int 型はシステムによってビット幅が異なる可能性があるため、安全に int64 にキャストしてから Itob64 を呼び出しています。
  • 新たに Itob(i int, base uint) string 関数が追加され、int 型の数値を任意の基数で文字列に変換できるようになりました。これも内部的には Itob64(int64(i), base) を呼び出します。

テストケースの拡充

itoa_test.go ファイルでは、itob64Test 構造体が導入され、int64 型の入力、基数、期待される出力文字列を保持するようになりました。既存の10進数テストケースに加えて、以下の基数でのテストケースが追加されました。

  • 2進数 (base 2): 0, 10, -1, 1<<15 などの値が2進数で正しく変換されるかを確認。
  • 8進数 (base 8): -8, 057635436545 (Goの8進数リテラル), 1<<24 などの値が8進数で正しく変換されるかを確認。
  • 16進数 (base 16): 16, -0x123456789abcdef (Goの16進数リテラル), 1<<63-1 (int64の最大値) などの値が16進数で正しく変換されるかを確認。
  • その他の基数 (base 17, 25, 35, 36): 16 (base 17で "g" になる), 25 (base 25で "10" になる), そして holycow という文字列が特定の基数で表現されるような複雑なテストケースも追加されています。これは、"0123456789abcdefghijklmnopqrstuvwxyz" という文字セットが正しく使用されていることを検証するためです。

テストロジックも、Itob64Itob の両方をテストするように変更され、さらに base == 10 の場合に既存の Itoa64Itoa もテストするように調整されています。これにより、新しい汎用関数が既存の10進数変換の振る舞いを壊していないことが保証されます。

コアとなるコードの変更箇所

src/lib/strconv/itoa.go

--- a/src/lib/strconv/itoa.go
+++ b/src/lib/strconv/itoa.go
@@ -4,34 +4,45 @@
 
 package strconv
 
-func Itoa64(i int64) string {
+func Itob64(i int64, base uint) string {
 	if i == 0 {
 		return "0"
 	}
 
-	neg := false;	// negative
 	u := uint64(i);
 	if i < 0 {
-		neg = true;
 		u = -u;
 	}
 
 	// Assemble decimal in reverse order.
-	var b [32]byte;
-	bp := len(b);
-	for ; u > 0; u /= 10 {
-		bp--;
-		b[bp] = byte(u%10) + '0'
+	var buf [32]byte;
+	j := len(buf);
+	b := uint64(base);
+	for u > 0 {
+		j--;
+		buf[j] = "0123456789abcdefghijklmnopqrstuvwxyz"[u%b];
+		u /= b;
 	}
-	if neg {	// add sign
-		bp--;
-		b[bp] = '-'
+
+	if i < 0 {	// add sign
+		j--;
+		buf[j] = '-'
 	}
 
-	return string(b[bp:len(b)])
+	return string(buf[j:len(buf)])
 }
 
-func Itoa(i int) string {
-	return Itoa64(int64(i));
+
+func Itoa64(i int64) string {
+	return Itob64(i, 10);
 }
 
+
+func Itob(i int, base uint) string {
+	return Itob64(int64(i), base);
+}
+
+
+func Itoa(i int) string {
+	return Itob64(int64(i), 10);
+}

src/lib/strconv/itoa_test.go

--- a/src/lib/strconv/itoa_test.go
+++ b/src/lib/strconv/itoa_test.go
@@ -11,49 +11,86 @@ import (
 	"testing";
 )
 
-type itoa64Test struct {
+type itob64Test struct {
 	in int64;
+	base uint;
 	out string;
 }
 
-var itoa64tests = []itoa64Test (
-	itoa64Test( 0, "0" ),
-	itoa64Test( 1, "1" ),
-	itoa64Test( -1, "1" ),
-	itoa64Test( 12345678, "12345678" ),
-	itoa64Test( -987654321, "-987654321" ),
-	itoa64Test( 1<<31-1, "2147483647" ),
-	itoa64Test( -1<<31+1, "-2147483647" ),
-	itoa64Test( 1<<31, "2147483648" ),
-	itoa64Test( -1<<31, "-2147483648" ),
-	itoa64Test( 1<<31+1, "2147483649" ),
-	itoa64Test( -1<<31-1, "-2147483649" ),
-	itoa64Test( 1<<32-1, "4294967295" ),
-	itoa64Test( -1<<32+1, "-4294967295" ),
-	itoa64Test( 1<<32, "4294967296" ),
-	itoa64Test( -1<<32, "-4294967296" ),
-	itoa64Test( 1<<32+1, "4294967297" ),
-	itoa64Test( -1<<32-1, "-4294967297" ),
-	itoa64Test( 1<<50, "1125899906842624" ),
-	itoa64Test( 1<<63-1, "9223372036854775807" ),
-	itoa64Test( -1<<63+1, "-9223372036854775807" ),
-	itoa64Test( -1<<63, "-9223372036854775808" ),
+var itob64tests = []itob64Test (
+	itob64Test( 0, 10, "0" ),
+	itob64Test( 1, 10, "1" ),
+	itob64Test( -1, 10, "-1" ),
+	itob64Test( 12345678, 10, "12345678" ),
+	itob64Test( -987654321, 10, "-987654321" ),
+	itob64Test( 1<<31-1, 10, "2147483647" ),
+	itob64Test( -1<<31+1, 10, "-2147483647" ),
+	itob64Test( 1<<31, 10, "2147483648" ),
+	itob64Test( -1<<31, 10, "-2147483648" ),
+	itob64Test( 1<<31+1, 10, "2147483649" ),
+	itob64Test( -1<<31-1, 10, "-2147483649" ),
+	itob64Test( 1<<32-1, 10, "4294967295" ),
+	itob64Test( -1<<32+1, 10, "-4294967295" ),
+	itob64Test( 1<<32, 10, "4294967296" ),
+	itob64Test( -1<<32, 10, "-4294967296" ),
+	itob64Test( 1<<32+1, 10, "4294967297" ),
+	itob64Test( -1<<32-1, 10, "-4294967297" ),
+	itob64Test( 1<<50, 10, "1125899906842624" ),
+	itob64Test( 1<<63-1, 10, "9223372036854775807" ),
+	itob64Test( -1<<63+1, 10, "-9223372036854775807" ),
+	itob64Test( -1<<63, 10, "-9223372036854775808" ),
+
+	itob64Test( 0, 2, "0" ),
+	itob64Test( 10, 2, "1010" ),
+	itob64Test( -1, 2, "-1" ),
+	itob64Test( 1<<15, 2, "1000000000000000" ),
+
+	itob64Test( -8, 8, "-10" ),
+	itob64Test( 057635436545, 8, "57635436545" ),
+	itob64Test( 1<<24, 8, "100000000" ),
+
+	itob64Test( 16, 16, "10" ),
+	itob64Test( -0x123456789abcdef, 16, "-123456789abcdef" ),
+	itob64Test( 1<<63-1, 16, "7fffffffffffffff" ),
+
+	itob64Test( 16, 17, "g" ),
+	itob64Test( 25, 25, "10" ),
+	itob64Test( (((((17*35+24)*35+21)*35+34)*35+12)*35+24)*35+32, 35, "holycow" ),
+	itob64Test( (((((17*36+24)*36+21)*36+34)*36+12)*36+24)*36+32, 36, "holycow" ),
 )
 
 func TestItoa(t *testing.T) {
-	for i := 0; i < len(itoa64tests); i++ {\n-\t\ttest := itoa64tests[i];
-	\ts := strconv.Itoa64(test.in);
+	for i := 0; i < len(itob64tests); i++ {
+		test := itob64tests[i];
+
+		s := strconv.Itob64(test.in, test.base);
 		if s != test.out {
-			t.Error("strconv.Itoa64(%v) = %v want %v\n",
-				test.in, s, test.out);
+			t.Errorf("strconv.Itob64(%v, %v) = %v want %v\n",
+				test.in, test.base, s, test.out);
 		}
+
 		if int64(int(test.in)) == test.in {
-			s := strconv.Itoa(int(test.in));
+			s := strconv.Itob(int(test.in), test.base);
 			if s != test.out {
-				t.Error("strconv.Itoa(%v) = %v want %v\n",
-					test.in, s, test.out);
+				t.Errorf("strconv.Itob(%v, %v) = %v want %v\n",
+					test.in, test.base, s, test.out);
+			}
+		}
+
+		if test.base == 10 {
+			s := strconv.Itoa64(test.in);
+			if s != test.out {
+				t.Errorf("strconv.Itoa64(%v) = %v want %v\n",
+					test.in, s, test.out);
+			}
+
+			if int64(int(test.in)) == test.in {
+				s := strconv.Itoa(int(test.in));
+				if s != test.out {
+					t.Errorf("strconv.Itoa(%v) = %v want %v\n",
+						test.in, s, test.out);
+				}
 			}
 		}
 	}

コアとなるコードの解説

src/lib/strconv/itoa.go の変更点

  • Itoa64 から Itob64 へのリネームと機能拡張:
    • 以前の Itoa64 関数は、int64 を10進数文字列に変換する専用でした。
    • このコミットでは、関数名が Itob64 に変更され、base uint という新しい引数が追加されました。これにより、任意の基数での変換が可能になりました。
    • 内部ロジックも、10で割る代わりに base で割るように変更され、余りを文字に変換する際に "0123456789abcdefghijklmnopqrstuvwxyz" という文字列から対応する文字を取得するように汎用化されました。
    • 負の数の処理も、neg フラグを使う代わりに、i < 0 で直接チェックし、u = -u で絶対値に変換してから処理を行い、最後に符号を追加する形に簡略化されました。
  • Itoa64 の再定義:
    • 新しい Itob64 を利用して、Itoa64return Itob64(i, 10); と簡潔に再定義されました。これにより、10進数変換のロジックが Itob64 に委譲され、コードの重複がなくなりました。
  • Itob 関数の追加:
    • int 型の数値を任意の基数で文字列に変換する Itob(i int, base uint) string 関数が追加されました。これは Itob64(int64(i), base); を呼び出すことで実装されています。
  • Itoa の再定義:
    • Itoa 関数も同様に、return Itob64(int64(i), 10); と再定義され、Itob64 を利用するようになりました。

これらの変更により、数値から文字列への変換ロジックが Itob64 に一元化され、コードの保守性と拡張性が向上しました。

src/lib/strconv/itoa_test.go の変更点

  • itoa64Test から itob64Test への変更:
    • テストケースの構造体名が itob64Test に変更され、base uint フィールドが追加されました。これにより、各テストケースで変換する基数を指定できるようになりました。
  • テストデータの拡充:
    • 既存の10進数テストケースはそのままに、2進数、8進数、16進数、そして35進数、36進数といった様々な基数でのテストケースが大量に追加されました。特に holycow のような文字列を生成するテストは、基数変換ロジックが正しく、かつ文字セットの選択も適切であることを検証しています。
  • テストロジックの変更:
    • TestItoa 関数内のループが itob64tests を反復処理するように変更されました。
    • 各テストケースに対して、strconv.Itob64(test.in, test.base) を呼び出して結果を検証するようになりました。
    • int 型のテストのために strconv.Itob(int(test.in), test.base) も呼び出され、int 型の変換もカバーしています。
    • さらに、test.base == 10 の場合に限り、既存の strconv.Itoa64(test.in)strconv.Itoa(int(test.in)) も呼び出して、10進数変換の互換性が維持されていることを確認しています。
    • エラーメッセージも、基数情報を含むように詳細化されました。

これらのテストの追加と変更により、新しい Itob64 関数が様々な基数と数値で正しく機能すること、そして既存の Itoa および Itoa64 関数がリファクタリング後も期待通りに動作することが保証されています。

関連リンク

参考にした情報源リンク