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

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

このコミットは、Goランタイムの浮動小数点数出力関数 runtime·printfloat における、符号付きゼロ(signed zero)の扱いに関するバグを修正するものです。具体的には、-0.00.0 と同じように表示されてしまう問題を解決し、IEEE 754浮動小数点数標準に準拠した正確な表現を保証します。この修正には、-0.0 を正しく識別するためのロジックの追加と、その動作を検証するための新しいテストケースが含まれています。

コミット

commit f574726f16887112e9165f9fd0b8832eada76c2f
Author: Carl Shapiro <cshapiro@google.com>
Date:   Mon Dec 9 17:51:30 2013 -0800

    runtime: check for signed zero in printfloat
    
    Fixes #6899
    
    R=golang-dev, r, cshapiro, iant, rsc
    CC=golang-dev
    https://golang.org/cl/38120043

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

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

元コミット内容

runtime: check for signed zero in printfloat
    
Fixes #6899
    
R=golang-dev, r, cshapiro, iant, rsc
CC=golang-dev
https://golang.org/cl/38120043

変更の背景

浮動小数点数の 0 には、数学的な 0 とは別に、+0.0(正のゼロ)と -0.0(負のゼロ)という2つの表現が存在します。これはIEEE 754浮動小数点数標準で定義されており、特に数値計算において、極限や特定のアルゴリズムの挙動を正確にモデル化するために重要です。

Go言語のランタイムにおける浮動小数点数出力関数 runtime·printfloat は、これまで +0.0-0.0 を区別せずに 0.0 として表示していました。しかし、これはIEEE 754標準に準拠しておらず、また、-0.0 が持つ情報(例えば、負の方向からゼロに近づいた結果であること)が失われるという問題がありました。

このコミットは、この問題を修正し、runtime·printfloat-0.0 を正しく -0.000000e+000 のような形式で出力するように変更することで、Go言語の浮動小数点数処理の正確性と標準への準拠を向上させることを目的としています。

前提知識の解説

浮動小数点数とIEEE 754標準

浮動小数点数は、非常に広い範囲の数値を表現するためにコンピュータで使用される数値表現形式です。一般的に、符号部指数部仮数部 の3つの要素で構成されます。

IEEE 754は、浮動小数点数の表現と演算に関する国際標準です。この標準は、異なるコンピュータシステム間での浮動小数点数計算の一貫性を保証するために非常に重要です。IEEE 754は、単精度(32ビット)と倍精度(64ビット)の浮動小数点数を定義しており、特殊な値として無限大(+Inf, -Inf)、非数(NaN)、そして**符号付きゼロ(+0.0, -0.0)**を規定しています。

符号付きゼロ(Signed Zero)

IEEE 754において、+0.0-0.0 は異なるビットパターンを持ちます。

  • +0.0 は、符号ビットが 0 で、指数部と仮数部がすべて 0 です。
  • -0.0 は、符号ビットが 1 で、指数部と仮数部がすべて 0 です。

数学的には +0 = -0 = 0 ですが、浮動小数点数の世界では、これらのゼロは特定の演算(例: 1/x)において異なる結果を生じることがあります。例えば、1 / +0.0+Inf になり、1 / -0.0-Inf になります。この特性は、符号付きゼロを区別する上で非常に重要です。

Go言語の println とランタイム

Go言語の println 関数は、引数を標準出力に出力するための組み込み関数です。内部的には、Goランタイム(runtime パッケージ)が提供する低レベルの関数を呼び出して、実際の出力処理を行います。浮動小数点数の出力には、runtime·printfloat のような関数が使用されます。これらのランタイム関数は、Go言語のコードではなく、C言語やアセンブリ言語で実装されていることが多く、パフォーマンスとシステムレベルの制御を最適化しています。

技術的詳細

このコミットが修正する問題は、runtime·printfloat 関数が浮動小数点数 v を文字列に変換する際に、v0 である場合に符号を考慮していなかった点にあります。

元のコードでは、if(v != 0) という条件でゼロ以外の値を処理していました。これは、+0.0-0.0 の両方に対して v != 0false と評価されるため、両者が同じパス(ゼロを処理するパス)を通ることになります。その結果、-0.0 の符号情報が失われ、0.0 として出力されていました。

修正後のコードでは、if(v == 0) という新しいブロックが追加されました。このブロック内で、v がゼロである場合に、それが +0.0 なのか -0.0 なのかを判別します。

判別の方法は、IEEE 754の特性を利用しています。

  • 1 / +0.0 は正の無限大(+Inf)になります。
  • 1 / -0.0 は負の無限大(-Inf)になります。

したがって、1/v == runtime·neginf という条件をチェックすることで、v-0.0 であるかどうかを正確に判別できます。runtime·neginf はGoランタイム内で定義されている負の無限大を表す定数です。

もし v-0.0 であれば、s = 1; を設定して符号ビットを 1 にします。これにより、後続の出力ロジックで負の符号が考慮され、-0.000000e+000 のような形式で出力されるようになります。

この変更により、Go言語の浮動小数点数出力はIEEE 754標準にさらに準拠し、-0.0 のような特殊な値も正確に表現できるようになりました。

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

diff --git a/src/pkg/runtime/print.c b/src/pkg/runtime/print.c
index 8de3ae4fa1..edb5a1c2ee 100644
--- a/src/pkg/runtime/print.c
+++ b/src/pkg/runtime/print.c
@@ -236,7 +236,10 @@ runtime·printfloat(float64 v)
  	n = 7;	// digits printed
  	e = 0;	// exp
  	s = 0;	// sign
- 	if(v != 0) {
+ 	if(v == 0) {
+ 		if(1/v == runtime·neginf)
+ 			s = 1;
+ 	} else {
  		// sign
  		if(v < 0) {
  			v = -v;
diff --git a/test/fixedbugs/issue6899.go b/test/fixedbugs/issue6899.go
new file mode 100644
index 0000000000..a693bf2850
--- /dev/null
+++ b/test/fixedbugs/issue6899.go
@@ -0,0 +1,13 @@
+// cmpout
+
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "math"
+
+func main() {
+	println(math.Copysign(0, -1))
+}
diff --git a/test/fixedbugs/issue6899.out b/test/fixedbugs/issue6899.out
new file mode 100644
index 0000000000..e2375f0776
--- /dev/null
+++ b/test/fixedbugs/issue6899.out
@@ -0,0 +1 @@
+-0.000000e+000

コアとなるコードの解説

src/pkg/runtime/print.c の変更

runtime·printfloat(float64 v) 関数は、float64 型の浮動小数点数 v を受け取り、その文字列表現を生成するGoランタイムの内部関数です。

変更前:

 	if(v != 0) {
 		// sign
 		if(v < 0) {
 			v = -v;

このコードでは、v0 でない場合にのみ、その符号をチェックしていました。+0.0-0.0 はどちらも 0 と等しいと評価されるため、この if ブロックには入らず、結果として符号が無視されていました。

変更後:

 	if(v == 0) {
 		if(1/v == runtime·neginf)
 			s = 1;
 	} else {
 		// sign
 		if(v < 0) {
 			v = -v;

この修正では、まず v0 であるかどうかを明示的にチェックする新しい if ブロックが追加されました。

  • if(v == 0): v+0.0 または -0.0 のいずれかである場合にこのブロックに入ります。
  • if(1/v == runtime·neginf): ここが肝となる部分です。
    • v+0.0 の場合、1/v+Inf になります。
    • v-0.0 の場合、1/v-Inf になります。
    • runtime·neginf はGoランタイムが定義する負の無限大の定数です。
    • したがって、この条件は v-0.0 である場合にのみ true となります。
  • s = 1;: v-0.0 であると判別された場合、s(符号を表す変数)を 1 に設定します。これにより、後続の出力ロジックで負の符号が適用されます。

else ブロックは、v がゼロではない通常の浮動小数点数を処理するためのもので、以前の if(v != 0) のロジックが移動されています。

test/fixedbugs/issue6899.go の追加

このファイルは、runtime·printfloat の修正を検証するための新しいテストケースです。

package main

import "math"

func main() {
	println(math.Copysign(0, -1))
}
  • math.Copysign(0, -1): この関数は、最初の引数 0 の絶対値に、2番目の引数 -1 の符号を適用した値を返します。結果として、この呼び出しは -0.0 を生成します。
  • println(...): 生成された -0.0println 関数によって出力されます。このテストの目的は、println-0.0 を正しく -0.000000e+000 として出力するかどうかを確認することです。

test/fixedbugs/issue6899.out の追加

このファイルは、test/fixedbugs/issue6899.go の期待される出力結果を定義しています。

-0.000000e+000

この出力は、-0.0 が正しく負の符号付きで表示されることを示しており、コミットの修正が意図通りに機能していることを確認します。

これらの変更により、Go言語は浮動小数点数の符号付きゼロをより正確に扱い、IEEE 754標準への準拠を強化しました。

関連リンク

参考にした情報源リンク

  • IEEE 754 浮動小数点数標準に関する一般的な知識
  • Go言語のランタイムおよび println 関数の動作に関する一般的な知識
  • コミットメッセージとコード差分自体