【问题标题】:Converting double to float without relying on the FPU rounding mode在不依赖 FPU 舍入模式的情况下将 double 转换为 float
【发布时间】:2010-01-06 09:29:48
【问题描述】:

有没有人可以方便地使用代码的 sn-ps 将 IEEE 754 double 转换为直接劣等(或上等)float无需更改或假设有关 FPU 当前舍入模式的任何内容强>?

注意:此约束可能意味着根本不使用 FPU。我希望在这些条件下最简单的方法是读取 64 位长的双精度位并使用它。

为简单起见,您可以假设您选择的字节序,并且可以通过以下联合的 d 字段获得所讨论的双精度:

union double_bits
{
  long i;
  double d;
};

我会尝试自己做,但我确信我会为非规范化或负数引入难以注意到的错误。

【问题讨论】:

  • 在 glibc 系统上,您会找到一个头文件 ieee754.h,它定义了浮点类型和位域结构的联合,因此您可以更轻松地处理尾数和指数,抱歉,但我不能给出你真正的代码。

标签: c floating-point bit-manipulation ieee-754


【解决方案1】:

我认为以下方法可行,但我将首先陈述我的假设:

  • 浮点数在您的实现中以 IEEE-754 格式存储,
  • 没有溢出,
  • 您有 nextafterf() 可用(在 C99 中指定)。

此外,这种方法很可能效率不高。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(int argc, char *argv[])
{
    /* Change to non-zero for superior, otherwise inferior */
    int superior = 0;

    /* double value to convert */
    double d = 0.1;

    float f;
    double tmp = d;

    if (argc > 1)
        d = strtod(argv[1], NULL);

    /* First, get an approximation of the double value */
    f = d;

    /* Now, convert that back to double */
    tmp = f;

    /* Print the numbers. %a is C99 */
    printf("Double: %.20f (%a)\n", d, d);
    printf("Float: %.20f (%a)\n", f, f);
    printf("tmp: %.20f (%a)\n", tmp, tmp);

    if (superior) {
        /* If we wanted superior, and got a smaller value,
           get the next value */
        if (tmp < d)
            f = nextafterf(f, INFINITY);
    } else {
        if (tmp > d)
            f = nextafterf(f, -INFINITY);
    }
    printf("converted: %.20f (%a)\n", f, f);

    return 0;
}

在我的机器上,它打印:

Double: 0.10000000000000000555 (0x1.999999999999ap-4)
Float: 0.10000000149011611938 (0x1.99999ap-4)
tmp: 0.10000000149011611938 (0x1.99999ap-4)
converted: 0.09999999403953552246 (0x1.999998p-4)

我的想法是我将double 值转换为float 值——这可能小于或大于双精度值,具体取决于舍入模式。当转换回double时,我们可以检查它是小于还是大于原始值。然后,如果float的值不在正确的方向,我们从转换后的数字中看下一个float数字在原始数字的方向。

【讨论】:

  • 非常感谢您提供此代码。我逐渐确信这是最不容易出错的解决方案。感谢您也指出nextafterf,这比在/减少float 的位时好得多,就好像它是int。为了减轻f+1等于f的风险,我可以改写nextafterf(f, INFINITY)吗?
  • 我刚刚阅读了手册页,C 标准草案,并尝试了一下,看起来 INFINITY 应该可以工作。
【解决方案2】:

要比重新组合尾数和指数位更准确地完成这项工作,请检查一下:

http://www.mathworks.com/matlabcentral/fileexchange/23173

问候

【讨论】:

  • 谢谢。 doubles2halfp 函数和我担心的一样复杂,但至少它已经有一半的常量是正确的,所以这是一个很好的起点。
  • 我会使用给定的代码作为参考并重写一个更简单的方法,使用 & >> 后跟 or,然后检查非常小的和非常大的数字。来自babbage.cs.qc.edu/IEEE-754/Decimal.html的移位计数和位位置一目了然
【解决方案3】:

我在此处发布了执行此操作的代码:https://stackoverflow.com/q/19644895/364818 并在下面复制了它以方便您使用。

    // d is IEEE double, but double is not natively supported.
    static float ConvertDoubleToFloat(void* d)
    {
        unsigned long long x;
        float f; // assumed to be IEEE float
        unsigned long long sign ;
        unsigned long long exponent;
        unsigned long long mantissa;

        memcpy(&x,d,8);

        // IEEE binary64 format (unsupported)
        sign     = (x >> 63) & 1; // 1
        exponent = ((x >> 52) & 0x7FF); // 11
        mantissa = (x >> 0) & 0x000FFFFFFFFFFFFFULL; // 52
        exponent -= 1023;

        // IEEE binary32 format (supported)
        exponent += 127; // rebase
        exponent &= 0xFF;
        mantissa >>= (52-23); // left justify

        x = mantissa | (exponent << 23) | (sign << 31);
        memcpy(&f,&x,4);

        return f;
    }

【讨论】:

  • 谢谢。 exponent &amp;= 0xFF; 行意味着当返回 ±FLT_MAX±inf 是合适的时,将返回具有奇怪指数的 float(并且非规范结果也关闭)。
猜你喜欢
  • 1970-01-01
  • 2013-03-29
  • 2012-08-03
  • 2012-05-04
  • 2015-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多