为什么我从 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 双精度浮点数.
表示
我正在用纯 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 双精度浮点数.
表示