首先,如果你想更好地理解浮点的弱点,你应该考虑阅读一篇论文:“What Every Computer Scientist Should Know About Floating Point Arithmetic”,http://www.validlab.com/goldberg/paper.pdf
现在来点肉。
以下代码是简单的代码,并尝试从unsigned int
0 < value < 2 24范围内的 IEEE-754 单精度浮点数。这是您在现代硬件上最有可能遇到的格式,也是您在原始问题中似乎引用的格式。
IEEE-754 单精度浮点数分为三个字段:单个符号位、8 位指数和 23 位有效位(有时称为尾数)。IEEE-754 使用隐藏的 1有效位,这意味着有效位实际上是总共 24 位。这些位从左到右打包,符号位在 31 位,指数在 30 .. 23 位,有效位在 22 .. 0 位。维基百科的下图说明:
指数的偏差为 127,这意味着与浮点数关联的实际指数比指数字段中存储的值小 127。因此,0 的指数将被编码为 127。
(注意:您可能会对完整的维基百科文章感兴趣。参考:http ://en.wikipedia.org/wiki/Single_precision_floating-point_format )
因此,IEEE-754 编号 0x40000000 解释如下:
- 位 31 = 0:正值
- 位 30 .. 23 = 0x80:指数 = 128 - 127 = 1(又名 2 1)
- 位 22 .. 0 全部为 0:有效数 = 1.00000000_00000000_0000000。(注意我恢复了隐藏的1)。
所以值为 1.0 x 2 1 = 2.0。
要将unsigned int
上面给出的有限范围内的一个转换为 IEEE-754 格式的内容,您可以使用如下所示的函数。它采取以下步骤:
- 将整数的前导 1 与浮点表示中隐藏的 1 的位置对齐。
- 在对齐整数时,记录所做的移位总数。
- 掩盖隐藏的1。
- 使用所做的移位次数,计算指数并将其附加到数字上。
- 使用
reinterpret_cast
, 将生成的位模式转换为float
. 这部分是一个丑陋的 hack,因为它使用了类型双关指针。您也可以通过滥用union
. 一些平台提供了一种内在操作(例如_itof
),以使这种重新解释不那么难看。
有更快的方法来做到这一点;如果不是超级有效的话,这个应该在教学上有用:
float uint_to_float(unsigned int significand)
{
// Only support 0 < significand < 1 << 24.
if (significand == 0 || significand >= 1 << 24)
return -1.0; // or abort(); or whatever you'd like here.
int shifts = 0;
// Align the leading 1 of the significand to the hidden-1
// position. Count the number of shifts required.
while ((significand & (1 << 23)) == 0)
{
significand <<= 1;
shifts++;
}
// The number 1.0 has an exponent of 0, and would need to be
// shifted left 23 times. The number 2.0, however, has an
// exponent of 1 and needs to be shifted left only 22 times.
// Therefore, the exponent should be (23 - shifts). IEEE-754
// format requires a bias of 127, though, so the exponent field
// is given by the following expression:
unsigned int exponent = 127 + 23 - shifts;
// Now merge significand and exponent. Be sure to strip away
// the hidden 1 in the significand.
unsigned int merged = (exponent << 23) | (significand & 0x7FFFFF);
// Reinterpret as a float and return. This is an evil hack.
return *reinterpret_cast< float* >( &merged );
}
您可以使用检测数字中前导 1 的函数来提高此过程的效率。(这些有时会clz
以“计数前导零”或norm
“规范化”之类的名称命名。)
您还可以通过记录符号,获取整数的绝对值,执行上述步骤,然后将符号放入数字的第 31 位,将其扩展到有符号数。
对于整数 >= 2 24,整个整数不适合 32 位浮点格式的有效位字段。这就是您需要“舍入”的原因:您丢失 LSB 以使值适合。因此,多个整数最终将映射到相同的浮点模式。确切的映射取决于舍入模式(向 -Inf 舍入,向 +Inf 舍入,向零舍入,向最接近的偶数舍入)。但事实是,您不能将 24 位推入少于 24 位而不会有任何损失。
您可以根据上面的代码看到这一点。它通过将前导 1 与隐藏 1 位置对齐来工作。如果一个值 >= 2 24,则代码需要右移,而不是左移,这必然会移开 LSB。舍入模式只是告诉您如何处理移位的位。