【问题标题】:Decimal to IEEE Single Precision Floating Point十进制转 IEEE 单精度浮点
【发布时间】:2013-12-16 22:05:15
【问题描述】:

我有兴趣学习如何仅使用位运算符将整数值转换为 IEEE 单精度浮点格式。但是,我很困惑在计算指数时可以做些什么来知道还需要多少逻辑移位。

给定一个 int,比如 15,我们有:

二进制:1111

-> 1.111 x 2^3 => 在第一位后放一个小数点后,我们发现'e'的值将是3。

E = Exp - 偏差 因此,Exp = 130 = 10000010

有效数字为:111000000000000000000000

但是,我知道 'e' 值将是 3,因为我能够看到将小数点放在第一位之后有 3 位。作为一般情况,是否有更通用的编码方式?

同样,这是一个 int 到 float 的转换,假设整数是非负数、非零并且不大于尾数允许的最大空间。

另外,有人可以解释为什么大于 23 位的值需要四舍五入吗? 提前致谢!

【问题讨论】:

  • 我想这取决于您是要完全手动执行此操作,还是要执行一些花哨和骇人听闻的事情,以及您是否希望它使用负值或大于尾数的值。
  • 哦,更重要的是,您似乎有兴趣将 integer 转换为浮点数(可能已经在 int 中),而不是用十进制写的数字,存储在,比如说,std::string。对吗?
  • 没错,我假设要转换的值是整数格式。我们假设这些值是非负的、非零的,并且能够适合尾数。感谢您提出这个问题,我会在我的问题中澄清这一点

标签: c++ assembly floating-point int single-precision


【解决方案1】:

首先,如果您想更好地理解浮点的弱点,您应该考虑阅读一篇论文:“What Every Computer Scientist Should Know About Floating Point Arithmetic”http://www.validlab.com/goldberg/paper.pdf

现在来点肉吧。

以下代码是简单的代码,并尝试从范围为 0 24 的 unsigned int 生成 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(又名 21
  • 位 22 .. 0 均为 0:有效数 = 1.00000000_00000000_0000000。 (注意我恢复了隐藏的1)。

所以值是 1.0 x 21 = 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 位,将其扩展到有符号数。

对于 >= 224 的整数,整个整数不适合 32 位浮点格式的有效位字段。这就是您需要“舍入”的原因:您丢失 LSB 以使值适合。因此,多个整数最终将映射到相同的浮点模式。确切的映射取决于舍入模式(向 -Inf 舍入,向 +Inf 舍入,向零舍入,向最接近的偶数舍入)。但事实是,你不能将 24 位推入少于 24 位而不会有损失。

您可以根据上面的代码看到这一点。它通过将前导 1 与隐藏 1 位置对齐来工作。如果值 >= 224,则代码需要右移,而不是,这必然会移开 LSB。舍入模式只是告诉您如何处理移位的位。

【讨论】:

  • 非常感谢 Joe Z 的帮助! :) 不过,我还有几个问题要问你,希望能解决问题!在上面问我的问题时,我使用了“15”的例子。为了找到指数,我在循环和计数器中使用了重复除以 2。但是,对于有效位,如果我使用逻辑左移,那只会给我 0000000000001110 存储。但是,我想存储在有效数字中的值是 1110000000000000。当您使用 0x7FFFFF '&'有效数字时,这是做什么的?至于四舍五入,有没有办法检查我们必须多少次
  • 右移?再次,非常感谢您的帮助,我真的很感激!你肯定增加了我对这个过程的理解:)
  • 上面的 while 循环不断移动有效数字 left 直到它最左边的 1 与隐藏的 1 所在的位置对齐。如果您查看浮点格式的图表,如果未隐藏,隐藏的 1 将位于第 23 位。当循环结束时,前导 1 仍然没有“隐藏”。这就是为什么循环一直移动直到第 23 位出现 1。&amp; 0x7FFFFF 去除了前导 1,使其“隐藏”。也就是说,它清除了 23 位。(它也清除了 24 .. 31 位,但它们应该已经被清除了。)有意义吗?
  • 啊,我现在明白循环的目的了。它将一直移动,直到达到隐藏的“1”,然后有效数字已经是所需的格式。然而,在这一点上,整个语法仍然值得怀疑。因此,while 循环的条件是,如果第 23 位为零,则移位计数器将增加。但是“significand
  • 在 C 和 C++ 中,有一整套 op= 运算符。当您看到x op= y 时,它大致相当于x = x op y。所以,significand &lt;&lt;= 1 大致相当于significand = significand &lt;&lt; 1。 IE。它左移significand 1 位。 (我说“大致等价”是因为在 C++ 中使用重载运算符时存在很大差异。但对于像 unsigned int 这样的基本类型,您可以将它们视为等价的。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-25
  • 2013-10-04
  • 1970-01-01
  • 2021-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多