为什么我从 32 位二进制大端编码的文件中读取 IEEE-754 浮点数时会丢失精度?

Why am I losing precision when reading in IEEE-754 floating point from a file encoded in 32-bit binary big endian?

我正在用纯 C 重写一些 Matlab 文件处理代码,我已经实现了以下函数,该函数将从应该表示 ieee-754 单精度浮点数的二进制大端编码文件中读取 4 个字节点值。我验证了我能够使用以下代码将相关的 32 位数据作为无符号整数从文件中提取出来。

int fread_uint32_be(uint32_t *result, FILE ** fp)
{
    uint8_t data[sizeof(uint32_t)];
    if (!result || !*fp || sizeof(uint32_t) != fread((void *) data, 1, sizeof(uint32_t), *fp))
    {
        return -1;
    }
    *result = ((uint32_t)(data[0]) << 24 | (uint32_t)(data[1]) << 16 |
               (uint32_t)(data[2]) << 8  | (uint32_t)(data[3]));
    return 0;
}

我期望的数据具有从此函数返回的十六进制值 0x1acba506,并且已通过大端格式的数据文件的十六进制转储进行验证。现在我的问题来了...

当我将此值从 uint32_t 转换为 float 时,我得到一个单精度浮点值 449553664.000000,它很接近但不完全是 Matlab 代码所具有的值,是 449553670.000000。我已经验证,当 Matlab 读取二进制文件时,它也获得了与我的 C 代码相同的十六进制值 0x1acba506

当我从 float 转换回 uint32_t 并打印十六进制值时,我最终得到 0x1acba500,这表明我在简单转换中失去了精度,即float ans = (float)result; 但我不太明白为什么?我在 x86 机器上使用 gcc 7.4,我已经验证 sizeof float == sizeof uint32。我是否错误地假设编译器使用的是 IEEE-754 单精度浮点数?

在调试中,我发现了一个online calculator for floating point,这使得精度看起来无可救药地丢失了,但问题就变成了Matlab如何保留它?

一个单精度浮点数可以放在一个32位的寄存器中,这与一个32位整数的大小完全相同。但并不是所有的浮点数都是精确的:其中一些(碰巧是 8 位)用于表示指数。所以这意味着单精度浮点数不能表示与 32 位整数相同的精度。

因此,当您将 32 位整数转换为单精度浮点数时,会出现一些精度损失。如果你想不损失精度,应该使用比较常见的双精度浮点格式,它使用64位,包括53位精度。

IEEE 754的尾数单精度浮点数为24位,其中第一位隐含1。

让我们看看你的两个整数 - Python 是调试它们的好工具。他们的位表示是

>>> format(449553664, '032b')
'00011010110010111010010100000000'

>>> format(449553670, '032b')
'00011010110010111010010100000110'

现在,如果我们查看后一个数字并查看它如何适合单精度尾数,第一个 1 位是左起第 4 位,包括我们计算 24 位,我们得到

>>> format(449553670, '032b').lstrip('0')[:24]
'110101100101110100101000'

很明显,最后一个 110 不适合尾数,因此该值被向下舍入。因此 (float)449553670 的值显示为

1.10101100101110100101000b * 10b ^ 11100b

即十进制

1.67471790313720703125 * 2 ^ 28

等于 449553664.0.


Matlab 很可能通过不使用浮点数而是使用双精度数来保持精度,就像 JavaScript 所做的那样。所有宽度小于 53 位的整数都可以用 IEEE 754 双精度浮点数.

表示