[インデックス 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. int64
と uint
型
int64
: Go言語における64ビット符号付き整数型です。約 -9 quintillion から +9 quintillion までの値を表現できます。uint
: Go言語における符号なし整数型です。そのサイズはシステムに依存しますが、通常はint
と同じビット幅を持ちます。このコミットでは、基数を表すために使用されており、基数は常に正の整数であるためuint
が適切です。
技術的詳細
このコミットの技術的な核心は、strconv
パッケージにおける数値から文字列への変換ロジックの汎用化です。
Itob64
関数の導入
新たに導入された Itob64(i int64, base uint) string
関数は、任意の int64
型の数値 i
を、指定された base
(基数)で文字列に変換します。この関数は、以下のロジックで動作します。
- ゼロ値のハンドリング:
i
が0
の場合、直接"0"
を返します。これは、ループ処理でゼロを特別扱いする必要があるためです。 - 符号の処理:
- 入力
i
が負の場合、まずその絶対値u
をuint64
型で計算します。 - 変換処理自体は符号なしの
u
に対して行われます。 - 最終的に文字列を構築する際に、元の数値が負であれば先頭にハイフン
'-'
を追加します。
- 入力
- 変換バッファの準備:
[32]byte
の固定サイズのバイト配列buf
を宣言します。これは、64ビット整数を文字列に変換する際に必要な最大桁数(2進数で約64桁、10進数で約19桁、符号とヌル終端を考慮しても十分なサイズ)を考慮したものです。j
はbuf
の末尾を指すインデックスとして初期化されます。
- 基数変換のループ:
u
が0
より大きい間、以下の処理を繰り返します。j
をデクリメントし、現在の桁の文字を格納する位置を決定します。u
をbase
で割った余りを計算し、その余りに対応する文字をbuf[j]
に格納します。文字は"0123456789abcdefghijklmnopqrstuvwxyz"
という文字列からインデックスで取得されます。これにより、10進数だけでなく、16進数(a-f)やそれ以上の基数(zまで)に対応できます。u
をbase
で割った商で更新します。
- 符号の追加 (負の場合):
- もし元の数値
i
が負であった場合、j
をさらにデクリメントし、buf[j]
に'-'
を格納します。
- もし元の数値
- 文字列の構築:
- 最終的に、
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"
という文字セットが正しく使用されていることを検証するためです。
テストロジックも、Itob64
と Itob
の両方をテストするように変更され、さらに base == 10
の場合に既存の Itoa64
と Itoa
もテストするように調整されています。これにより、新しい汎用関数が既存の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
を利用して、Itoa64
はreturn 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
のような文字列を生成するテストは、基数変換ロジックが正しく、かつ文字セットの選択も適切であることを検証しています。
- 既存の10進数テストケースはそのままに、2進数、8進数、16進数、そして35進数、36進数といった様々な基数でのテストケースが大量に追加されました。特に
- テストロジックの変更:
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
関数がリファクタリング後も期待通りに動作することが保証されています。
関連リンク
- Go言語
strconv
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/strconv - Go言語のソースコードリポジトリ: https://github.com/golang/go
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- 基数変換に関する一般的な情報 (例: Wikipedia): https://ja.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E5%A4%89%E6%8F%9B
- Go言語の初期開発に関する情報 (Goブログなど): https://go.dev/blog/
- Go言語の
strconv
パッケージの歴史的な変更点 (Goのソースコード履歴から読み解く)