我有兴趣学习如何仅使用按位运算符将整数值转换为IEEE单精度浮点格式。但是,我很困惑在计算指数时可以做些什么来知道剩下多少逻辑移位。
给定一个int,比如15,我们有:
二进制:1111
- > 1.111 x 2 ^ 3 =>在第一位之后放置小数点后,我们发现'e'值将为3。
E = Exp - 偏见 因此,Exp = 130 = 10000010
有效数字为:111000000000000000000000
然而,我知道'e'值将是3,因为我能够看到在第一位之后放置小数后有三位。作为一般情况,是否有更通用的代码编码方式?
同样,这是一个int到float的转换,假设整数是非负的,非零,并且不大于尾数允许的最大空间。
另外,有人可以解释为什么大于23位的值需要舍入? 提前谢谢!
答案 0 :(得分:5)
首先,如果你想更好地理解浮点弱点,你应该考虑阅读一篇论文:“每个计算机科学家应该知道的关于浮点运算的内容”,http://www.validlab.com/goldberg/paper.pdf
现在对某些人来说。
以下代码是简单的,并试图从unsigned int
生成一个范围为0< {0}的IEEE-754单精度浮点数。价值< 2 24 。这是您在现代硬件上最有可能遇到的格式,它是您在原始问题中引用的格式。
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解释如下:
因此该值为1.0 x 2 1 = 2.0。
要将上面给出的有限范围内的unsigned int
转换为IEEE-754格式的内容,您可以使用如下所示的函数。它需要以下步骤:
reinterpret_cast
,将生成的位模式转换为float
。这部分是一个丑陋的黑客,因为它使用类型惩罚指针。你也可以通过滥用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位,将其扩展为带符号的数字。
对于整数&gt; = 2 24 ,整个整数不适合32位浮点格式的有效数字段。这就是你需要“舍入”的原因:你失去了LSB以使价值合适。因此,多个整数将最终映射到相同的浮点模式。确切的映射取决于舍入模式(向-Inf舍入,向+ Inf舍入,向零舍入,向最近偶数舍入)。但事实是你不能将24位变成少于24位而没有一些损失。
您可以根据上面的代码看到这一点。它的工作原理是将前导1对齐到隐藏的1位置。如果值>> 2 24 ,则代码需要移位右,而不是 left ,这必然会使LSB移位。舍入模式只是告诉你如何处理移走的位。