dtoa vs sprintf vs Grisu3 algorithm

dtoa vs sprintf vs Grisu3 algorithm

本文关键字:vs algorithm Grisu3 sprintf dtoa      更新时间:2023-10-16

C++中将双精度数字呈现为字符串的最佳方法是什么?

我看到了这篇文章 这里是龙:你甚至不知道你的问题的进展 讨论打印浮点数。

我一直在使用sprintf.我不明白为什么我需要修改代码?

如果你

对sprintf_s感到满意,你就不应该改变。但是,如果您需要以库不支持的方式格式化输出,则可能需要重新实现专用版本的 sprintf(使用任何已知算法(。

例如,JavaScript 对其数字必须如何打印有非常精确的要求(参见规范的第 9.8.1 节(。正确的输出不能通过简单地调用 sprintf 来完成。事实上,Grisu 的开发是为了为 JavaScript 编译器实现正确的数字打印。

Grisu 也比 sprintf 快,但除非浮点打印是应用程序中的瓶颈,否则这不应该成为切换到其他库的理由。

啊!

你给出的文章中概述的问题是,对于某些数字,计算机显示的东西在理论上是正确的,但不是我们人类会使用的东西。

例如,就像文章所说的,1.2999999... = 1.3,所以如果你的结果是1.3,那么计算机将其显示为1.29999999999是(相当(正确的......但这不是你会看到的...

现在的问题是计算机为什么要这样做?原因是计算机以 2 为基数(二进制(计算,而我们通常以 10 为基数(十进制(进行计算。结果是一样的(感谢上帝!(,但内部存储和表示不是。

有些数字以 10 为基数时看起来不错,例如 1.3,但其他数字则不然,例如 1/3 = 0.333333333....在基数 2 中也是如此,有些数字在基数 2 中"看起来"不错(通常由 2 的分数组成时(,而另一些则不然。当计算机在内部存储数字时,它可能无法"精确"存储它并存储最接近的可能表示形式,即使数字在十进制中看起来是"有限的"。所以是的,在这种情况下,它会"漂移"一点。如果你一次又一次地这样做,你可能会失去精度。但是没有其他方法(除非使用能够存储分数的特殊数学库(

当计算机试图以 10 为基数返回您给它的数字时,就会出现问题。然后计算机可能会给你 1.299999 而不是你预期的 1.3。

这也是为什么你不应该将浮点数与 ==、<、> 进行比较,而是使用特殊函数 islessgreater(a, b( isgreater(a, b( 等的原因。

所以你使用的实际函数(sprintf(很好,而且尽可能精确,它给你正确的值,你只需要知道在处理浮点数时,如果你期望1.3,那么最大精度的1.2999999是可以

现在,如果你想"漂亮地打印"这些数字以获得最佳的"人类"表示(以10为基数(,你可能想要使用一个特殊的库,比如你的grisu3,它将尝试消除可能发生的漂移,并将数字对齐到最接近的10基数表示。

现在库无法使用水晶球来查找哪些数字漂移或不漂移,因此您可能会真正表示存储在计算机中的最高精度的 1.2999999,并且 lib 会将其"转换"为 1.3...但它并不比显示 1.29999 而不是 1.3 更差,也不更不精确。

如果您需要良好的可读性,这样的库将很有用。如果没有,那只是浪费时间。

希望这个帮助!

在任何合理的语言中执行此操作的最佳方法是:

  1. 使用语言的运行时库。 永远不要自己滚。 即使你有知识和好奇心来写它,你也不想测试它,你也不想维护它。
  2. 如果您发现运行时库转换有任何不当行为,请提交 bug
  3. 如果这些转换是程序的可衡量瓶颈,请不要尝试使它们更快。 相反,找到一种方法来避免这样做。 与其将数字存储为字符串,不如只存储浮点数据(在可能控制字节序之后(。 如果需要字符串表示形式,请改用十六进制浮点格式。

我不是要劝阻你或任何人。 这些实际上是令人着迷的功能,但它们也非常复杂,并且试图为任何非天真的实现设计良好的测试覆盖率甚至更加复杂。 除非你准备花几个月的时间思考这个问题,否则不要开始。

您可能希望使用 Grisu 之类的东西(或更快的方法(,因为它为您提供了具有往返保证的最短十进制表示形式,这与仅采用固定精度sprintf不同。好消息是,C++20 包括默认情况下为您提供此功能的std::format。例如:

printf("%.*g", std::numeric_limits<double>::max_digits10, 0.3);

打印0.29999999999999999同时

puts(fmt::format("{}", 0.3).c_str());

打印0.3(Godbolt(。

同时,您可以使用 {fmt} 库,std::format基于。{FMT} 还提供了 print 函数,使这更容易、更高效(Godbolt(:

fmt::print("{}", 0.3);

免责声明:我是 {fmt} 和 C++20 std::format 的作者。

C++你为什么不使用iostreams?您可能应该将 cout 用于控制台,ostringstream用于面向字符串的输出(除非您非常具体地需要使用 printf 系列方法(。

您不必担心格式化性能,除非实际分析显示 CPU 是瓶颈(与 I/O 相比(。

void outputdouble( ostringstream & oss, double d )
{
    oss.precision( 5 );
    oss << d;
}

http://www.cplusplus.com/reference/iostream/ostringstream/