c - 为什么从 32 位二进制大端编码的文件中读取 IEEE-754 浮点时会丢失精度?
问题描述
我正在用纯 C 重写一些 Matlab 文件处理代码,并且我实现了以下函数,该函数将从二进制大端编码文件中读取 4 个字节,该文件应该表示 ieee-754 单精度浮点值。我验证了我能够使用以下代码将相关的 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 读取二进制文件时,它也会获得与0x1acba506
我的 C 代码相同的十六进制值。
当我从float
to 转换回uint32_t
并打印十六进制值时,我最终得到0x1acba500
,这表明我在简单转换中失去了精度,float ans = (float)result;
但我真的不明白为什么?我在 x86 机器上使用 gcc 7.4,并且我已经验证了sizeof float == sizeof uint32
. 我是否假设编译器正在使用 IEEE-754 单精度浮点?
在调试的时候,我发现了一个在线的浮点计算器,它看起来精度已经无可救药地丢失了,但接下来的问题是Matlab是如何保留它的?
解决方案
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双精度浮点数表示。
推荐阅读
- php - Laravel 返回视图不起作用,而是显示错误页面
- sql - Postgresql 查询到 Redshift 的转换
- graphql - Prisma 上 GraphQL 错误中的 Apollo 聚合函数
- python-3.x - 如何在我的 df 中创建一个新列,即两个日期之间的天数(具有不同的数据类型)
- angular - Angular ng 测试在 RxJS 运算符上抛出错误
- php - 如何获得下面提到的数组的平均值
- android - 无法使用 dataBinding 从 xml 调用方法 ViewModel isValidate()
- nix - nixos vm 中的 nixos-shell 似乎无法识别存在的存储路径?
- javascript - 从 api 接收数据后在 ReactJS 中设置状态
- php - 无效的令牌格式谷歌人