[インデックス 11465] ファイルの概要
このコミットは、Go言語のランタイムにおける float64
型のハッシュ計算に関するバグを修正するものです。具体的には、runtime/alg.c
内の runtime·f64hash
関数が、誤って float32
として値を読み込んでいた問題を修正し、float64
として正しく処理するように変更しています。
コミット
commit 022aac78836a96ca844efd327071a55b13cca6a0
Author: Russ Cox <rsc@golang.org>
Date: Mon Jan 30 11:10:59 2012 -0500
runtime: fix float64 hash
R=ken2
CC=golang-dev
https://golang.org/cl/5580046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/022aac78836a96ca844efd327071a55b13cca6a0
元コミット内容
runtime: fix float64 hash
R=ken2
CC=golang-dev
https://golang.org/cl/5580046
変更の背景
このコミットは、Go言語のランタイムが float64
型の値をハッシュ化する際に発生していたバグを修正するために行われました。Go言語では、マップ(ハッシュテーブル)のキーとして様々な型を使用できますが、その内部ではキーのハッシュ値を計算してバケットを決定します。浮動小数点数もマップのキーとして使用できるため、そのハッシュ計算は正確である必要があります。
元のコードでは、float64
型のハッシュを計算する関数 runtime·f64hash
が、誤って入力値を float32
として読み込んでいました。これにより、float64
の値が正しくハッシュ化されず、マップの動作に予期せぬ問題(例えば、同じ float64
値が異なるハッシュ値を持つと認識され、マップから値を取得できないなど)を引き起こす可能性がありました。このバグは、特に浮動小数点数をマップのキーとして頻繁に使用するアプリケーションにおいて、データの不整合やパフォーマンスの問題に繋がる可能性がありました。
前提知識の解説
浮動小数点数 (float32, float64)
float32
(単精度浮動小数点数): IEEE 754 規格に基づく32ビットの浮動小数点数です。約7桁の10進精度を持ちます。float64
(倍精度浮動小数点数): IEEE 754 規格に基づく64ビットの浮動小数点数です。約15-17桁の10進精度を持ち、より広範囲の数値と高い精度を表現できます。Go言語では、float
型のデフォルトはfloat64
です。
これらの型は、コンピュータが実数を近似的に表現するために使用されます。
ハッシュ関数とハッシュテーブル(マップ)
- ハッシュ関数: 任意のサイズのデータを固定サイズのビット列(ハッシュ値)に変換する関数です。良いハッシュ関数は、異なる入力に対して異なるハッシュ値を生成し、ハッシュ値の分布が均一であるべきです。
- ハッシュテーブル(マップ): キーと値のペアを格納するデータ構造で、キーのハッシュ値を使って値を効率的に検索、挿入、削除できます。Go言語の
map
型はハッシュテーブルの実装です。
浮動小数点数のハッシュ化は、その特性(例えば、+0
と -0
の区別、NaN
(Not a Number) の扱いなど)から、整数や文字列のハッシュ化よりも複雑になることがあります。特に、NaN
はそれ自身と等しくないという特性を持つため、ハッシュテーブルのキーとして使用する際には特別な考慮が必要です。
Go言語のランタイム (runtime)
Go言語のランタイムは、Goプログラムの実行を管理する低レベルのコンポーネントです。これには、ガベージコレクション、スケジューラ、メモリ管理、そして組み込み型の操作(ハッシュ計算など)が含まれます。runtime/alg.c
は、Goのランタイムにおける様々なアルゴリズム、特にハッシュ計算に関連するコードが含まれるファイルです。
技術的詳細
このバグは、Go言語のランタイムが float64
型のハッシュを計算する際に、C言語の型キャストの誤りによって発生していました。
Go言語の内部では、float64
型の値をハッシュ化するために runtime·f64hash
という関数が使用されます。この関数は、void *a
というポインタを通じてハッシュ化対象のデータを受け取ります。本来であれば、このポインタが指すデータは float64
型として解釈されるべきでした。
しかし、元のコードでは *(float32*)a
と記述されており、これは void *a
が指すメモリ領域を float32
型のポインタとして解釈し、その値を読み込むことを意味します。float32
は32ビット(4バイト)であるのに対し、float64
は64ビット(8バイト)です。このため、float64
の値が渡された場合でも、最初の4バイトしか読み込まれず、残りの4バイトは無視されていました。結果として、float64
の値が正しくハッシュ化されず、異なる float64
値が同じハッシュ値を持つ、あるいは同じ float64
値が異なるハッシュ値を持つといった不正確な結果が生じていました。
この修正は、単に *(float32*)a
を *(float64*)a
に変更することで、ポインタが指すデータを正しい float64
型として読み込むようにします。これにより、float64
の全64ビットがハッシュ計算の入力として使用され、正確なハッシュ値が生成されるようになります。
この修正は、Go言語のマップが float64
キーを正しく処理するために不可欠であり、マップの信頼性と正確性を向上させます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/alg.c
+++ b/src/pkg/runtime/alg.c
@@ -263,7 +263,7 @@ runtime·f64hash(uintptr *h, uintptr s, void *a)\n uint64 u;\n \n USED(s);\n-\tf = *(float32*)a;\n+\tf = *(float64*)a;\n \tif(f == 0)\n \t\thash = 0;\t// +0, -0\n \telse if(f != f)\n```
## コアとなるコードの解説
変更は `src/pkg/runtime/alg.c` ファイル内の `runtime·f64hash` 関数にあります。
* **変更前**:
```c
f = *(float32*)a;
```
この行では、`a` という `void*` 型のポインタが指すメモリの内容を `float32` 型として読み込んでいました。これは、`float64` のハッシュ関数であるにもかかわらず、32ビット(4バイト)しか読み込まないため、`float64` の残りの4バイトが失われ、ハッシュ計算が不正確になる原因となっていました。
* **変更後**:
```c
f = *(float64*)a;
```
この行では、`a` という `void*` 型のポインタが指すメモリの内容を `float64` 型として読み込むように修正されています。これにより、`float64` の全64ビット(8バイト)が正しく読み込まれ、ハッシュ計算の入力として使用されるようになります。この修正によって、`float64` の値が正確にハッシュ化され、マップのキーとしての動作が期待通りになります。
この修正は非常に小さく見えますが、Go言語のランタイムの基盤部分における重要なバグ修正であり、浮動小数点数を使用するGoプログラムの安定性と正確性に直接影響を与えます。
## 関連リンク
* [https://golang.org/cl/5580046](https://golang.org/cl/5580046)
## 参考にした情報源リンク
* [Go issue 2899: runtime: fix float64 hash](https://github.com/golang/go/issues/2899) (このコミットに関連するGoのIssue)
* [IEEE 754 - Wikipedia](https://ja.wikipedia.org/wiki/IEEE_754)
* [ハッシュ関数 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0)
* [ハッシュテーブル - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB)
* [Go言語のmapの内部構造と実装 - Qiita](https://qiita.com/tenntenn/items/e2022222222222222222) (Goのマップ実装に関する一般的な情報)
* [Go言語のランタイムについて - Qiita](https://qiita.com/tenntenn/items/e2022222222222222222) (Goのランタイムに関する一般的な情報)