在 .NET 中,
float
使用
IEEE binary32 单精度浮点数来表示,并使用 32 位存储。代码将这个数字通过将其位组装成一个
int
,然后使用
unsafe
强制转换为
float
来构建。在 C++ 中,此类强制类型转换被称为
reinterpret_cast
,转换时不执行任何转换 - 只是将位重新解释为新类型。
![IEEE single precision floating number](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Float_example.svg/590px-Float_example.svg.png)
组装的数字是十六进制值 4019999A
,或二进制值 01000000 00011001 10011001 10011010
:
- 符号位为 0 (正数)。
- 指数位为
10000000
(即 128),结果指数为 128 - 127 = 1 (分数乘以 2 ^ 1 = 2)。
- 小数位为
00110011001100110011010
,如果没有其他内容,几乎可以识别出零和一的模式。
返回的浮点数的位与转换为浮点数的 2.4 完全相同,整个函数可以简单地替换为文字字面常量 2.4f
。
最后的零有点“破坏”了小数部分的位模式,这是为了使浮点数匹配可以使用浮点数文字字面量写出的内容吗?
那么普通转换和这种奇怪的“不安全转换”之间有什么区别呢?
假设以下代码:
int result = 0x4019999A // 1075419546
float normalCast = (float) result;
float unsafeCast = *(float*) &result; // Only possible in an unsafe context
第一个转换将整数
1075419546
转换为其浮点表示,例如
1075419546f
。这涉及计算用于表示原始整数的浮点数所需的符号、指数和小数位。这是一个需要完成的复杂计算。
第二个转换更加阴险(只能在不安全的上下文中执行)。
&result
获取
result
的地址,返回指向存储整数
1075419546
的位置的指针。然后可以使用指针解引用运算符
*
来检索指针指向的值。使用
*&result
将从该位置检索存储的整数,但首先将指针强制转换为
float*
(指向
float
的指针),从内存位置中检索到一个浮点数,结果是将浮点数
2.4f
分配给
unsafeCast
。因此,
*(float*) &result
的叙述是“给我一个指向
result
的指针,并假设该指针是指向
float
的指针,并检索指针指向的值”。
与第一个转换相反,第二个转换不需要任何计算。它只是将存储在
result
中的32位数据塞入
unsafeCast
中(它也是32位的)。
通常,执行这样的强制转换可能会失败,但通过使用
unsafe
,您告诉编译器您知道自己在做什么。
return (float)result;
和reinterpret_cast<float*>(&result)
的区别 - 前者是一个转换(conversion),它把整数123
转换成浮点数123.0F
,但这并不是一种重新解释的强制类型转换(re-interpretive cast),因为123
和123.0F
的字节完全不同。而后者则是一种重新解释的强制类型转换,它表示"这里有4个字节,现在将它们视为一个float
"。 - Marc Gravell