[インデックス 1690] ファイルの概要
このコミットは、Go言語の標準ライブラリstrconv
パッケージにおける数値文字列変換関数、特にAtoi
およびAtoui
系の関数に、16進数(0x
または0X
プレフィックス)と8進数(0
プレフィックス)のサポートを追加するものです。これにより、これらの関数がより柔軟に様々な基数の数値文字列を解釈できるようになります。
コミット
commit eb3823a44d7ee50d213d2eeb8cb3b24791e6f5b1
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 16 20:44:21 2009 -0800
allow hex, octal in Atoi, etc.
R=r
DELTA=169 (79 added, 23 deleted, 67 changed)
OCL=25079
CL=25083
---
src/lib/strconv/atoi.go | 97 +++++++++++++++++++++++---------
src/lib/strconv/atoi_test.go | 131 ++++++++++++++++++++++++-------------------
2 files changed, 142 insertions(+), 86 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eb3823a44d7ee50d213d2eeb8cb3b24791e6f5b1
元コミット内容
allow hex, octal in Atoi, etc.
R=r
DELTA=169 (79 added, 23 deleted, 67 changed)
OCL=25079
CL=25083
変更の背景
Go言語の初期段階において、strconv
パッケージのAtoi
やAtoui
といった文字列から数値への変換関数は、主に10進数表記の文字列のみをサポートしていました。しかし、プログラミングにおいては、16進数(例: 0xFF
)や8進数(例: 0777
)といった異なる基数で数値を表現することが一般的であり、特にシステムプログラミングやビット操作を伴う場面では不可欠です。
このコミット以前は、これらの基数の文字列を数値に変換するには、開発者が自前でパースロジックを実装するか、より低レベルな変換関数(もし存在すれば)を使用する必要がありました。これは不便であり、エラーの温床となる可能性がありました。
この変更の背景には、Go言語の標準ライブラリがより実用的で、一般的なプログラミングニーズに対応できるようにするという設計思想があります。C言語などの他の言語では、strtol
のような関数が基数プレフィックスを自動的に解釈する機能を提供しており、Goも同様の利便性を提供することで、開発者の負担を軽減し、コードの可読性と堅牢性を向上させることを目指しました。
具体的には、0x
または0X
で始まる文字列を16進数として、0
で始まる文字列を8進数として解釈する機能を追加することで、より自然な数値リテラルの表現を文字列変換関数でも扱えるようにすることが目的でした。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
- 基数(Radix/Base): 数値を表現するための桁の種類の数。
- 10進数 (Decimal): 基数が10。0-9の数字を使用。日常的に最も使われる。
- 16進数 (Hexadecimal): 基数が16。0-9とA-F(またはa-f)の文字を使用。コンピュータサイエンスでメモリアドレスや色コードなどを表現する際によく使われる。通常、
0x
または0X
をプレフィックスとして付けることで識別される(例:0xFF
)。 - 8進数 (Octal): 基数が8。0-7の数字を使用。ファイルパーミッションなど、一部のシステム設定で使われることがある。通常、
0
をプレフィックスとして付けることで識別される(例:0755
)。
- Go言語の
strconv
パッケージ: 文字列と基本的なデータ型(数値、真偽値など)の間で変換を行うための関数を提供する標準ライブラリパッケージ。Atoi(s string) (int, error)
: 文字列s
をint
型に変換する。Atoui(s string) (uint, error)
: 文字列s
をuint
型に変換する。Atoi64(s string) (int64, error)
: 文字列s
をint64
型に変換する。Atoui64(s string) (uint64, error)
: 文字列s
をuint64
型に変換する。
- 数値オーバーフロー (Integer Overflow): 整数型で表現できる最大値を超えた場合に発生する現象。通常、最大値を超えると最小値に戻る(ラップアラウンド)か、エラーとなる。
os.EINVAL
: Go言語のos
パッケージで定義されているエラーコードの一つで、無効な引数(Invalid Argument)が渡されたことを示す。os.ERANGE
: Go言語のos
パッケージで定義されているエラーコードの一つで、結果が表現可能な範囲を超えている(Result out of range)ことを示す。
技術的詳細
このコミットの主要な技術的変更点は、strconv
パッケージ内の数値変換ロジックを汎用化し、基数プレフィックスを自動的に検出して適切な基数で文字列を数値に変換するようにしたことです。
変更前は、Atoui64
関数は10進数のみを想定しており、0
で始まる文字列は無効な入力としてos.EINVAL
エラーを返していました。これは、C言語などにおける8進数リテラルの慣習(0
で始まる数値は8進数)とは異なり、Goの数値リテラルとしての8進数表記(0o
プレフィックス)とも異なる挙動でした。
変更後、以下の新しい関数とロジックが導入されました。
-
Btoui64(base int, s string) (n uint64, err *os.Error)
の導入:- この関数は、任意の基数(
base
引数で指定)の文字列s
をuint64
に変換する汎用的な内部関数として導入されました。 - 文字列の各文字を対応する数値に変換し、指定された基数で累積的に数値を構築します。
- 変換中にオーバーフローが発生しないように、
cutoff64
関数を使用して事前にオーバーフローの可能性をチェックするロジックが組み込まれています。 '0'
から'9'
、'a'
から'z'
、'A'
から'Z'
の文字をそれぞれ数値にマッピングし、指定された基数を超える文字が出現した場合はos.EINVAL
エラーを返します。
- この関数は、任意の基数(
-
cutoff64(base int) uint64
の導入:Btoui64
関数内で使用されるヘルパー関数です。n * base
がuint64
の最大値(1<<64 - 1
)を超える可能性があるかどうかを効率的に判断するためのカットオフ値を計算します。- 具体的には、
(1<<64 - 1) / uint64(base) + 1
を計算し、現在の累積値n
がこのcutoff
値以上になった場合、次の乗算でオーバーフローが発生する可能性が高いと判断します。
-
Atoui64
関数のロジック変更:- 変更前は直接10進数パースロジックを持っていましたが、変更後はまず文字列のプレフィックスをチェックするようになりました。
s[0] == '0'
かつlen(s) > 1
の場合、さらにs[1]
をチェックします。s[1] == 'x'
またはs[1] == 'X'
の場合、残りの文字列(s[2:]
)を16進数としてBtoui64(16, ...)
を呼び出します。- それ以外の場合(
s[1]
がx
でもX
でもない場合)、残りの文字列(s[1:]
)を8進数としてBtoui64(8, ...)
を呼び出します。
- プレフィックスがない場合、または
s[0]
が0
でない場合は、10進数としてBtoui64(10, s)
を呼び出します。
-
Atoi64
,Atoui
,Atoi
関数の変更:- これらの関数は、内部的に
Atoui64
またはAtoi64
を呼び出すように変更されました。これにより、基数プレフィックスの自動検出機能がこれらの関数にも伝播し、一貫した挙動が実現されました。 - 特に
Atoi64
は、符号(+
または-
)を処理した後、残りの文字列をAtoui64
に渡し、その結果を符号に応じて調整し、int64
の範囲チェックを行います。
- これらの関数は、内部的に
この変更により、strconv
パッケージの数値変換関数は、C言語のstrtol
やPythonのint()
関数のように、文字列のプレフィックスに基づいて自動的に基数を推測し、変換できるようになりました。これにより、開発者は明示的に基数を指定することなく、一般的な数値リテラル形式の文字列を扱うことが可能になり、利便性が大幅に向上しました。
コアとなるコードの変更箇所
src/lib/strconv/atoi.go
computeIntsize()
: 変更なし。cutoff64(base int) uint64
: 新規追加。オーバーフローチェックのためのカットオフ値を計算。Btoui64(base int, s string) (n uint64, err *os.Error)
: 新規追加。任意の基数で符号なし整数をパースする汎用関数。Atoui64(s string) (i uint64, err *os.Error)
: ロジックが大幅に変更。プレフィックス(0x
,0X
,0
)をチェックし、Btoui64
を呼び出すように修正。- 変更前は
0
で始まる文字列をos.EINVAL
としていたが、変更後は8進数として解釈するようになった。
- 変更前は
Atoi64(s string) (i int64, err *os.Error)
: 符号処理後、Atoui64
を呼び出すように変更。Atoui(s string) (i uint, err *os.Error)
:Atoui64
を呼び出すように変更。Atoi(s string) (i int, err *os.Error)
:Atoi64
を呼び出すように変更。
src/lib/strconv/atoi_test.go
atoui64tests
: 以下のテストケースが追加・変更され、8進数および16進数の文字列が正しくパースされることを確認。atoui64Test("012345", 012345, nil)
: 8進数テストの追加。atoui64Test("0x12345", 0x12345, nil)
: 16進数テストの追加。atoui64Test("0X12345", 0x12345, nil)
: 大文字Xの16進数テストの追加。- オーバーフローに関する8進数・16進数テストの追加。
atoi64test
: 同様に、符号付きの8進数および16進数テストケースが追加・変更。atoui32tests
: 同様に、8進数および16進数テストケースが追加・変更。atoi32tests
: 同様に、符号付きの8進数および16進数テストケースが追加・変更。
コアとなるコードの解説
src/lib/strconv/atoi.go
func cutoff64(base int) uint64
func cutoff64(base int) uint64 {
if base < 2 {
return 0;
}
return (1<<64 - 1) / uint64(base) + 1;
}
この関数は、uint64
の最大値(1<<64 - 1
)をbase
で割った商に1を加えた値を返します。これは、次の乗算(n * base
)でuint64
のオーバーフローが発生するかどうかを事前にチェックするための「カットオフ値」として使用されます。もし現在の累積値n
がこのcutoff
値以上になった場合、n * base
は確実にオーバーフローするか、またはオーバーフローする可能性が非常に高くなります。
func Btoui64(base int, s string) (n uint64, err *os.Error)
func Btoui64(base int, s string) (n uint64, err *os.Error) {
if base < 2 || base > 36 || len(s) < 1 {
return 0, os.EINVAL;
}
n = 0;
cutoff := cutoff64(base);
for i := 0; i < len(s); i++ {
var v byte;
switch {
case '0' <= s[i] && s[i] <= '9':
v = s[i] - '0';
case 'a' <= s[i] && s[i] <= 'z':
v = s[i] - 'a' + 10;
case 'A' <= s[i] && s[i] <= 'Z':
v = s[i] - 'A' + 10;
default:
return 0, os.EINVAL;
}
if int(v) >= base {
return 0, os.EINVAL;
}
if n >= cutoff {
// n*base overflows
return 1<<64-1, os.ERANGE;
}
n *= uint64(base);
n1 := n+uint64(v);
if n1 < n {
// n+v overflows
return 1<<64-1, os.ERANGE;
}
n = n1;
}
return n, nil;
}
この関数は、指定されたbase
(2から36まで)に基づいて文字列s
をuint64
に変換する中心的なロジックです。
- 引数チェック:
base
が有効な範囲内か、s
が空でないかをチェックします。 - 初期化: 結果を格納する
n
を0に初期化し、オーバーフローチェック用のcutoff
値を計算します。 - ループ処理: 文字列
s
の各文字を順に処理します。- 文字から数値への変換: 現在の文字
s[i]
が数字(0
-9
)、小文字アルファベット(a
-z
)、大文字アルファベット(A
-Z
)のいずれかであるかに応じて、対応する数値v
(0-35)に変換します。それ以外の文字は無効な入力としてos.EINVAL
を返します。 - 基数チェック: 変換された数値
v
がbase
以上である場合、その文字は現在の基数では無効であるため、os.EINVAL
を返します(例: 10進数でA
が出現した場合)。 - 乗算オーバーフローチェック:
n
がcutoff
以上の場合、次のn * base
の計算でオーバーフローが発生する可能性が高いため、os.ERANGE
エラーを返します。 - 乗算:
n
をbase
倍します。 - 加算オーバーフローチェック:
n
にv
を加算した結果n1
が、加算前のn
よりも小さい場合、加算によってオーバーフローが発生したことを意味するため、os.ERANGE
エラーを返します。 - 累積:
n
をn1
で更新します。
- 文字から数値への変換: 現在の文字
- 結果返却: 全ての文字を正常に処理できたら、最終的な
n
とnil
エラーを返します。
func Atoui64(s string) (i uint64, err *os.Error)
func Atoui64(s string) (i uint64, err *os.Error) {
// Empty string bad.
if len(s) == 0 {
return 0, os.EINVAL
}
// Look for octal, hex prefix.
if s[0] == '0' && len(s) > 1 {
if s[1] == 'x' || s[1] == 'X' {
// hex
return Btoui64(16, s[2:len(s)]);
}
// octal
return Btoui64(8, s[1:len(s)]);
}
// decimal
return Btoui64(10, s);
}
この関数は、ユーザーが直接呼び出す可能性のあるuint64
変換の主要なエントリポイントです。
- 空文字列チェック: 入力文字列
s
が空の場合、os.EINVAL
エラーを返します。 - プレフィックスチェック:
- 文字列が
'0'
で始まり、かつ2文字以上ある場合、基数プレフィックスの可能性をチェックします。 - もし2文字目が
'x'
または'X'
であれば、残りの文字列(s[2:]
)を16進数としてBtoui64
を呼び出します。 - そうでなければ(
'0'
で始まるが'x'
や'X'
が続かない場合)、残りの文字列(s[1:]
)を8進数としてBtoui64
を呼び出します。
- 文字列が
- 10進数として処理: 上記のプレフィックスに該当しない場合、文字列全体を10進数として
Btoui64
を呼び出します。
このロジックにより、Atoui64
は自動的に入力文字列の基数を判別し、適切な変換を行います。
src/lib/strconv/atoi_test.go
テストファイルでは、新しいBtoui64
関数やcutoff64
関数自体のテストは直接行われていませんが、Atoui64
やAtoi64
などの公開APIが、8進数や16進数の文字列を正しく処理できるようになったことを検証するテストケースが追加されています。
例えば、atoui64tests
配列には、以下のような新しいテストケースが含まれています。
atoui64Test("012345", 012345, nil)
:0
で始まる8進数表記の文字列が正しく変換されることを確認。atoui64Test("0x12345", 0x12345, nil)
:0x
で始まる16進数表記の文字列が正しく変換されることを確認。atoui64Test("0xFFFFFFFFFFFFFFFF", 1<<64-1, nil)
: 16進数でのuint64
最大値の変換を確認。atoui64Test("0x10000000000000000", 1<<64-1, os.ERANGE)
: 16進数でのオーバーフローを確認。atoui64Test("01777777777777777777777", 1<<64-1, nil)
: 8進数でのuint64
最大値の変換を確認。atoui64Test("02000000000000000000000", 1<<64-1, os.ERANGE)
: 8進数でのオーバーフローを確認。
これらのテストケースは、変更されたAtoui64
関数が、従来の10進数だけでなく、8進数と16進数の文字列も正しく解釈し、オーバーフローなどのエラーも適切に処理できるようになったことを包括的に検証しています。
関連リンク
- Go言語
strconv
パッケージのドキュメント (現在のバージョン):- https://pkg.go.dev/strconv
- このコミットはGo言語の非常に初期の段階のものであるため、現在のドキュメントは当時のものとは大きく異なりますが、
ParseInt
やParseUint
などの関数が基数を引数として受け取るようになっていることが確認できます。このコミットで導入された自動判別ロジックは、後のParseInt
やParseUint
のbase
引数に0
を指定した場合の挙動の基礎となっています。
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub):
- Go言語の初期の設計に関する議論(Go開発者ブログなど、当時の情報源):
- このコミットは2009年のものであり、当時のGo言語の設計思想や議論を直接参照できる公開資料は限られている可能性がありますが、Go言語の設計原則(シンプルさ、実用性)がこのような標準ライブラリの機能拡張に影響を与えていると考えられます。
- C言語
strtol
関数に関する情報:- https://en.cppreference.com/w/c/string/byte/strtol
strtol
関数は、基数として0
を指定した場合に、文字列のプレフィックス(0x
,0
)に基づいて自動的に基数を判別する機能を持っており、Goのこの変更のインスピレーション源の一つである可能性があります。
- Python
int()
関数に関する情報:- https://docs.python.org/3/library/functions.html#int
- Pythonの
int()
関数も、文字列のプレフィックスに基づいて基数を自動判別する機能を持っています。
[インデックス 1690] ファイルの概要
このコミットは、Go言語の標準ライブラリstrconv
パッケージにおける数値文字列変換関数、特にAtoi
およびAtoui
系の関数に、16進数(0x
または0X
プレフィックス)と8進数(0
プレフィックス)のサポートを追加するものです。これにより、これらの関数がより柔軟に様々な基数の数値文字列を解釈できるようになります。
コミット
commit eb3823a44d7ee50d213d2eeb8cb3b24791e6f5b1
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 16 20:44:21 2009 -0800
allow hex, octal in Atoi, etc.
R=r
DELTA=169 (79 added, 23 deleted, 67 changed)
OCL=25079
CL=25083
---
src/lib/strconv/atoi.go | 97 +++++++++++++++++++++++---------
src/lib/strconv/atoi_test.go | 131 ++++++++++++++++++++++++-------------------
2 files changed, 142 insertions(+), 86 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eb3823a44d7ee50d213d2eeb8cb3b24791e6f5b1
元コミット内容
allow hex, octal in Atoi, etc.
R=r
DELTA=169 (79 added, 23 deleted, 67 changed)
OCL=25079
CL=25083
変更の背景
Go言語の初期段階において、strconv
パッケージのAtoi
やAtoui
といった文字列から数値への変換関数は、主に10進数表記の文字列のみをサポートしていました。しかし、プログラミングにおいては、16進数(例: 0xFF
)や8進数(例: 0777
)といった異なる基数で数値を表現することが一般的であり、特にシステムプログラミングやビット操作を伴う場面では不可欠です。
このコミット以前は、これらの基数の文字列を数値に変換するには、開発者が自前でパースロジックを実装するか、より低レベルな変換関数(もし存在すれば)を使用する必要がありました。これは不便であり、エラーの温床となる可能性がありました。
この変更の背景には、Go言語の標準ライブラリがより実用的で、一般的なプログラミングニーズに対応できるようにするという設計思想があります。C言語などの他の言語では、strtol
のような関数が基数プレフィックスを自動的に解釈する機能を提供しており、Goも同様の利便性を提供することで、開発者の負担を軽減し、コードの可読性と堅牢性を向上させることを目指しました。
具体的には、0x
または0X
で始まる文字列を16進数として、0
で始まる文字列を8進数として解釈する機能を追加することで、より自然な数値リテラルの表現を文字列変換関数でも扱えるようにすることが目的でした。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
- 基数(Radix/Base): 数値を表現するための桁の種類の数。
- 10進数 (Decimal): 基数が10。0-9の数字を使用。日常的に最も使われる。
- 16進数 (Hexadecimal): 基数が16。0-9とA-F(またはa-f)の文字を使用。コンピュータサイエンスでメモリアドレスや色コードなどを表現する際によく使われる。通常、
0x
または0X
をプレフィックスとして付けることで識別される(例:0xFF
)。 - 8進数 (Octal): 基数が8。0-7の数字を使用。ファイルパーミッションなど、一部のシステム設定で使われることがある。通常、
0
をプレフィックスとして付けることで識別される(例:0755
)。
- Go言語の
strconv
パッケージ: 文字列と基本的なデータ型(数値、真偽値など)の間で変換を行うための関数を提供する標準ライブラリパッケージ。Atoi(s string) (int, error)
: 文字列s
をint
型に変換する。Atoui(s string) (uint, error)
: 文字列s
をuint
型に変換する。Atoi64(s string) (int64, error)
: 文字列s
をint64
型に変換する。Atoui64(s string) (uint64, error)
: 文字列s
をuint64
型に変換する。
- 数値オーバーフロー (Integer Overflow): 整数型で表現できる最大値を超えた場合に発生する現象。通常、最大値を超えると最小値に戻る(ラップアラウンド)か、エラーとなる。
os.EINVAL
: Go言語のos
パッケージで定義されているエラーコードの一つで、無効な引数(Invalid Argument)が渡されたことを示す。os.ERANGE
: Go言語のos
パッケージで定義されているエラーコードの一つで、結果が表現可能な範囲を超えている(Result out of range)ことを示す。
技術的詳細
このコミットの主要な技術的変更点は、strconv
パッケージ内の数値変換ロジックを汎用化し、基数プレフィックスを自動的に検出して適切な基数で文字列を数値に変換するようにしたことです。
変更前は、Atoui64
関数は10進数のみを想定しており、0
で始まる文字列は無効な入力としてos.EINVAL
エラーを返していました。これは、C言語などにおける8進数リテラルの慣習(0
で始まる数値は8進数)とは異なり、Goの数値リテラルとしての8進数表記(0o
プレフィックス)とも異なる挙動でした。
変更後、以下の新しい関数とロジックが導入されました。
-
Btoui64(base int, s string) (n uint64, err *os.Error)
の導入:- この関数は、任意の基数(
base
引数で指定)の文字列s
をuint64
に変換する汎用的な内部関数として導入されました。 - 文字列の各文字を対応する数値に変換し、指定された基数で累積的に数値を構築します。
- 変換中にオーバーフローが発生しないように、
cutoff64
関数を使用して事前にオーバーフローの可能性をチェックするロジックが組み込まれています。 '0'
から'9'
、'a'
から'z'
、'A'
から'Z'
の文字をそれぞれ数値にマッピングし、指定された基数を超える文字が出現した場合はos.EINVAL
エラーを返します。
- この関数は、任意の基数(
-
cutoff64(base int) uint64
の導入:Btoui64
関数内で使用されるヘルパー関数です。n * base
がuint64
の最大値(1<<64 - 1
)を超える可能性があるかどうかを効率的に判断するためのカットオフ値を計算します。- 具体的には、
(1<<64 - 1) / uint64(base) + 1
を計算し、現在の累積値n
がこのcutoff
値以上になった場合、次の乗算でオーバーフローが発生する可能性が高いと判断します。
-
Atoui64
関数のロジック変更:- 変更前は直接10進数パースロジックを持っていましたが、変更後はまず文字列のプレフィックスをチェックするようになりました。
s[0] == '0'
かつlen(s) > 1
の場合、さらにs[1]
をチェックします。s[1] == 'x'
またはs[1] == 'X'
の場合、残りの文字列(s[2:]
)を16進数としてBtoui64(16, ...)
を呼び出します。- それ以外の場合(
s[1]
がx
でもX
でもない場合)、残りの文字列(s[1:]
)を8進数としてBtoui64(8, ...)
を呼び出します。
- プレフィックスがない場合、または
s[0]
が0
でない場合は、10進数としてBtoui64(10, s)
を呼び出します。
-
Atoi64
,Atoui
,Atoi
関数の変更:- これらの関数は、内部的に
Atoui64
またはAtoi64
を呼び出すように変更されました。これにより、基数プレフィックスの自動検出機能がこれらの関数にも伝播し、一貫した挙動が実現されました。 - 特に
Atoi64
は、符号(+
または-
)を処理した後、残りの文字列をAtoui64
に渡し、その結果を符号に応じて調整し、int64
の範囲チェックを行います。
- これらの関数は、内部的に
この変更により、strconv
パッケージの数値変換関数は、C言語のstrtol
やPythonのint()
関数のように、文字列のプレフィックスに基づいて自動的に基数を推測し、変換できるようになりました。これにより、開発者は明示的に基数を指定することなく、一般的な数値リテラル形式の文字列を扱うことが可能になり、利便性が大幅に向上しました。
コアとなるコードの変更箇所
src/lib/strconv/atoi.go
computeIntsize()
: 変更なし。cutoff64(base int) uint64
: 新規追加。オーバーフローチェックのためのカットオフ値を計算。Btoui64(base int, s string) (n uint64, err *os.Error)
: 新規追加。任意の基数で符号なし整数をパースする汎用関数。Atoui64(s string) (i uint64, err *os.Error)
: ロジックが大幅に変更。プレフィックス(0x
,0X
,0
)をチェックし、Btoui64
を呼び出すように修正。- 変更前は
0
で始まる文字列をos.EINVAL
としていたが、変更後は8進数として解釈するようになった。
- 変更前は
Atoi64(s string) (i int64, err *os.Error)
: 符号処理後、Atoui64
を呼び出すように変更。Atoui(s string) (i uint, err *os.Error)
:Atoui64
を呼び出すように変更。Atoi(s string) (i int, err *os.Error)
:Atoi64
を呼び出すように変更。
src/lib/strconv/atoi_test.go
atoui64tests
: 以下のテストケースが追加・変更され、8進数および16進数の文字列が正しくパースされることを確認。atoui64Test("012345", 012345, nil)
: 8進数テストの追加。atoui64Test("0x12345", 0x12345, nil)
: 16進数テストの追加。atoui64Test("0X12345", 0x12345, nil)
: 大文字Xの16進数テストの追加。- オーバーフローに関する8進数・16進数テストの追加。
atoi64test
: 同様に、符号付きの8進数および16進数テストケースが追加・変更。atoui32tests
: 同様に、8進数および16進数テストケースが追加・変更。atoi32tests
: 同様に、符号付きの8進数および16進数テストケースが追加・変更。
コアとなるコードの解説
src/lib/strconv/atoi.go
func cutoff64(base int) uint64
func cutoff64(base int) uint64 {
if base < 2 {
return 0;
}
return (1<<64 - 1) / uint64(base) + 1;
}
この関数は、uint64
の最大値(1<<64 - 1
)をbase
で割った商に1を加えた値を返します。これは、次の乗算(n * base
)でuint64
のオーバーフローが発生するかどうかを事前にチェックするための「カットオフ値」として使用されます。もし現在の累積値n
がこのcutoff
値以上になった場合、n * base
は確実にオーバーフローするか、またはオーバーフローする可能性が非常に高くなります。
func Btoui64(base int, s string) (n uint64, err *os.Error)
func Btoui64(base int, s string) (n uint64, err *os.Error) {
if base < 2 || base > 36 || len(s) < 1 {
return 0, os.EINVAL;
}
n = 0;
cutoff := cutoff64(base);
for i := 0; i < len(s); i++ {
var v byte;
switch {
case '0' <= s[i] && s[i] <= '9':
v = s[i] - '0';
case 'a' <= s[i] && s[i] <= 'z':
v = s[i] - 'a' + 10;
case 'A' <= s[i] && s[i] <= 'Z':
v = s[i] - 'A' + 10;
default:
return 0, os.EINVAL;
}
if int(v) >= base {
return 0, os.EINVAL;
}
if n >= cutoff {
// n*base overflows
return 1<<64-1, os.ERANGE;
}
n *= uint64(base);
n1 := n+uint64(v);
if n1 < n {
// n+v overflows
return 1<<64-1, os.ERANGE;
}
n = n1;
}
return n, nil;
}
この関数は、指定されたbase
(2から36まで)に基づいて文字列s
をuint64
に変換する中心的なロジックです。
- 引数チェック:
base
が有効な範囲内か、s
が空でないかをチェックします。 - 初期化: 結果を格納する
n
を0に初期化し、オーバーフローチェック用のcutoff
値を計算します。 - ループ処理: 文字列
s
の各文字を順に処理します。- 文字から数値への変換: 現在の文字
s[i]
が数字(0
-9
)、小文字アルファベット(a
-z
)、大文字アルファベット(A
-Z
)のいずれかであるかに応じて、対応する数値v
(0-35)に変換します。それ以外の文字は無効な入力としてos.EINVAL
を返します。 - 基数チェック: 変換された数値
v
がbase
以上である場合、その文字は現在の基数では無効であるため、os.EINVAL
を返します(例: 10進数でA
が出現した場合)。 - 乗算オーバーフローチェック:
n
がcutoff
以上の場合、次のn * base
の計算でオーバーフローが発生する可能性が高いため、os.ERANGE
エラーを返します。 - 乗算:
n
をbase
倍します。 - 加算オーバーフローチェック:
n
にv
を加算した結果n1
が、加算前のn
よりも小さい場合、加算によってオーバーフローが発生したことを意味するため、os.ERANGE
エラーを返します。 - 累積:
n
をn1
で更新します。
- 文字から数値への変換: 現在の文字
- 結果返却: 全ての文字を正常に処理できたら、最終的な
n
とnil
エラーを返します。
func Atoui64(s string) (i uint64, err *os.Error)
func Atoui64(s string) (i uint64, err *os.Error) {
// Empty string bad.
if len(s) == 0 {
return 0, os.EINVAL
}
// Look for octal, hex prefix.
if s[0] == '0' && len(s) > 1 {
if s[1] == 'x' || s[1] == 'X' {
// hex
return Btoui64(16, s[2:len(s)]);
}
// octal
return Btoui64(8, s[1:len(s)]);
}
// decimal
return Btoui64(10, s);
}
この関数は、ユーザーが直接呼び出す可能性のあるuint64
変換の主要なエントリポイントです。
- 空文字列チェック: 入力文字列
s
が空の場合、os.EINVAL
エラーを返します。 - プレフィックスチェック:
- 文字列が
'0'
で始まり、かつ2文字以上ある場合、基数プレフィックスの可能性をチェックします。 - もし2文字目が
'x'
または'X'
であれば、残りの文字列(s[2:]
)を16進数としてBtoui64
を呼び出します。 - そうでなければ(
'0'
で始まるが'x'
や'X'
が続かない場合)、残りの文字列(s[1:]
)を8進数としてBtoui64
を呼び出します。
- 文字列が
- 10進数として処理: 上記のプレフィックスに該当しない場合、文字列全体を10進数として
Btoui64
を呼び出します。
このロジックにより、Atoui64
は自動的に入力文字列の基数を判別し、適切な変換を行います。
src/lib/strconv/atoi_test.go
テストファイルでは、新しいBtoui64
関数やcutoff64
関数自体のテストは直接行われていませんが、Atoui64
やAtoi64
などの公開APIが、8進数や16進数の文字列を正しく処理できるようになったことを検証するテストケースが追加されています。
例えば、atoui64tests
配列には、以下のような新しいテストケースが含まれています。
atoui64Test("012345", 012345, nil)
:0
で始まる8進数表記の文字列が正しく変換されることを確認。atoui64Test("0x12345", 0x12345, nil)
:0x
で始まる16進数表記の文字列が正しく変換されることを確認。atoui64Test("0xFFFFFFFFFFFFFFFF", 1<<64-1, nil)
: 16進数でのuint64
最大値の変換を確認。atoui64Test("0x10000000000000000", 1<<64-1, os.ERANGE)
: 16進数でのオーバーフローを確認。atoui64Test("01777777777777777777777", 1<<64-1, nil)
: 8進数でのuint64
最大値の変換を確認。atoui64Test("02000000000000000000000", 1<<64-1, os.ERANGE)
: 8進数でのオーバーフローを確認。
これらのテストケースは、変更されたAtoui64
関数が、従来の10進数だけでなく、8進数と16進数の文字列も正しく解釈し、オーバーフローなどのエラーも適切に処理できるようになったことを包括的に検証しています。
関連リンク
- Go言語
strconv
パッケージのドキュメント (現在のバージョン):- https://pkg.go.dev/strconv
- このコミットはGo言語の非常に初期の段階のものであるため、現在のドキュメントは当時のものとは大きく異なりますが、
ParseInt
やParseUint
などの関数が基数を引数として受け取るようになっていることが確認できます。このコミットで導入された自動判別ロジックは、後のParseInt
やParseUint
のbase
引数に0
を指定した場合の挙動の基礎となっています。
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub):
- Go言語の初期の設計に関する議論(Go開発者ブログなど、当時の情報源):
- このコミットは2009年のものであり、当時のGo言語の設計思想や議論を直接参照できる公開資料は限られている可能性がありますが、Go言語の設計原則(シンプルさ、実用性)がこのような標準ライブラリの機能拡張に影響を与えていると考えられます。
- C言語
strtol
関数に関する情報:- https://en.cppreference.com/w/c/string/byte/strtol
strtol
関数は、基数として0
を指定した場合に、文字列のプレフィックス(0x
,0
)に基づいて自動的に基数を判別する機能を持っており、Goのこの変更のインスピレーション源の一つである可能性があります。
- Python
int()
関数に関する情報:- https://docs.python.org/3/library/functions.html#int
- Pythonの
int()
関数も、文字列のプレフィックスに基づいて基数を自動判別する機能を持っています。