c# - C 和 C# 之间浮点精度行为的差异
问题描述
这是一个学术问题,因此诸如“不要那样做”之类的答案没有抓住重点。
我不是在尝试解决问题 - 我是在尝试理解观察到的行为,即比较 C 和 C# 时浮点数学的功能差异
假设:C 中的浮点精度
我的假设是在 Cfloats
中使用 23 位尾数和 8 位指数(https://en.wikipedia.org/wiki/Single-precision_floating-point_format)实现
对于给定的数字,我们可以通过计算尾数的最后一位的值来计算最小精度 - 您可以添加到纯粹结构上不能再存储它的数字的最小值。
如果浮点数被评估为:
[sign] * 1.[mantissa] * 2^[exponent]
然后因为我们在尾数中有 23 位精度的值是2^(exponent-23)
,其中给定数字的指数是:
floor(log2(number))
所以一个相当大的数字的精度10^9
如下计算:
exponent = floor(log2(10^9))
= 29
precision = 2^(exponent-23)
= 2^(29-23)
= 2^6
= 64
这是在存储为浮点数时可以添加的裸机、理论上可能的最低值10^9
,因为我们实际上是在翻转尾数的最低有效位:
正如 IEEE-754 浮点转换器所显示的那样
我还可以使用快速 C 程序(在线运行)来验证这一点:
#include <cstdio>
int main()
{
float number = 1e9f; // exponent: 29, precision: 64
printf("%'.0f\n", number); // prints: 1000000000
number += 30; // 30 rounded to nearest multiple of 64 is 0
printf("%'.0f\n", number); // prints: 1000000000
number += 40; // 40 rounded to nearest multiple of 64 is 64
printf("%0'.0f\n", number); // prints: 1000000064
return 0;
}
我的假设是,通用的 32 位浮点格式(1 位符号、8 位指数、23 位尾数)是如此普遍,以至于它是现代 CPU 固有的东西,因此通常跨编程语言的行为是相同的。
问题:C#中的浮点精度
因此,当我在 C# 中尝试相同的验证测试时,数字的值不会改变。
如果我使用较小的 value 10^8
,它的指数是26
,因此精度是 ,2^(26-23) = 8
我上面假设浮点格式的位如何在内部表示数字,我注意到以下行为:
float number = 1e8f; // exponent: 26, precision: 8
Console.WriteLine($"{number,1:0}"); // prints: 100000000
number += 30; // 30 rounded to multiple of 8 -should- be 32
Console.WriteLine($"{number,1:0}"); // prints: 100000000
number += 40; // 40 rounded to multiple of 8 -should- be 40
Console.WriteLine($"{number,1:0}"); // prints: 100000100
这……让我有些困惑。那100从哪里来?这甚至不是2的倍数!
值为 1e8f C 的行为也符合预期并支持精度为“8”:cpp.sh/6qesv
查看浮点值的C# 文档没有什么让我觉得 C# 应该以与 C 不同的方式处理浮点加法,以及考虑到浮点值的实现方式,我所期望的。
文档确实提到浮点数的近似精度约为 6-9 位,这令人沮丧地模糊。我想这可能是一个答案:“您正在处理超出保证限制的数字,这是未定义的行为”,虽然这是真的,但令人不满意。
我想知道,理想情况下逐步分解,在那里的 C# 实现中实际发生了什么,使它的行为与这里的 C 如此不同。
解决方案
将我的评论推广到答案:
这里的问题不是浮点数,而是字符串格式的差异。我不熟悉指定为“0”的格式究竟是什么意思或作用(似乎在任何地方都找不到它的记录),但它是造成您看到的不寻常的舍入的原因。
建议使用“G9”的格式说明符来格式化单精度浮点数,以使其正确往返(这意味着将字符串解析回单精度浮点数将准确再现原始值)。如果您更改代码以{number:G9}
在插值字符串中使用,您应该会看到预期的结果。
推荐阅读
- google-api - 谷歌工作空间服务帐户的可移植性
- flutter - Flutter 2.5 更新 - 每个静态 Widget 上的 const 关键字
- c - C语言的UART通信(十六进制)
- substrate - Substrate Offchain-worker Http Fetch 错误
- nativescript - 预览服务已被禁用,直至另行通知。在 Svelte Native 上运行 ns preview 时,配置本地构建并使用“ns run”
- node.js - 移动语音通道不和谐js
- elasticsearch - Elasticsearch 在 MacOS 中启动失败?
- deployment - 用于瞪羚安全套件 6.2.7 版本的 EAR 部署失败,出现错误“无法索引类 module-info.class”
- python - 如何在 Python 中将字符串字典值转换为 dict 类型
- php - PHP 字符编码问题 Windows-1252 到 UTF-8