dtoa vs sprintf_s vs Grisu3算法

时间:2011-07-06 17:46:54

标签: c++ floating-point rendering double

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

我遇到了这篇文章http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/,其中有关于打印浮动点的信息?

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

6 个答案:

答案 0 :(得分:3)

啊哈!

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

例如,就像文章所说的那样,1.2999999 ... = 1.3,所以如果你的结果是1.3,那么(非常)正确的计算机将其显示为1.299999999 ...但这不是你所看到的。 ..

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

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

当计算机试图以10给你的数量给你回到基数10时出现问题。然后计算机可能会给你1.299999而不是你预期的1.3。

这也是你永远比较浮点数与==,<,>的原因,而是使用特殊函数 islessgreater(a,b)isgreater(a,b) )

所以你使用的实际函数(sprintf)很好并且尽可能准确,它给你正确的值,你只需要知道当处理浮点数时,如果你期望1.3 <1.3999999,那么最大精度是可以的/ p>

现在,如果你想“漂亮地打印”这些数字以获得最佳“人类”表示(基数为10),你可能想要使用一个特殊的库,就像你的grisu3一样,它会试图消除可能发生的漂移并将数字与最近的基数10表示对齐。

现在图书馆不能使用水晶球并找到漂移的数字,所以可能会发生你真的意味着1.2999999以最大精度存储在计算机中并且lib将“转换“它到1.3 ......但它并不比显示1.29999而不是1.3更精确,也不精确。

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

希望这有帮助!

答案 1 :(得分:3)

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

例如,JavaScript对如何打印数字有非常精确的要求(参见specification的第9.8.1节)。只需调用sprintf就无法完成正确的输出。实际上,Grisu已经开发用于为JavaScript编译器实现正确的数字打印。

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

答案 2 :(得分:1)

在C ++中你为什么不使用iostreams?您可能应该使用cout作为控制台,使用ostringstream作为面向字符串的输出(除非您非常特别需要使用printf系列方法)。

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

答案 3 :(得分:1)

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

  1. 使用您的语言的运行时库。永远不要自己动手。即使你有编写它的知识和好奇心,你也不想测试它而你也不想维护它。
  2. 如果您发现运行时库转换有任何不当行为,提交错误
  3. 如果这些转化对您的计划来说是一个可衡量的瓶颈,请不要试图让它们更快。相反,找到一种方法来避免这样做。不是将数字存储为字符串,而是存储浮点数据(在可能控制字节序之后)。如果您需要字符串表示,请改用十六进制浮点格式。
  4. 我并不是要劝阻你或任何人。这些实际上是令人着迷的功能,但它们也很复杂,并且试图为任何非天真的实现设计良好的测试覆盖率甚至更多。除非你准备好花几个月思考这个问题,否则不要开始。

答案 4 :(得分:0)

您可能要使用Grisu(或faster method)之类的东西,因为它可以为您提供具有往返保证的最短十进制表示形式,而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.3godbolt)。

在此期间,您可以使用the {fmt} librarystd::format是基于。 {fmt}还提供了print功能,使此操作变得更加轻松和高效(godbolt):

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

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

答案 5 :(得分:-1)

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

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