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

[インデックス 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パッケージのAtoiAtouiといった文字列から数値への変換関数は、主に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): 文字列sint型に変換する。
    • Atoui(s string) (uint, error): 文字列suint型に変換する。
    • Atoi64(s string) (int64, error): 文字列sint64型に変換する。
    • Atoui64(s string) (uint64, error): 文字列suint64型に変換する。
  • 数値オーバーフロー (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プレフィックス)とも異なる挙動でした。

変更後、以下の新しい関数とロジックが導入されました。

  1. Btoui64(base int, s string) (n uint64, err *os.Error) の導入:

    • この関数は、任意の基数(base引数で指定)の文字列suint64に変換する汎用的な内部関数として導入されました。
    • 文字列の各文字を対応する数値に変換し、指定された基数で累積的に数値を構築します。
    • 変換中にオーバーフローが発生しないように、cutoff64関数を使用して事前にオーバーフローの可能性をチェックするロジックが組み込まれています。
    • '0'から'9''a'から'z''A'から'Z'の文字をそれぞれ数値にマッピングし、指定された基数を超える文字が出現した場合はos.EINVALエラーを返します。
  2. cutoff64(base int) uint64 の導入:

    • Btoui64関数内で使用されるヘルパー関数です。
    • n * baseuint64の最大値(1<<64 - 1)を超える可能性があるかどうかを効率的に判断するためのカットオフ値を計算します。
    • 具体的には、(1<<64 - 1) / uint64(base) + 1を計算し、現在の累積値nがこのcutoff値以上になった場合、次の乗算でオーバーフローが発生する可能性が高いと判断します。
  3. 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)を呼び出します。
  4. 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まで)に基づいて文字列suint64に変換する中心的なロジックです。

  1. 引数チェック: baseが有効な範囲内か、sが空でないかをチェックします。
  2. 初期化: 結果を格納するnを0に初期化し、オーバーフローチェック用のcutoff値を計算します。
  3. ループ処理: 文字列sの各文字を順に処理します。
    • 文字から数値への変換: 現在の文字s[i]が数字(0-9)、小文字アルファベット(a-z)、大文字アルファベット(A-Z)のいずれかであるかに応じて、対応する数値v(0-35)に変換します。それ以外の文字は無効な入力としてos.EINVALを返します。
    • 基数チェック: 変換された数値vbase以上である場合、その文字は現在の基数では無効であるため、os.EINVALを返します(例: 10進数でAが出現した場合)。
    • 乗算オーバーフローチェック: ncutoff以上の場合、次のn * baseの計算でオーバーフローが発生する可能性が高いため、os.ERANGEエラーを返します。
    • 乗算: nbase倍します。
    • 加算オーバーフローチェック: nvを加算した結果n1が、加算前のnよりも小さい場合、加算によってオーバーフローが発生したことを意味するため、os.ERANGEエラーを返します。
    • 累積: nn1で更新します。
  4. 結果返却: 全ての文字を正常に処理できたら、最終的なnnilエラーを返します。

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変換の主要なエントリポイントです。

  1. 空文字列チェック: 入力文字列sが空の場合、os.EINVALエラーを返します。
  2. プレフィックスチェック:
    • 文字列が'0'で始まり、かつ2文字以上ある場合、基数プレフィックスの可能性をチェックします。
    • もし2文字目が'x'または'X'であれば、残りの文字列(s[2:])を16進数としてBtoui64を呼び出します。
    • そうでなければ('0'で始まるが'x''X'が続かない場合)、残りの文字列(s[1:])を8進数としてBtoui64を呼び出します。
  3. 10進数として処理: 上記のプレフィックスに該当しない場合、文字列全体を10進数としてBtoui64を呼び出します。

このロジックにより、Atoui64は自動的に入力文字列の基数を判別し、適切な変換を行います。

src/lib/strconv/atoi_test.go

テストファイルでは、新しいBtoui64関数やcutoff64関数自体のテストは直接行われていませんが、Atoui64Atoi64などの公開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言語の非常に初期の段階のものであるため、現在のドキュメントは当時のものとは大きく異なりますが、ParseIntParseUintなどの関数が基数を引数として受け取るようになっていることが確認できます。このコミットで導入された自動判別ロジックは、後のParseIntParseUintbase引数に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() 関数に関する情報:

[インデックス 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パッケージのAtoiAtouiといった文字列から数値への変換関数は、主に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): 文字列sint型に変換する。
    • Atoui(s string) (uint, error): 文字列suint型に変換する。
    • Atoi64(s string) (int64, error): 文字列sint64型に変換する。
    • Atoui64(s string) (uint64, error): 文字列suint64型に変換する。
  • 数値オーバーフロー (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プレフィックス)とも異なる挙動でした。

変更後、以下の新しい関数とロジックが導入されました。

  1. Btoui64(base int, s string) (n uint64, err *os.Error) の導入:

    • この関数は、任意の基数(base引数で指定)の文字列suint64に変換する汎用的な内部関数として導入されました。
    • 文字列の各文字を対応する数値に変換し、指定された基数で累積的に数値を構築します。
    • 変換中にオーバーフローが発生しないように、cutoff64関数を使用して事前にオーバーフローの可能性をチェックするロジックが組み込まれています。
    • '0'から'9''a'から'z''A'から'Z'の文字をそれぞれ数値にマッピングし、指定された基数を超える文字が出現した場合はos.EINVALエラーを返します。
  2. cutoff64(base int) uint64 の導入:

    • Btoui64関数内で使用されるヘルパー関数です。
    • n * baseuint64の最大値(1<<64 - 1)を超える可能性があるかどうかを効率的に判断するためのカットオフ値を計算します。
    • 具体的には、(1<<64 - 1) / uint64(base) + 1を計算し、現在の累積値nがこのcutoff値以上になった場合、次の乗算でオーバーフローが発生する可能性が高いと判断します。
  3. 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)を呼び出します。
  4. 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まで)に基づいて文字列suint64に変換する中心的なロジックです。

  1. 引数チェック: baseが有効な範囲内か、sが空でないかをチェックします。
  2. 初期化: 結果を格納するnを0に初期化し、オーバーフローチェック用のcutoff値を計算します。
  3. ループ処理: 文字列sの各文字を順に処理します。
    • 文字から数値への変換: 現在の文字s[i]が数字(0-9)、小文字アルファベット(a-z)、大文字アルファベット(A-Z)のいずれかであるかに応じて、対応する数値v(0-35)に変換します。それ以外の文字は無効な入力としてos.EINVALを返します。
    • 基数チェック: 変換された数値vbase以上である場合、その文字は現在の基数では無効であるため、os.EINVALを返します(例: 10進数でAが出現した場合)。
    • 乗算オーバーフローチェック: ncutoff以上の場合、次のn * baseの計算でオーバーフローが発生する可能性が高いため、os.ERANGEエラーを返します。
    • 乗算: nbase倍します。
    • 加算オーバーフローチェック: nvを加算した結果n1が、加算前のnよりも小さい場合、加算によってオーバーフローが発生したことを意味するため、os.ERANGEエラーを返します。
    • 累積: nn1で更新します。
  4. 結果返却: 全ての文字を正常に処理できたら、最終的なnnilエラーを返します。

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変換の主要なエントリポイントです。

  1. 空文字列チェック: 入力文字列sが空の場合、os.EINVALエラーを返します。
  2. プレフィックスチェック:
    • 文字列が'0'で始まり、かつ2文字以上ある場合、基数プレフィックスの可能性をチェックします。
    • もし2文字目が'x'または'X'であれば、残りの文字列(s[2:])を16進数としてBtoui64を呼び出します。
    • そうでなければ('0'で始まるが'x''X'が続かない場合)、残りの文字列(s[1:])を8進数としてBtoui64を呼び出します。
  3. 10進数として処理: 上記のプレフィックスに該当しない場合、文字列全体を10進数としてBtoui64を呼び出します。

このロジックにより、Atoui64は自動的に入力文字列の基数を判別し、適切な変換を行います。

src/lib/strconv/atoi_test.go

テストファイルでは、新しいBtoui64関数やcutoff64関数自体のテストは直接行われていませんが、Atoui64Atoi64などの公開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言語の非常に初期の段階のものであるため、現在のドキュメントは当時のものとは大きく異なりますが、ParseIntParseUintなどの関数が基数を引数として受け取るようになっていることが確認できます。このコミットで導入された自動判別ロジックは、後のParseIntParseUintbase引数に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() 関数に関する情報: