これらの再解釈キャストでは、厳密なエイリアシング規則 (C++ 標準のセクション 3.10) に実際に違反しています。コンパイラの最適化をオンにすると、これはおそらくあなたの顔に吹き飛ばされます。
C++ 標準、セクション 3.10 パラグラフ 15 は次のように述べています。
プログラムが、次の型以外の左辺値を介してオブジェクトの保存された値にアクセスしようとした場合、動作は未定義です。
- オブジェクトの動的タイプ、
- オブジェクトの動的型の cv 修飾バージョン、
- オブジェクトの動的タイプに類似したタイプ、
- オブジェクトの動的な型に対応する符号付きまたは符号なしの型である型、
- オブジェクトの動的型の cv 修飾バージョンに対応する符号付きまたは符号なしの型である型、
- 前述の型の 1 つをメンバーに含む集約型または共用体型 (再帰的に、部分集約型または含まれる共用体のメンバーを含む)、
- オブジェクトの動的型の (おそらく cv 修飾された) 基本クラス型である型、
- char または unsigned char 型。
具体的には、3.10/15 では、unsigned int 型の左辺値を介して float オブジェクトにアクセスすることはできません。私は実際にこれに噛まれました。私が書いたプログラムは、最適化をオンにした後、動作しなくなりました。どうやら、GCC は float 型の左辺値が int 型の左辺値をエイリアスすることを期待していませんでした。命令は、3.10/15 を悪用する as-if ルールの下でオプティマイザによってシャッフルされ、動作を停止しました。
以下の仮定の下で
- float は実際には 32 ビットの IEEE-float に対応します。
- sizeof(float)==sizeof(int)
- unsigned int にはパディング ビットまたはトラップ表現がありません
次のようにできるはずです。
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
これは「3.10 違反」の影響を受けず、通常は非常に高速です。少なくとも GCC は memcpy を組み込み関数として扱います。NaN、無限大、または非常に大きな数値を扱う関数が必要ない場合は、"r >> 2" を "(r+1) >> 2" に置き換えることで精度を向上させることもできます。
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
IEEE-754 コーディングでは、連続する浮動小数点値が連続する整数にマップされるため (+/- ゼロは無視されます)、仮数オーバーフローが原因で指数が変更されても、これは機能します。このマッピングは、実際には対数を非常によく近似しています。