【发布时间】:2021-04-24 21:14:00
【问题描述】:
在我们的 C++ 代码库中,我们有一个默认的格式化方法来将 double 浮点数转换为字符串,这主要用于 JSON 序列化和调试日志。对于该默认数字格式,我有以下矛盾的要求:
- 有利于人类可读性。首选
1000到1e3或0.125到1.25e-1。 - 保持精度。首选
3.1415926535897931而非3.14。 - 避免十进制数的虚假数字。首选
0.1而不是0.10000000000000001。
到目前为止,我发现的最佳权衡是使用等效于 printf("%.15g", value) 的格式。它满足要求1和3,但不完全满足2。有大约 4 位的精度损失。
其他人使用基于"%.17g" 的默认格式,它满足1 和2 要求,但不满足3 要求。例如,数字 0.2 的格式为 0.20000000000000001。
在两者之间,"%.16g" 格式接近满足要求 2 和 3,但并非总是同时满足这两个要求。
作为说明,我希望 0.3 被格式化为0.3,但是 0.1+0.2 由于舍入误差而稍大,被格式化为0.30000000000000004 看看有什么区别。
我编写了以下函数,以我希望的方式格式化浮点数,作为概念证明。然而,从性能的角度来看,这是不可接受的,因为它可以在 double 和字符串之间进行多达 34 次转换,与当前使用 "%.15g" 的实现相比,精度增益有限。
std::string doubleToString(double number)
{
char buffer[32];
long long intVal = static_cast<long long>(number);
if(intVal == number)
{
sprintf(buffer, "%lld", intVal);
}
else
{
for(int i=1; i<=17; i++)
{
sprintf(buffer, "%.*g", i, number);
double readBack = atof(buffer);
if(readBack == number)
break;
}
}
return buffer;
}
我刚刚意识到 Python 已经按照我想要的方式格式化数字:
$ python3
Python 3.8.6 (default, Oct 8 2020, 14:06:32)
[Clang 12.0.0 (clang-1200.0.32.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.3
0.3
>>> 0.1+0.2
0.30000000000000004
>>>
有没有办法在不牺牲太多性能的情况下在 C++ 中实现相同的行为?
【问题讨论】:
-
一些线索 0.10000000000000001 == 0.1 是
true。所以你可以放下长字符串。 0.20000000000000001 == 0.2 是true。再次放下长绳。但是0.30000000000000004 == 0.3是false。所以你不能丢弃长字符串。 -
请定义(或让您的需求作者定义)“虚假数字”。这应该是一个规则,而不是一个例子。 “0 的 16 位小数,后跟最不重要的 1”是一个定义,但可能不是他们想要的。
-
“保持精度。” 对浮点十进制数的实际含义并不明确。二进制分数可以产生非常多的小数位,但这些数字不一定是有效的。计算出唯一标识一个
double需要多少位数字并非易事。 -
这听起来很像 Stephan 在他的
演示文稿(来自 cppcon 2019)中谈到的内容。试试看这里:youtube.com/watch?v=4P_kbF0EbZM这可能正是你要找的。span> -
这不是一个简单的问题。例如,0.3 不能完全表示为二进制浮点值,因此任何将该值显示为“0.3”的代码都在隐藏信息。有时这就是你想要的,有时不是。
标签: c++ number-formatting