【问题标题】:Parse double precision IEEE floating-point on a C compiler with no double precision type在没有双精度类型的 C 编译器上解析双精度 IEEE 浮点
【发布时间】:2011-04-10 19:41:44
【问题描述】:

我正在使用 8 位 AVR 芯片。 64 位双精度没有数据类型(双精度仅映射到 32 位浮点数)。但是,我将通过串行接收 64 位双精度,并且需要通过串行输出 64 位双精度。

如何在不强制转换的情况下将 64 位双精度数转换为 32 位浮点数并再次返回? 32 位和 64 位的格式都将遵循 IEEE 754。当然,我假设转换为 32 位浮点数时会损失精度。

对于从 64 位到 32 位浮点数的转换,我正在尝试这个:

// Script originally from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1281990303
float convert(uint8_t *in) {
  union {
    float real;
    uint8_t base[4];
  } u;
  uint16_t expd = ((in[7] & 127) << 4) + ((in[6] & 240) >> 4);
  uint16_t expf = expd ? (expd - 1024) + 128 : 0;
  u.base[3] = (in[7] & 128) + (expf >> 1);
  u.base[2] = ((expf & 1) << 7) + ((in[6] & 15) << 3) + ((in[5] & 0xe0) >> 5);
  u.base[1] = ((in[5] & 0x1f) << 3) + ((in[4] & 0xe0) >> 5);
  u.base[0] = ((in[4] & 0x1f) << 3) + ((in[3] & 0xe0) >> 5);
  return u.real;
}

对于像 1.0 和 2.0 这样的数字,上述方法有效,但是当我将 1.1 作为 64 位双精度值传递进行测试时,输出稍微偏离了一点(字面意思,不是双关语!),尽管这可能是我的测试有问题。见:

// Comparison of bits for a float in Java and the bits for a float in C after
// converted from a 64-bit double. Last bit is different.
// Java code can be found at https://gist.github.com/912636
JAVA FLOAT:        00111111 10001100 11001100 11001101
C CONVERTED FLOAT: 00111111 10001100 11001100 11001100

【问题讨论】:

  • 请注意,1.1 没有精确的表示,无论是 double 还是 float。可以想象,将双精度数缩短为浮点数可以通过仅切割不太重要的位或四舍五入来完成。我无法从你的代码中弄清楚它在做什么。
  • 这个问题太棒了!
  • @Gabe - 稍微偏离 LSB 可能是可接受的精度损失。但是,它确实让我质疑我提供的代码并寻找替代方案。
  • 这让我又回来了——我的第一份工作(早在 87 年)是编写代码以将 PDP-11 浮点格式转换为 IEEE-754 格式,这样我就可以在 PC 上读取数据文件。
  • @TonyK:这种舍入算法不是“不稳定的”,它是向 0 舍入。与 Java 不同的默认舍入模式不是我对错误的定义。

标签: c++ c casting avr


【解决方案1】:

IEEE 指定five different rounding modes,但默认使用的是Round half to even。所以你有一个形式为 10001100 11001100 11001100 11001100... 的尾数,你必须将它四舍五入到 24 位。从 0(最高有效)开始对位进行编号,第 24 位为 1;但这还不足以告诉您是否将第 23 位向上舍入。如果所有剩余位均为 0,则不会四舍五入,因为位 23 为 0(偶数)。但是剩下的位不是零,所以你在所有情况下都四舍五入。

一些例子:

10001100 11001100 11001100 10000000...(全为零)不会四舍五入,因为第 23 位已经是偶数。

10001100 11001100 11001101 10000000...(全为零)确实向上取整,因为第 23 位是奇数。

10001100 11001100 1100110x 10000000...0001 总是向上取整,因为其余位并非全为零。

10001100 11001100 1100110x 0xxxxxxx... 从不向上取整,因为第 24 位为零。

【讨论】:

  • 感谢 TonyK 的澄清:在 1.1 中,((in[3] & 0xe0) >> 5) 中包含的最后一位是 100,之后的位是 11001001010001111010111000011。正确的形式是浮点数的最后一位是 101。这如何与“四舍五入”一起工作?
  • 我在上面添加了舍入逻辑。它看起来已经解决了我的基本测试(1.1、1.11、1.2 等)的问题,尽管测试还不够广泛。代码可见github.com/baalexander/avr_bridge/blob/master/scripts/avr_ros/…。我现在正在研究 32 位到 64 位。感谢您为我指明正确的方向 TonyK!
【解决方案2】:

以下代码似乎从单精度转换为双精度。我将把它作为练习留给读者来实现缩小版本。不过,这应该可以帮助您入门。最难的部分是在有效数字中正确地获取位位置。我包括了一些包含正在发生的事情的 cmets。

double
extend_float(float f)
{
    unsigned char flt_bits[sizeof(float)];
    unsigned char dbl_bits[sizeof(double)] = {0};
    unsigned char sign_bit;
    unsigned char exponent;
    unsigned int  significand;
    double out;

    memcpy(&flt_bits[0], &f, sizeof(flt_bits));
    /// printf("---------------------------------------\n");
    /// printf("float = %f\n", f);
#if LITTLE_ENDIAN
    reverse_bytes(flt_bits, sizeof(flt_bits));
#endif
    /// dump_bits(&flt_bits[0], sizeof(flt_bits));

    /* IEEE 754 single precision
     *    1 sign bit              flt_bits[0] & 0x80
     *    8 exponent bits         flt_bits[0] & 0x7F | flt_bits[1] & 0x80
     *   23 fractional bits       flt_bits[1] & 0x7F | flt_bits[2] & 0xFF |
     *                            flt_bits[3] & 0xFF
     *
     * E = 0   & F  = 0 -> +/- zero
     * E = 0   & F != 0 -> sub-normal
     * E = 127 & F  = 0 -> +/- INF
     * E = 127 & F != 0 -> NaN
     */
    sign_bit = (flt_bits[0] & 0x80) >> 7;
    exponent = ((flt_bits[0] & 0x7F) << 1) | ((flt_bits[1] & 0x80) >> 7);
    significand = (((flt_bits[1] & 0x7F) << 16) |
                   (flt_bits[2] << 8) |
                   (flt_bits[3]));

    /* IEEE 754 double precision
     *    1 sign bit              dbl_bits[0] & 0x80
     *   11 exponent bits         dbl_bits[0] & 0x7F | dbl_bits[1] & 0xF0
     *   52 fractional bits       dbl_bits[1] & 0x0F | dbl_bits[2] & 0xFF
     *                            dbl_bits[3] & 0xFF | dbl_bits[4] & 0xFF
     *                            dbl_bits[5] & 0xFF | dbl_bits[6] & 0xFF
     *                            dbl_bits[7] & 0xFF
     *
     * E = 0    & F  = 0 -> +/- zero
     * E = 0    & F != 0 -> sub-normal
     * E = x7FF & F  = 0 -> +/- INF
     * E = x7FF & F != 0 -> NaN
     */
    dbl_bits[0] = flt_bits[0] & 0x80; /* pass the sign bit along */

    if (exponent == 0) {
        if (significand  == 0) { /* +/- zero */
            /* nothing left to do for the outgoing double */
        } else { /* sub-normal number */
            /* not sure ... pass on the significand?? */
        }
    } else if (exponent == 0xFF) { /* +/-INF and NaN */
        dbl_bits[0] |= 0x7F;
        dbl_bits[1]  = 0xF0;
        /* pass on the significand */
    } else { /* normal number */
        signed int int_exp = exponent;
        int_exp -= 127;  /* IEEE754 single precision exponent bias */
        int_exp += 1023; /* IEEE754 double precision exponent bias */
        dbl_bits[0] |= (int_exp & 0x7F0) >> 4;  /* 7 bits */
        dbl_bits[1]  = (int_exp & 0x00F) << 4;  /* 4 bits */
    }

    if (significand != 0) {
        /* pass on the significand most-significant-bit first */
        dbl_bits[1] |=  (flt_bits[1] & 0x78) >> 3;    /* 4 bits */
        dbl_bits[2] = (((flt_bits[1] & 0x07) << 5) |  /* 3 bits */
                       ((flt_bits[2] & 0xF8) >> 3));  /* 5 bits */
        dbl_bits[3] = (((flt_bits[2] & 0x07) << 5) |  /* 3 bits */
                       ((flt_bits[3] & 0xF8) >> 3));  /* 5 bits */
        dbl_bits[4] =  ((flt_bits[3] & 0x07) << 5);   /* 3 bits */
    }

    ///dump_bits(&dbl_bits[0], sizeof(dbl_bits));
#if LITTLE_ENDIAN
    reverse_bytes(&dbl_bits[0], sizeof(dbl_bits));
#endif
    memcpy(&out, &dbl_bits[0], sizeof(out));

    return out;
}

我留下了一些 printf 行,但在 C++ 样式 cmets 中注释掉了。您必须为reverse_bytesLITTLE_ENDIANdump_bits 提供适当的定义。毕竟我不想破坏你所有的乐趣。 single precisiondouble precision 数字上的维基百科条目非常好。

如果您要经常修改浮点数,您应该阅读 David Goldberg 的 "What Every Computer Scientist Should Know About Floating Point Arithmetic""How to Print Floating Point Numbers Accurately"斯蒂尔和怀特。在了解浮点数的工作原理时,它们是两篇信息量最大的文章。

【讨论】:

  • 加宽很容易。缩小是很难纠正的。
  • 一旦你将这些位分开,它并没有那么糟糕......好吧......只要你有一个fegetround的实现或某种方式来获得主动舍入模式。跨度>
  • 没错。此外,当 lsb 舍入导致溢出时,它会变得很棘手。好的,没有那么棘手,但绝对比扩大更难。我不会称其为“读者练习”。
  • 谢谢!我刚刚完成了扩展代码,这在很大程度上基于您的示例代码。来源可以在我对答案的评论中看到。
【解决方案3】:

http://www.google.com/search?q=c+convert+ieee+754+double+single

第一个结果是这样的:

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

该代码显示了如何将 IEEE-754 双精度转换为 IEEE-754 类 (1,5,10) 浮点格式。这段代码包含很多 cmets,并提到了你可能陷入的典型陷阱。

这不是你想要的,但它是一个很好的起点。

【讨论】:

    【解决方案4】:

    据我所知,GCC for AVR 中只有一个完整的 IEEE754 double 实现,您可以在 here 找到它。

    您将需要 this 存档,然后将存档中的 avr_f64.c 替换为 this 之一。

    库大约需要 21K 闪存和 310 字节内存。

    原帖可以在here找到。由于我认为您需要有一个帐户才能登录论坛,因此我已从原始帖子中提取了所有重要信息并在这里展示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-06
      • 1970-01-01
      • 1970-01-01
      • 2011-10-24
      • 2018-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多