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

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

このコミットは、Go言語のgo/printerパッケージにおける浮動小数点数の扱いに関するバグ修正です。具体的には、1.0のような浮動小数点定数において、.0が忘れられてしまう(整数定数除算が浮動小数点定数除算の代わりに使われる)問題を解決します。この修正により、gofmtなどのツールがコードを正しくフォーマットできるようになります。

変更されたファイルは以下の通りです。

  • src/pkg/go/printer/nodes.go: 浮動小数点数の計算ロジックが修正されました。
  • src/pkg/go/printer/testdata/declarations.golden: 修正後の期待される出力(テストのゴールデンファイル)が更新されました。
  • src/pkg/go/printer/testdata/declarations.input: テスト入力ファイルが更新され、問題の再現と修正の検証が行われました。

コミット

commit 1f6fba2d5648edb757644431b87f93c8481f36c1
Author: Robert Griesemer <gri@golang.org>
Date:   Fri Oct 5 08:48:23 2012 -0700

    go/printer: don't forget the .0 in 1.0
    
    (use floating-point rather then integer constant division)
    
    gofmt -w src misc
    
    Fixes #3965.
    
    R=r, bsiegert, 0xjnml
    CC=bradfitz, golang-dev
    https://golang.org/cl/6610051

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

https://github.com/golang/go/commit/1f6fba2d5648edb757644431b87f93c8481f36c1

元コミット内容

go/printer: don't forget the .0 in 1.0 (浮動小数点定数除算を使用し、整数定数除算ではない) gofmt -w src misc Fixes #3965.

変更の背景

このコミットは、Go言語のコードフォーマッタであるgofmtや、その基盤となるgo/printerパッケージが、特定の浮動小数点数の計算において誤った挙動を示すバグを修正するために行われました。具体的には、1/rのような除算式において、rが整数定数である場合に、結果が整数として扱われてしまい、浮動小数点数としての精度が失われる問題がありました。

この問題は、GoのIssue 3965として報告されており、go/printerがコードを整形する際に、1.0のような浮動小数点定数の.0部分を「忘れて」しまい、結果として1と整形してしまう可能性がありました。これは、コードの意図を正確に反映しないだけでなく、場合によっては計算結果に影響を与える可能性もありました。この修正は、このような数値の表現に関する不正確さを解消し、gofmtがより信頼性の高いフォーマットを提供できるようにすることを目的としています。

前提知識の解説

浮動小数点数と整数

コンピュータにおける数値の表現には、大きく分けて「整数」と「浮動小数点数」があります。

  • 整数: 小数点以下の部分を持たない数値(例: 1, 10, -5)。
  • 浮動小数点数: 小数点以下の部分を持つ数値(例: 1.0, 3.14, -0.5)。

プログラミング言語では、これらの数値型に対して異なる演算規則が適用されます。

整数除算と浮動小数点除算

除算演算において、オペランド(演算の対象となる数値)の型によって結果の型と計算方法が変わることがあります。

  • 整数除算: 両方のオペランドが整数の場合、結果も整数になります。小数点以下の部分は切り捨てられます。例えば、1 / 40になります。
  • 浮動小数点除算: 少なくとも一方のオペランドが浮動小数点数の場合、結果も浮動小数点数になります。例えば、1.0 / 41 / 4.01.0 / 4.0はすべて0.25になります。

Go言語の「Perfect」定数

Go言語には「型なし定数(untyped constants)」または「Perfect定数」と呼ばれる概念があります。これは、定数が特定の型を持たず、その値が使用される文脈によって適切な型に変換されるというものです。例えば、const c = 1という定数cは、int型として使われることもあれば、float64型として使われることもあります。

しかし、この「Perfect定数」の挙動は、除算のような演算においては注意が必要です。1 / rのような式で、1rも型なしの整数定数である場合、Goコンパイラはデフォルトで整数除算として扱います。このため、1 / 40となり、1.0 / 4とは異なる結果になります。

このコミットの背景にある問題は、まさにこの「Perfect定数」の整数除算の挙動が、go/printerの意図しない結果(浮動小数点数の.0が失われる)を引き起こしていた点にあります。

技術的詳細

このコミットの核心は、src/pkg/go/printer/nodes.goファイル内のexprList関数における除算式の変更です。

変更前:

ratio := float64(size) / float64(prevSize)
useFF = ratio <= 1/r || r <= ratio

変更後:

ratio := float64(size) / float64(prevSize)
useFF = ratio <= 1.0/r || r <= ratio

この変更は非常に小さいですが、その影響は大きいです。 1/rという式において、1は型なしの整数定数であり、rも同様に整数定数です。Go言語の仕様では、このような場合、整数除算が実行されます。例えば、r4の場合、1/4は整数除算の結果0になります。

一方、1.0/rという式では、1.0は型なしの浮動小数点定数です。このため、Goコンパイラは浮動小数点除算を実行します。r4の場合、1.0/4は浮動小数点除算の結果0.25になります。

go/printerパッケージは、Goのソースコードを整形する役割を担っています。このパッケージ内で、特定の条件(ratio1/rの比較)に基づいてフォーマットの挙動を決定するロジックがありました。もし1/rが整数除算によって0になってしまうと、本来意図していた浮動小数点数としての比較が正しく行われず、結果として1.01と整形されてしまうなどの問題が発生していました。

この修正により、11.0と明示的に浮動小数点数として指定することで、常に浮動小数点除算が行われるようになり、go/printerが数値の精度を正しく扱えるようになりました。これにより、gofmtが生成するコードの正確性と一貫性が向上しました。

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

--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -203,7 +203,7 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
 			} else {
 				const r = 4 // threshold
 				ratio := float64(size) / float64(prevSize)
-				useFF = ratio <= 1/r || r <= ratio
+				useFF = ratio <= 1.0/r || r <= ratio
 			}
 		}

コアとなるコードの解説

上記のコードスニペットは、src/pkg/go/printer/nodes.goファイル内のexprList関数の一部です。この関数は、Goの抽象構文木(AST)を走査し、式リストの整形を行う際に呼び出されます。

  • const r = 4: rは定数で、ここでは4という整数値が設定されています。これは、特定のフォーマットの閾値として使用されます。
  • ratio := float64(size) / float64(prevSize): sizeprevSizeは、おそらくコードの要素のサイズや長さを表す変数で、これらをfloat64にキャストしてから除算することで、浮動小数点数としての比率を計算しています。
  • useFF = ratio <= 1.0/r || r <= ratio: この行が今回の修正の核心です。
    • 変更前はratio <= 1/rとなっていました。ここで1r4)はどちらも整数定数であるため、Goのルールに従い整数除算が実行され、1/40となります。
    • 変更後はratio <= 1.0/rとなりました。1.0は浮動小数点定数であるため、浮動小数点除算が実行され、1.0/40.25となります。

このuseFFという変数は、おそらく特定のフォーマット(例えば、改行を入れるかどうかなど)を適用するかどうかを決定するためのフラグです。1/r0になることで、このフラグの計算結果が意図せず変わり、結果としてgofmtの出力に影響を与えていました。1.0/rとすることで、正しい浮動小数点数としての比較が行われ、フォーマットの挙動が修正されました。

関連リンク

  • Go CL 6610051: https://golang.org/cl/6610051
  • Go Issue 3965 (関連する可能性のある情報): 検索結果からは直接的なgolang/goのIssue 3965は見つかりませんでしたが、コミットメッセージにFixes #3965とあるため、内部的なトラッキング番号であるか、または関連する別のリポジトリのIssueである可能性があります。Goのコードレビューシステム(Gerrit)のCLに記載されているため、Goプロジェクト内で解決された問題であることは確かです。

参考にした情報源リンク

  • Go CL 6610051: https://golang.org/cl/6610051 (主要な情報源)
  • Go言語の定数に関する情報 (一般的なGoのドキュメントやチュートリアル)
  • 浮動小数点数と整数除算に関する一般的なプログラミングの知識