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

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

このコミットは、Go言語の標準ライブラリにおいて、ASCII文字列を浮動小数点数に変換する基本的な機能(atof64, atof, atof32)を導入し、既存の浮動小数点数を文字列に変換する関数(dtoaf64toaにリネーム)を整理するものです。特に、文字列から浮動小数点数への変換は「非常に弱い (VERY WEAK)」実装であると明記されており、初期段階のシンプルな実装であることが示唆されています。

コミット

commit 175dd773e6808d755e276c13427c7d448dfa29a7
Author: Rob Pike <r@golang.org>
Date:   Thu Nov 6 16:32:28 2008 -0800

    simpleminded ascii to floating point conversion
    
    R=rsc
    DELTA=111  (107 added, 0 deleted, 4 changed)
    OCL=18720
    CL=18725

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

https://github.com/golang/go/commit/175dd773e6808d755e276c13427c7d448dfa29a7

元コミット内容

simpleminded ascii to floating point conversion

R=rsc
DELTA=111  (107 added, 0 deleted, 4 changed)
OCL=18720
CL=18725

変更の背景

このコミットは、Go言語がまだ開発の初期段階にあった2008年11月に行われたものです。当時のGo言語には、文字列から浮動小数点数への変換を行う標準的なユーティリティ関数が不足していました。アプリケーションが外部からの入力(設定ファイル、ユーザー入力など)を数値として処理するためには、このような変換機能が不可欠です。

コミットメッセージにある「simpleminded」やコード内の「THIS CODE IS VERY WEAK」というコメントから、これは完全な実装ではなく、まずは基本的な機能を提供し、後でより堅牢で高性能な実装に置き換えることを意図した、暫定的な措置であったと考えられます。Go言語の設計哲学の一つに「実用性」があり、まずは動くものを提供し、徐々に改善していくアプローチが取られていたことが伺えます。

また、dtoaからf64toaへのリネームは、関数名がより具体的になり、float64型に特化した変換であることを明確にするための変更と推測されます。これは、Go言語の型システムと命名規則の進化の一環である可能性があります。

前提知識の解説

浮動小数点数 (Floating-Point Numbers)

浮動小数点数は、非常に大きい数や非常に小さい数を表現するためにコンピュータで用いられる数値表現形式です。一般的にはIEEE 754標準に基づいており、符号部、指数部、仮数部から構成されます。Go言語ではfloat32(単精度)とfloat64(倍精度)の2つの浮動小数点型があります。

ASCIIから浮動小数点数への変換 (ASCII to Floating-Point Conversion)

文字列(ASCII表現)を浮動小数点数に変換するプロセスは、通常、以下のステップを含みます。

  1. 符号の解析: 文字列の先頭にある+または-を識別し、数値の符号を決定します。
  2. 整数部の解析: 小数点より前の数字列を整数として解析します。
  3. 小数部の解析: 小数点より後の数字列を解析し、その値を10の負のべき乗でスケーリングして加算します。
  4. 指数部の解析: eまたはEに続く指数部(例: 1.23e+4+4)を解析し、最終的な数値を10のそのべき乗で乗算または除算します。

このプロセスは、数値の精度、丸め処理、エラーハンドリング(無効な文字、オーバーフロー、アンダーフローなど)を考慮に入れると非常に複雑になります。

Go言語のreflectパッケージとstringsパッケージ

  • reflectパッケージ: Go言語のreflectパッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。このコミットでは、reflect/tostring.gostrings.dtoaからstrings.f64toaへの関数名変更に対応しています。これは、リフレクションを通じて値の文字列表現を取得する際に、適切な浮動小数点数変換関数を呼び出すための変更です。
  • stringsパッケージ: stringsパッケージは、文字列操作のためのユーティリティ関数を提供します。このコミットの主要な変更は、このパッケージに文字列から浮動小数点数への変換機能を追加することです。

技術的詳細

このコミットの主要な技術的変更は、src/lib/strings.goatof64atofatof32という3つの新しい関数が追加されたことです。これらは、文字列をそれぞれfloat64float(当時のGo言語におけるデフォルトの浮動小数点型、通常はfloat64)、float32に変換します。

atof64関数の実装概要

atof64関数は、文字列をfloat64に変換する中心的なロジックを含んでいます。その実装は、以下のステップで構成されます。

  1. 空文字列のチェック: 入力文字列が空の場合、falseを返してエラーとします。
  2. 符号の処理:
    • 文字列の先頭が+または-であるかをチェックします。
    • -であればnegフラグをtrueに設定し、符号文字をスキップします。
    • +であればそのままスキップします。
  3. 整数部の解析:
    • 小数点(.)または指数表記(e, E)が現れるまで、数字を読み込み、整数値nを構築します。
    • 数字以外の文字が現れた場合、falseを返してエラーとします。
    • resultfloat64(n)を代入します。
  4. 小数部の解析:
    • 現在の文字が小数点(.)の場合、小数点以下を解析します。
    • frac(小数部の整数値)とscale(小数部の桁数に応じた10のべき乗)を計算します。
    • resultfloat64(frac)/scaleを加算します。
    • 数字以外の文字が現れた場合、falseを返してエラーとします。
  5. 指数部の解析:
    • 現在の文字がeまたはEの場合、指数部を解析します。
    • 指数部の符号(+または-)を処理します。
    • 指数値expを読み込みます。
    • expの値に応じて、resultを10のべき乗で乗算または除算します。
    • 数字以外の文字が現れた場合、falseを返してエラーとします。
  6. 最終的な符号の適用: negフラグがtrueであれば、resultを負の値にします。
  7. 結果の返却: 変換されたfloat64値とtrue(成功)を返します。

atofatof32関数

これらの関数は、内部的にatof64を呼び出し、その結果をそれぞれfloat型またはfloat32型にキャストして返します。これは、Go言語における型変換の一般的なパターンです。

dtoaからf64toaへのリネーム

src/lib/strings.goでは、既存のdtoa関数がf64toaにリネームされています。これは、float64型を文字列に変換する関数であることをより明確にするための変更です。src/lib/reflect/tostring.goもこの変更に合わせて更新されています。

既知の弱点

コミットメッセージとコード内のコメントで「THIS CODE IS VERY WEAK.」と明記されているように、この実装にはいくつかの既知の弱点があります。

  • 不完全なエラーハンドリング: 「.」や「e4」、「1e-」のような不完全または無効な形式の文字列を許容してしまう可能性があります。これは、厳密な数値解析においては問題となります。
  • 精度と丸め: 浮動小数点数の変換において、精度や丸め処理に関する考慮が不十分である可能性があります。特に、非常に長い小数部や指数部を持つ数値の場合、正確な変換が保証されないことがあります。
  • パフォーマンス: 文字列を1文字ずつ解析し、ループ内で乗算や除算を行うシンプルな実装であるため、大規模なデータセットや高性能が求められる場面では、より最適化されたアルゴリズム(例: Dragonboxアルゴリズムなど)に比べてパフォーマンスが劣る可能性があります。

これらの弱点は、この機能が「simpleminded」な初期実装であり、将来的に改善されるべき課題として認識されていたことを示しています。

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

src/lib/reflect/tostring.go

--- a/src/lib/reflect/tostring.go
+++ b/src/lib/reflect/tostring.go
@@ -124,7 +124,7 @@ func integer(v int64) string {
 }
 
 func floatingpoint(v float64) string {
-	return strings.dtoa(v);
+	return strings.f64toa(v);
 }
 
 func ValueToString(val Value) string {

floatingpoint関数内でstrings.dtoaの呼び出しがstrings.f64toaに変更されています。

src/lib/strings.go

--- a/src/lib/strings.go
+++ b/src/lib/strings.go
@@ -220,7 +220,7 @@ export func itoa(i int) string {
 
 // Convert float64 to string.  No control over format.
 // Result not great; only useful for simple debugging.
-export func dtoa(v float64) string {
+export func f64toa(v float64) string {
 	var buf [20]byte;
 
 	const n = 7;	// digits printed
@@ -280,5 +280,107 @@ export func dtoa(v float64) string {
 }
 
 export func ftoa(v float) string {
-	return dtoa(float64(v));
+	return f64toa(float64(v));
+}
+
+export func f32toa(v float32) string {
+	return f64toa(float64(v));
+}
+
+// Simple conversion of string to floating point.
+// TODO: make much better. THIS CODE IS VERY WEAK.
+// Lets through some poor cases such as "." and "e4" and "1e-".  Fine.
+export func atof64(s string) (f float64, ok bool) {
+	// empty string bad
+	if len(s) == 0 {
+		return 0, false
+	}
+
+	// pick off leading sign
+	neg := false;
+	if s[0] == '+' {
+		s = s[1:len(s)]
+	} else if s[0] == '-' {
+		neg = true;
+		s = s[1:len(s)]
+	}
+
+	// parse number
+	// first, left of the decimal point.
+	n := uint64(0);
+	i := 0;
+	for ; i < len(s); i++ {
+		if s[i] == '.' || s[i] == 'e' || s[i] == 'E' {
+			break
+		}
+		if s[i] < '0' || s[i] > '9' {
+			return 0, false
+		}
+		n = n*10 + uint64(s[i] - '0')
+	}
+	result := float64(n);
+	if i != len(s) {
+		frac := uint64(0);
+		scale := float64(1);
+		// decimal and fraction
+		if s[i] == '.' {
+			i++;
+			for ; i < len(s); i++ {
+				if s[i] == 'e' || s[i] == 'E' {
+					break
+				}
+				if s[i] < '0' || s[i] > '9' {
+					return 0, false
+				}
+				frac = frac*10 + uint64(s[i] - '0');
+				scale = scale * 10.0;
+			}
+		}
+		result += float64(frac)/scale;
+		// exponent
+		if i != len(s) {	// must be 'e' or 'E'
+			i++;
+			eneg := false;
+			if i < len(s) && s[i] == '-' {
+				eneg = true;
+				i++;
+			} else if i < len(s) && s[i] == '+' {
+				i++;
+			}
+			// this works ok for "1e+" - fine.
+			exp := uint64(0);
+			for ; i < len(s); i++ {
+				if s[i] < '0' || s[i] > '9' {
+					return 0, false
+				}
+				exp = exp*10 + uint64(s[i] - '0');
+			}
+			if eneg {
+				for exp > 0 {
+					result /= 10.0;
+					exp--;
+				}
+			} else {
+				for exp > 0 {
+					result *= 10.0;
+					exp--;
+				}
+			}
+		}
+	}
+
+	if neg {
+		result = -result
+	}
+	return result, true
+}
+
+export func atof(s string) (f float, ok bool) {
+	a, b := atof64(s);
+	return float(a), b;
+}
+
+export func atof32(s string) (f float32, ok bool) {
+	a, b := atof64(s);
+	return float32(a), b;
 }

dtoa関数がf64toaにリネームされ、atof64atofatof32の各関数が新規追加されています。

test/stringslib.go

--- a/test/stringslib.go
+++ b/test/stringslib.go
@@ -109,4 +109,9 @@ func main() {
 
 	// should work if int == int64: is there some way to know?
 	// if itoa(-1<<63) != "-9223372036854775808" { panic("itoa 1<<63") }\n+\n+\t{\n+\t\ta, ok := strings.atof64("-1.2345e4");\n+\t\tif !ok || a != -12345. { panic(a, "atof64 -1.2345e4") }\n+\t}\n }

strings.atof64の簡単なテストケースが追加されています。-1.2345e4-12345.0に正しく変換されるかを確認しています。

コアとなるコードの解説

このコミットの核心は、src/lib/strings.goに追加されたatof64関数です。この関数は、文字列を解析して浮動小数点数に変換する基本的なロジックを実装しています。

  • f64toaへのリネーム: 既存のdtoa関数は、float64を文字列に変換する役割を担っていましたが、より明確な命名としてf64toaにリネームされました。これにより、関数の目的と対象となる型が直感的に理解できるようになりました。
  • atof64の実装:
    • この関数は、文字列の先頭から順に、符号、整数部、小数部、指数部を解析していきます。
    • 各部分の解析は、文字が数字であるか、小数点、または指数記号であるかを確認することで行われます。
    • 整数部と小数部は、uint64として読み込まれ、その後float64に変換されます。小数部はscale変数を使って適切な桁に調整されます。
    • 指数部は、exp変数に読み込まれ、ループを使ってresultを10のべき乗で乗算または除算することで適用されます。
    • エラーハンドリングは非常にシンプルで、無効な文字が見つかった場合や空文字列の場合にfalseを返します。
  • atofatof32: これらはatof64のラッパー関数であり、Go言語の型システムに合わせて、より汎用的なfloat型やfloat32型への変換を提供します。これにより、ユーザーは特定の浮動小数点型を意識せずに変換関数を呼び出すことができます。
  • テストケース: test/stringslib.goに追加されたテストケースは、atof64が負の数と指数表記を含む文字列を正しく解析できることを検証しています。これは、基本的な機能が動作することを確認するための最小限のテストです。

この実装は、Go言語の初期段階において、必要最低限の機能を提供することを目的としたものであり、その後のGo言語の進化とともに、より堅牢で高機能なstrconvパッケージのParseFloat関数などに置き換えられていくことになります。

関連リンク

参考にした情報源リンク