[インデックス 1081] ファイルの概要
このコミットは、Go言語の初期のランタイムおよびコンパイラに、float32bits
と float64bits
という2つの新しいシステム関数を追加するものです。これらの関数は、それぞれ float32
および float64
型の浮動小数点数の「生(raw)のビット表現」を、符号なし整数(uint32
および uint64
)として取得する機能を提供します。これにより、浮動小数点数の内部表現を直接操作したり、デバッグしたりする低レベルな操作が可能になります。
コミット
commit 335a5236e72b1d059fabfbfdd2ac13107aec4072
Author: Rob Pike <r@golang.org>
Date: Thu Nov 6 15:48:36 2008 -0800
add sys.float32bits, sys.float64bits
R=rsc
DELTA=21 (21 added, 0 deleted, 0 changed)
OCL=18709
CL=18718
---
src/cmd/gc/sys.go | 2 ++\n src/cmd/gc/sysimport.c | 2 ++\n src/runtime/runtime.c | 17 +++++++++++++++++
3 files changed, 21 insertions(+)
diff --git a/src/cmd/gc/sys.go b/src/cmd/gc/sys.go
index 0ee9deb51b..72e36a0c75 100644
--- a/src/cmd/gc/sys.go
+++ b/src/cmd/gc/sys.go
@@ -49,6 +49,8 @@ export func isInf(float64, int) bool; // test for infinity
export func isNaN(float64) bool; // test for not-a-number
export func Inf(int) float64; // return signed Inf
export func NaN() float64; // return a NaN
+export func float32bits(float32) uint32; // raw bits
+export func float64bits(float64) uint64; // raw bits
export func newmap(keysize int, valsize int,
keyalg int, valalg int,
diff --git a/src/cmd/gc/sysimport.c b/src/cmd/gc/sysimport.c
index 02b5a86e81..9ee99a1892 100644
--- a/src/cmd/gc/sysimport.c
+++ b/src/cmd/gc/sysimport.c
@@ -39,6 +39,8 @@ char *sysimport =
\"export func sys.isNaN (? float64) (? bool)\\n\"\n \t\"export func sys.Inf (? int) (? float64)\\n\"\n \t\"export func sys.NaN () (? float64)\\n\"\n+\t\"export func sys.float32bits (? float32) (? uint32)\\n\"\n+\t\"export func sys.float64bits (? float64) (? uint64)\\n\"\n \t\"export func sys.newmap (keysize int, valsize int, keyalg int, valalg int, hint int) (hmap *map[any] any)\\n\"\n \t\"export func sys.mapaccess1 (hmap *map[any] any, key any) (val any)\\n\"\n \t\"export func sys.mapaccess2 (hmap *map[any] any, key any) (val any, pres bool)\\n\"\ndiff --git a/src/runtime/runtime.c b/src/runtime/runtime.c
index db31b77df3..a0d97dcda9 100644
--- a/src/runtime/runtime.c
+++ b/src/runtime/runtime.c
@@ -347,6 +347,23 @@ sys·NaN(float64 out)\n \tFLUSH(&out);\n }\n \n+// func float32bits(float32) uint32; // raw bits of float32\n+void\n+sys·float32bits(float32 din, uint32 iou)\n+{\n+\tiou = *(uint32*)&din;\n+\tFLUSH(&iou);\n+}\n+\n+// func float64bits(float64) uint64; // raw bits of float64\n+void\n+sys·float64bits(float64 din, uint64 iou)\n+{\n+\tiou = *(uint64*)&din;\n+\tFLUSH(&iou);\n+}\n+\n+\n static int32 argc;\n static uint8** argv;\n static int32 envc;\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/335a5236e72b1d059fabfbfdd2ac13107aec4072](https://github.com/golang/go/commit/335a5236e72b1d059fabfbfdd2ac13107aec4072)
## 元コミット内容
このコミットは、Go言語のシステムパッケージ(`sys`)に `float32bits` と `float64bits` という2つの関数を追加します。これらの関数は、それぞれ `float32` と `float64` の浮動小数点数の内部的なビット表現を `uint32` と `uint64` として返します。
## 変更の背景
Go言語の初期開発段階において、低レベルな操作や特定の最適化、あるいはデバッグの目的で、浮動小数点数の内部表現に直接アクセスする必要がありました。IEEE 754浮動小数点標準では、数値が符号、指数部、仮数部という特定のビットパターンで表現されます。このビットパターンを直接操作したり、検査したりする機能は、例えば、浮動小数点数の比較(特にNaNや無限大の扱い)、ハッシュ計算、あるいは特定のビット操作に基づくアルゴリズムの実装において有用です。
このコミットは、Goの標準ライブラリに `math.Float32bits` および `math.Float64bits` として後に公開される機能の、初期の内部的な実装に相当します。`sys` パッケージは、Goのランタイムやコンパイラが内部的に使用する低レベルな関数群をエクスポートするために存在していました。これにより、GoのコードからC言語で実装されたランタイムの機能にアクセスできるようになります。
## 前提知識の解説
### 1. IEEE 754 浮動小数点標準
現代のコンピュータにおける浮動小数点数の表現は、ほとんどがIEEE 754標準に基づいています。この標準は、単精度(32ビット、`float32`)と倍精度(64ビット、`float64`)の2つの主要な形式を定義しています。
* **単精度 (float32)**: 32ビットで構成され、以下の部分に分かれます。
* 1ビット: 符号ビット (Sign bit) - 0は正、1は負
* 8ビット: 指数部 (Exponent) - 2のべき乗を表す
* 23ビット: 仮数部 (Mantissa/Fraction) - 有効数字を表す
* **倍精度 (float64)**: 64ビットで構成され、以下の部分に分かれます。
* 1ビット: 符号ビット
* 11ビット: 指数部
* 52ビット: 仮数部
この標準により、異なるシステム間での浮動小数点計算の互換性が保証されます。また、通常の数値だけでなく、無限大(`Inf`)や非数(`NaN`)といった特殊な値も定義されており、それぞれ特定のビットパターンで表現されます。
### 2. 型のビット表現の再解釈 (Type Punning)
C言語のような低レベル言語では、メモリ上の特定のバイト列を異なるデータ型として解釈するテクニックがよく用いられます。これを「型パンニング (Type Punning)」と呼びます。このコミットで使われている `*(uint32*)&din;` というCのイディオムは、まさにこの型パンニングの典型例です。
* `&din`: `float32` 型の変数 `din` のメモリアドレスを取得します。
* `(uint32*)`: そのメモリアドレスを `uint32` 型へのポインタとしてキャストします。これは、「このメモリ位置にあるデータを `uint32` として扱いたい」という意図を示します。
* `*`: キャストされたポインタを逆参照(デリファレンス)します。これにより、`float32` が占めていたメモリ領域のビットパターンが、`uint32` 型の値として読み出されます。
この操作は、数値的な型変換(例: `float` から `int` への切り捨て)とは異なり、ビットパターンそのものを変更せずに、その解釈方法だけを変更します。
### 3. Go言語の `sys` パッケージ
Go言語の初期には、`sys` パッケージという特別なパッケージが存在しました。これは、Goのランタイム(Goプログラムを実行するための基盤)やコンパイラが内部的に使用する、非常に低レベルな関数群をGoのコードから呼び出せるようにするためのものでした。これらの関数は、Goの標準ライブラリの `unsafe` パッケージや `runtime` パッケージが提供する機能よりもさらにプリミティブな操作を可能にすることがありました。多くの場合、`sys` パッケージの関数はC言語で実装されており、Goのコードからは外部関数としてリンクされていました。
## 技術的詳細
このコミットの核心は、浮動小数点数のビット表現を直接取得する機能の追加です。これは、GoのランタイムがC言語で実装されていた初期の段階において、Cの型パンニングの機能を利用して実現されています。
`src/cmd/gc/sys.go` と `src/cmd/gc/sysimport.c` は、Goコンパイラが `sys` パッケージの関数を認識し、Goのコードから呼び出せるようにするための宣言部分です。
* `src/cmd/gc/sys.go`: Go言語のソースコードから `sys` パッケージの関数として `float32bits` と `float64bits` がエクスポートされることを宣言しています。これは、Goのユーザーコードがこれらの関数を呼び出すためのインターフェースを定義します。
* `src/cmd/gc/sysimport.c`: Goコンパイラの内部で、C言語で実装されたランタイム関数をGoの `sys` パッケージの関数としてインポートするための定義です。GoコンパイラがGoのソースコードを解析する際に、`sys.float32bits` や `sys.float64bits` といった呼び出しを、対応するC言語のランタイム関数にマッピングするために使用されます。
実際の機能実装は `src/runtime/runtime.c` にあります。ここで、C言語の関数 `sys·float32bits` と `sys·float64bits` が定義されています。
```c
// func float32bits(float32) uint32; // raw bits of float32
void
sys·float32bits(float32 din, uint32 iou)
{
iou = *(uint32*)&din;
FLUSH(&iou);
}
// func float64bits(float64) uint64; // raw bits of float64
void
sys·float64bits(float64 din, uint64 iou)
{
iou = *(uint64*)&din;
FLUSH(&iou);
}
このCコードは、入力された浮動小数点数 din
のメモリアドレスを uint32*
または uint64*
にキャストし、そのポインタを逆参照することで、浮動小数点数のビットパターンを符号なし整数として iou
に代入しています。FLUSH(&iou);
は、コンパイラの最適化によって変数がレジスタにのみ存在し、メモリに書き込まれないことを防ぐためのマクロで、結果が確実にメモリに書き込まれるようにします。
この実装は、Go言語がまだC言語のランタイムに大きく依存していた初期段階の典型的な例であり、GoのコードからCの低レベル機能を利用するためのブリッジとして sys
パッケージが機能していたことを示しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、以下の3つのファイルにまたがっています。
-
src/cmd/gc/sys.go
:--- a/src/cmd/gc/sys.go +++ b/src/cmd/gc/sys.go @@ -49,6 +49,8 @@ export func isInf(float64, int) bool; // test for infinity export func isNaN(float64) bool; // test for not-a-number export func Inf(int) float64; // return signed Inf export func NaN() float64; // return a NaN +export func float32bits(float32) uint32; // raw bits +export func float64bits(float64) uint64; // raw bits export func newmap(keysize int, valsize int, keyalg int, valalg int,
Go言語側から利用可能な
sys
パッケージの関数としてfloat32bits
とfloat64bits
が追加されています。 -
src/cmd/gc/sysimport.c
:--- a/src/cmd/gc/sysimport.c +++ b/src/cmd/gc/sysimport.c @@ -39,6 +39,8 @@ char *sysimport = \"export func sys.isNaN (? float64) (? bool)\\n\"\n \t\"export func sys.Inf (? int) (? float64)\\n\"\n \t\"export func sys.NaN () (? float64)\\n\"\n+\t\"export func sys.float32bits (? float32) (? uint32)\\n\"\n+\t\"export func sys.float64bits (? float64) (? uint64)\\n\"\n \t\"export func sys.newmap (keysize int, valsize int, keyalg int, valalg int, hint int) (hmap *map[any] any)\\n\"\n \t\"export func sys.mapaccess1 (hmap *map[any] any, key any) (val any)\\n\"\n \t\"export func sys.mapaccess2 (hmap *map[any] any, key any) (val any, pres bool)\\n\"\n ``` GoコンパイラがC言語で実装されたランタイム関数をGoの `sys` パッケージの関数として認識するためのインポート定義が追加されています。
-
src/runtime/runtime.c
:--- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -347,6 +347,23 @@ sys·NaN(float64 out)\n \tFLUSH(&out);\n }\n \n+// func float32bits(float32) uint32; // raw bits of float32\n+void\n+sys·float32bits(float32 din, uint32 iou)\n+{\n+\tiou = *(uint32*)&din;\n+\tFLUSH(&iou);\n+}\n+\n+// func float64bits(float64) uint64; // raw bits of float64\n+void\n+sys·float64bits(float64 din, uint64 iou)\n+{\n+\tiou = *(uint64*)&din;\n+\tFLUSH(&iou);\n+}\n+\n+\n static int32 argc;\n static uint8** argv;\n static int32 envc;\
sys·float32bits
とsys·float64bits
の実際のC言語による実装が追加されています。
コアとなるコードの解説
src/runtime/runtime.c
に追加されたC言語の関数が、このコミットの機能の中核をなします。
void
sys·float32bits(float32 din, uint32 iou)
{
iou = *(uint32*)&din;
FLUSH(&iou);
}
この関数 sys·float32bits
は、float32
型の引数 din
を受け取り、そのビットパターンを uint32
型の引数 iou
に格納します。
&din
:din
変数のメモリアドレスを取得します。(uint32*)
: このアドレスをuint32
型へのポインタとして解釈するようにキャストします。*
: キャストされたポインタを逆参照することで、din
が占めるメモリ領域の32ビットをuint32
型の値として読み出します。iou = ...
: 読み出したビットパターンをiou
に代入します。FLUSH(&iou);
: これはGoのランタイム内部で使用されるマクロで、コンパイラの最適化によってiou
の値がレジスタにのみ保持され、呼び出し元に正しく返されないことを防ぐために、明示的にメモリに書き出すことを保証します。
sys·float64bits
も同様に、float64
型の引数 din
の64ビットのビットパターンを uint64
型の引数 iou
に格納します。
これらの関数は、数値的な変換(例: 3.14
を 3
に切り捨てる)を行うのではなく、浮動小数点数がメモリ上でどのようにバイナリデータとして表現されているかを、そのまま整数として取り出す役割を果たします。これは、IEEE 754標準に準拠した浮動小数点数の内部構造を理解し、操作するために不可欠な機能です。
関連リンク
- IEEE 754 浮動小数点標準: https://ja.wikipedia.org/wiki/IEEE_754
- Go言語の
math.Float32bits
(現在の標準ライブラリ関数): https://pkg.go.dev/math#Float32bits - Go言語の
math.Float64bits
(現在の標準ライブラリ関数): https://pkg.go.dev/math#Float64bits
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/gc/
,src/runtime/
ディレクトリ) - C言語のポインタと型キャストに関する一般的な知識
- IEEE 754 浮動小数点標準に関するドキュメント
- Go言語の初期の設計に関する議論やドキュメント (Goの公式ブログやメーリングリストアーカイブなど)
- Goの
sys
パッケージに関する情報 (現在はほとんどの機能がruntime
やunsafe
パッケージに統合されているため、古い情報源を参照する必要がある) - Goのコミット履歴 (GitHub)
- GoのChange List (CL) システム (Gerrit)