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

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

このコミットは、Go言語のリポジトリ内の test/float_lit.go ファイルに対する変更です。このファイルは、Go言語における浮動小数点リテラルの挙動をテストするために使用されるものです。具体的には、様々な形式で記述された浮動小数点数が正しく解釈され、期待される値を持つことを検証するためのテストケースが含まれています。

コミット

このコミットは、Ken Thompsonによって2008年6月8日に行われました。コミットメッセージは「asdf」と非常に簡潔ですが、これは初期のGo言語開発におけるテストや一時的なコミットによく見られる傾向です。このコミットでは、test/float_lit.go ファイルが変更され、83行が追加され、61行が削除されています。これは、浮動小数点リテラルのテスト方法が大幅に改訂されたことを示しています。

  • コミットハッシュ: ad073b1cf14acccef7284fbfea20bad651ec42e1
  • Author: Ken Thompson ken@golang.org
  • Date: Sun Jun 8 16:16:17 2008 -0700
  • コミットメッセージ:
    asdf
    
    SVN=121608
    
  • 変更ファイル: test/float_lit.go

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

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

元コミット内容

元々のコミットメッセージは「asdf」とだけあり、具体的な変更内容や意図を直接的に示していません。これは、開発の初期段階における一時的なコミットや、テスト目的で作成されたコミットによく見られるパターンです。しかし、コードの変更内容を見ることで、このコミットの真の目的が浮動小数点リテラルのテストの改善であることが明らかになります。

変更の背景

浮動小数点数は、コンピュータが実数を近似的に表現するためのデータ型です。その性質上、厳密な等価比較が困難であり、丸め誤差や表現可能な範囲の限界といった問題が常に伴います。Go言語のような新しいプログラミング言語を開発する際には、言語仕様に沿って浮動小数点リテラルが正確にパースされ、期待される値に変換されることを保証するための堅牢なテストが不可欠です。

このコミットの背景には、Go言語のコンパイラやランタイムが様々な形式の浮動小数点リテラル(例: 0., .0, 0.0, 指数表記 1e2 など)を正しく処理できることを検証する必要があったと考えられます。以前のテストでは単にリテラルを列挙するだけでしたが、このコミットでは、より厳密な数値比較を行うためのヘルパー関数を導入し、テストの信頼性を向上させています。

前提知識の解説

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

浮動小数点数は、非常に大きな数から非常に小さな数まで、広範囲の実数を表現するために使用されるコンピュータの数値表現形式です。ほとんどのシステムでは、IEEE 754標準に従って実装されています。この標準では、数を符号、仮数(有効数字)、指数という3つの部分で表現します。

  • 符号 (Sign): 数の正負を示します。
  • 仮数 (Mantissa/Significand): 数の精度を決定する部分です。
  • 指数 (Exponent): 小数点の位置を決定し、数の大きさを表します。

この表現方法により、固定小数点数では扱えないような非常に広い範囲の数を表現できますが、その代償として、すべての実数を正確に表現できるわけではなく、丸め誤差が発生する可能性があります。

浮動小数点数の比較と誤差

浮動小数点数の性質上、a == b のような厳密な等価比較は、予期せぬ結果を招くことがあります。例えば、0.1 + 0.20.3 と厳密に等しくならない場合があります。これは、0.10.2 といった十進数が二進数で正確に表現できないために発生する丸め誤差によるものです。

このため、浮動小数点数を比較する際には、ある程度の許容誤差(イプシロン、epsilon)を設けて比較するのが一般的です。つまり、2つの数値 ab が「ほぼ等しい」と判断されるのは、それらの差の絶対値が非常に小さな閾値(イプシロン)よりも小さい場合です。

|a - b| < epsilon

このコミットで導入された close 関数は、まさにこのイプシロン比較の概念を実装しています。

Go言語のテストフレームワーク(初期)

Go言語の初期のテストは、現在の testing パッケージのような洗練されたフレームワークがまだ存在しないか、あるいは十分に成熟していなかった可能性があります。このコミットに見られるように、テストの失敗を panic で表現したり、手動で比較関数を記述したりするアプローチは、初期のGo言語開発における一般的なテスト手法だったと考えられます。

技術的詳細

このコミットの主要な変更点は、test/float_lit.go ファイルにおける浮動小数点リテラルのテスト方法の根本的な変更です。

  1. close 関数の導入: 浮動小数点数の近似比較を行うための close 関数が導入されました。この関数は、2つの double 型の引数 ab を受け取り、それらが「十分に近似している」場合に true を返します。

    func
    close(a, b double) bool
    {
        if a == 0 {
            if b == 0 {
                return true;
            }
            return false;
        }
        d := a-b;
        if d < 0 {
            d = -d;
        }
        e := a;
        if e < 0 {
            e = -e;
        }
        if e*1.0e-14 < d { // ここでイプシロン比較が行われる
            return true;
        }
        return false;
    }
    

    この close 関数は、a がゼロの場合の特殊なケース(b もゼロなら true)と、それ以外の場合の相対誤差に基づく比較を行います。e*1.0e-14 がイプシロンとして機能しており、a の絶対値に 1.0e-14 を掛けたものが許容誤差となります。これは、数値の大きさに応じて許容誤差を調整する、一般的な相対誤差比較の手法です。

  2. テストロジックの変更: 以前は、様々な浮動小数点リテラルを単に []float(...) の中に列挙するだけでした。これは、コンパイラがこれらのリテラルをエラーなくパースできることを確認する目的だったと考えられます。

    変更後、これらのリテラルは close 関数と panic ステートメントを組み合わせたアサーションに置き換えられました。例えば、if !close(0., 0.) { panic ... } のように、リテラルがそれ自身と close であることを確認し、そうでなければ panic を発生させてテストを失敗させます。これにより、リテラルが期待通りの数値に評価されているかをより厳密に検証できるようになりました。

  3. テストされる浮動小数点リテラルの種類: テストケースには、以下のような様々な形式の浮動小数点リテラルが含まれています。

    • 整数部のみ、小数部のみ、両方を持つ形式 (0., +10., -210., .0, +.01, -.012, 0.0, +10.01, -210.012)
    • 指数表記 (0E+1, +10e2, -210e3, 0E-1, +0e23, -0e345)
    • 小数点と指数表記の組み合わせ (0.E1, +10.e+2, -210.e-3, .0E1, +.01e2, -.012e3, 0.0E1, +10.01e2, -210.012e3)
    • 非常に大きな指数や小さな指数を持つリテラル (0.E+12, +10.e23, -210.e34, .0E-12, +.01e23, -.012e34, 0.0E12, +10.01e23, -210.012e34)
    • 一部のテストケースがコメントアウトされている点も注目に値します。例えば、//\tif !close(-210e345, -210e345) { panic ... } のような行です。これは、当時のGo言語の浮動小数点数の実装が、非常に大きな指数や小さな指数を持つ数値の表現に限界があったか、あるいは特定の環境で問題が発生していた可能性を示唆しています。

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

test/float_lit.go ファイルの変更差分は以下の通りです。

--- a/test/float_lit.go
+++ b/test/float_lit.go
@@ -6,66 +6,88 @@

 package main

+func
+close(a, b double) bool
+{
+   if a == 0 {
+       if b == 0 {
+           return true;
+       }
+       return false;
+   }
+   d := a-b;
+   if d < 0 {
+       d = -d;
+   }
+   e := a;
+   if e < 0 {
+       e = -e;
+   }
+   if e*1.0e-14 < d {
+       return true;
+   }
+   return false;
+}
+
 func main() {
-  []float(
-    0.,
-    +10.,
-    -210.,
-
-    .0,
-    +.01,
-    -.012,
-
-    0.0,
-    +10.01,
-    -210.012,
-
-    0E+1,
-    +10e2,
-    -210e3,
-
-    0E-1,
-    +0e23,
-    -0e345,
-
-    0E1,
-    +10e23,
-    -210e345,
-
-    0.E1,
-    +10.e+2,
-    -210.e-3,
-
-    .0E1,
-    +.01e2,
-    -.012e3,
-
-    0.0E1,
-    +10.01e2,
-    -210.012e3,
-
-    0.E+12,
-    +10.e23,
-    -210.e34,
-
-    .0E-12,
-    +.01e23,
-    -.012e34,
-
-    0.0E12,
-    +10.01e23,
-    -210.012e34,
-
-    0.E123,
-    +10.e+234,
-    -210.e-345,
-
-    .0E123,
-    +.01e234,
-    -.012e345,
-
-    0.0E123,
-    +10.01e234,
-    -210.012e345
-  );
+
+   if !close(0., 0.) { panic "0. is ", 0., " should be ", 0., "\n"; }
+   if !close(+10., +10.) { panic "+10. is ", +10., " should be ", +10., "\n"; }
+   if !close(-210., -210.) { panic "-210. is ", -210., " should be ", -210., "\n"; }
+
+   if !close(.0, .0) { panic ".0 is ", .0, " should be ", .0, "\n"; }
+   if !close(+.01, +.01) { panic "+.01 is ", +.01, " should be ", +.01, "\n"; }
+   if !close(-.012, -.012) { panic "-.012 is ", -.012, " should be ", -.012, "\n"; }
+
+   if !close(0.0, 0.0) { panic "0.0 is ", 0.0, " should be ", 0.0, "\n"; }
+   if !close(+10.01, +10.01) { panic "+10.01 is ", +10.01, " should be ", +10.01, "\n"; }
+   if !close(-210.012, -210.012) { panic "-210.012 is ", -210.012, " should be ", -210.012, "\n"; }
+
+   if !close(0E+1, 0E+1) { panic "0E+1 is ", 0E+1, " should be ", 0E+1, "\n"; }
+   if !close(+10e2, +10e2) { panic "+10e2 is ", +10e2, " should be ", +10e2, "\n"; }
+   if !close(-210e3, -210e3) { panic "-210e3 is ", -210e3, " should be ", -210e3, "\n"; }
+
+   if !close(0E-1, 0E-1) { panic "0E-1 is ", 0E-1, " should be ", 0E-1, "\n"; }
+   if !close(+0e23, +0e23) { panic "+0e23 is ", +0e23, " should be ", +0e23, "\n"; }
+   if !close(-0e345, -0e345) { panic "-0e345 is ", -0e345, " should be ", -0e345, "\n"; }
+
+   if !close(0E1, 0E1) { panic "0E1 is ", 0E1, " should be ", 0E1, "\n"; }
+   if !close(+10e23, +10e23) { panic "+10e23 is ", +10e23, " should be ", +10e23, "\n"; }
+//   if !close(-210e345, -210e345) { panic "-210e345 is ", -210e345, " should be ", -210e345, "\n"; }
+
+   if !close(0.E1, 0.E1) { panic "0.E1 is ", 0.E1, " should be ", 0.E1, "\n"; }
+   if !close(+10.e+2, +10.e+2) { panic "+10.e+2 is ", +10.e+2, " should be ", +10.e+2, "\n"; }
+   if !close(-210.e-3, -210.e-3) { panic "-210.e-3 is ", -210.e-3, " should be ", -210.e-3, "\n"; }
+
+   if !close(.0E1, .0E1) { panic ".0E1 is ", .0E1, " should be ", .0E1, "\n"; }
+   if !close(+.01e2, +.01e2) { panic "+.01e2 is ", +.01e2, " should be ", +.01e2, "\n"; }
+   if !close(-.012e3, -.012e3) { panic "-.012e3 is ", -.012e3, " should be ", -.012e3, "\n"; }
+
+   if !close(0.0E1, 0.0E1) { panic "0.0E1 is ", 0.0E1, " should be ", 0.0E1, "\n"; }
+   if !close(+10.01e2, +10.01e2) { panic "+10.01e2 is ", +10.01e2, " should be ", +10.01e2, "\n"; }
+   if !close(-210.012e3, -210.012e3) { panic "-210.012e3 is ", -210.012e3, " should be ", -210.012e3, "\n"; }
+
+   if !close(0.E+12, 0.E+12) { panic "0.E+12 is ", 0.E+12, " should be ", 0.E+12, "\n"; }
+   if !close(+10.e23, +10.e23) { panic "+10.e23 is ", +10.e23, " should be ", +10.e23, "\n"; }
+   if !close(-210.e34, -210.e34) { panic "-210.e34 is ", -210.e34, " should be ", -210.e34, "\n"; }
+
+   if !close(.0E-12, .0E-12) { panic ".0E-12 is ", .0E-12, " should be ", .0E-12, "\n"; }
+   if !close(+.01e23, +.01e23) { panic "+.01e23 is ", +.01e23, " should be ", +.01e23, "\n"; }
+   if !close(-.012e34, -.012e34) { panic "-.012e34 is ", -.012e34, " should be ", -.012e34, "\n"; }
+
+   if !close(0.0E12, 0.0E12) { panic "0.0E12 is ", 0.0E12, " should be ", 0.0E12, "\n"; }
+   if !close(+10.01e23, +10.01e23) { panic "+10.01e23 is ", +10.01e23, " should be ", +10.01e23, "\n"; }
+   if !close(-210.012e34, -210.012e34) { panic "-210.012e34 is ", -210.012e34, " should be ", -210.012e34, "\n"; }
+
+   if !close(0.E123, 0.E123) { panic "0.E123 is ", 0.E123, " should be ", 0.E123, "\n"; }
+   if !close(+10.e+234, +10.e+234) { panic "+10.e+234 is ", +10.e+234, " should be ", +10.e+234, "\n"; }
+//   if !close(-210.e-345, -210.e-345) { panic "-210.e-345 is ", -210.e-345, " should be ", -210.e-345, "\n"; }
+
+   if !close(.0E123, .0E123) { panic ".0E123 is ", .0E123, " should be ", .0E123, "\n"; }
+//   if !close(+.01e234, +.01e234) { panic "+.01e234 is ", +.01e234, " should be ", +.01e234, "\n"; }
+//   if !close(-.012e345, -.012e345) { panic "-.012e345 is ", -.012e345, " should be ", -.012e345, "\n"; }
+
+   if !close(0.0E123, 0.0E123) { panic "0.0E123 is ", 0.0E123, " should be ", 0.0E123, "\n"; }
+//   if !close(+10.01e234, +10.01e234) { panic "+10.01e234 is ", +10.01e234, " should be ", +10.01e234, "\n"; }
+//   if !close(-210.012e345, -210.012e345) { panic "-210.012e345 is ", -210.012e345, " should be ", -210.012e345, "\n"; }
  }

コアとなるコードの解説

close 関数

close 関数は、浮動小数点数の比較における丸め誤差を考慮するための重要なヘルパー関数です。

func
close(a, b double) bool
{
    if a == 0 { // aがゼロの場合の特殊処理
        if b == 0 {
            return true; // 両方ゼロなら等しい
        }
        return false; // aがゼロでbがゼロでないなら等しくない
    }
    d := a-b; // 差を計算
    if d < 0 {
        d = -d; // 差の絶対値
    }
    e := a; // 比較基準となる値
    if e < 0 {
        e = -e; // 比較基準の絶対値
    }
    // 相対誤差による比較: 基準値の1.0e-14倍よりも差の絶対値が小さいか
    if e*1.0e-14 < d {
        return true; // 差が許容範囲内なら近似的に等しい
    }
    return false; // 差が許容範囲外なら近似的に等しくない
}

この関数は、以下のロジックで動作します。

  1. ゼロの特殊処理: a0 の場合、b0 であれば true を返します。これは、0 の相対誤差を計算することができないため、特別なケースとして扱われます。
  2. 差の絶対値: ab の差 d を計算し、その絶対値を取ります。
  3. 比較基準の絶対値: a の絶対値 e を計算します。これが相対誤差の基準となります。
  4. 相対誤差比較: e1.0e-14(非常に小さな値、イプシロン)を掛けたものと、差の絶対値 d を比較します。もし de * 1.0e-14 よりも小さければ、2つの数値は近似的に等しいと判断し true を返します。

1.0e-14 という値は、double 型(通常は64ビット浮動小数点数)の精度を考慮した一般的なイプシロン値です。この値は、浮動小数点数の有効桁数に基づいて選択されます。

main 関数内のテストロジック

main 関数では、様々な浮動小数点リテラルに対して close 関数を用いたアサーションが行われています。

if !close(0., 0.) { panic "0. is ", 0., " should be ", 0., "\n"; }

この行は、0. という浮動小数点リテラルが、それ自身と close であることを確認しています。もし close(0., 0.)false を返した場合(つまり、0.0. と近似的に等しくないと判断された場合)、panic が発生し、テストが失敗します。panic メッセージには、どのリテラルが問題で、期待される値は何だったのかが示されます。

このテストの目的は、Go言語のコンパイラがこれらの浮動小数点リテラルを正しくパースし、内部的に正確な浮動小数点値に変換できることを検証することです。様々な形式のリテラルをテストすることで、Go言語の字句解析器とパーサーが浮動小数点数の仕様に準拠していることを保証します。

コメントアウトされた行は、当時のGo言語の浮動小数点数の実装が、非常に大きな指数(例: e345)や非常に小さな指数(例: e-345)を持つ数値の表現に限界があったことを示唆しています。これは、double 型の表現範囲を超えるか、あるいは特定のプラットフォームでの挙動が不安定だったため、一時的にテストから除外された可能性があります。

関連リンク

参考にした情報源リンク

  • コミット情報 (./commit_data/127.txt)
  • Go言語の浮動小数点数とテストに関する一般的な知識
  • IEEE 754 浮動小数点標準に関する一般的な知識