dtoa vs sprintf vs Grisu3 算法

发布于 2024-11-19 06:45:33 字数 282 浏览 7 评论 0原文

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

我读过这篇文章这里是龙:你甚至不知道自己遇到的问题的进展,其中讨论了打印浮点数。

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

What is the best way to render double precision numbers as strings in C++?

I ran across the article Here be dragons: advances in problems you didn’t even know you had which discusses printing floating point numbers.

I have been using sprintf. I don't understand why I would need to modify the code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

时间海 2024-11-26 06:45:33

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

例如,JavaScript 对于如何打印其数字有非常精确的要求(请参阅 规范)。简单地调用 sprintf 并不能完成正确的输出。事实上,Grisu 的开发目的是为 JavaScript 编译器实现正确的数字打印。

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

If you are happy with sprintf_s you shouldn't change. However if you need to format your output in a way that is not supported by your library, you might need to reimplement a specialized version of sprintf (with any of the known algorithms).

For example JavaScript has very precise requirements on how its numbers must be printed (see section 9.8.1 of the specification). The correct output can't be accomplished by simply calling sprintf. Indeed, Grisu has been developed to implement correct number-printing for a JavaScript compiler.

Grisu is also faster than sprintf, but unless floating-point printing is a bottleneck in your application this should not be a reason to switch to a different library.

请叫√我孤独 2024-11-26 06:45:33

啊哈!

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

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

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

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

当计算机尝试以 10 为基数返回您所给出的数字时,就会出现问题。那么计算机可能会给出 1.299999,而不是您期望的 1.3。

这也是为什么您永远不要将浮点数与 ==、<、> 进行比较,而是使用特殊函数 islessgreater(a, b) isgreater(a, b)

并且尽可能精确,它给你正确的值,你只需要知道在处理浮点数时,最大精度的 1.2999999 是可以的,如果你本来期望 1.3

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

现在,库无法使用水晶球来查找哪些数字发生了漂移,因此可能会发生这样的情况:您实际上是指计算机中存储的最高精度 1.2999999,而库会将其“转换”为1.3...但它并不比显示 1.29999 而不是 1.3 更差,也不比显示 1.29999 更不精确。

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

希望这有帮助!

Ahah !

The problem outlined in the article you give is that for some numbers, the computer displays something that is theoritically correct but not what we, humans, would have used.

For example, like the article says, 1.2999999... = 1.3, so if your result is 1.3, it's (quite) correct for the computer to display it as 1.299999999... But that's not what you would have seen...

Now the question is why does the computer do that ? The reason is the computer compute in base 2 (binary) and that we usually compute in base 10 (decimal). The results are the same (thanks god !) but the internal storage and the representation are not.

Some numbers looks nice when displayed in base 10, like 1.3 for example, but others don't, for example 1/3 = 0.333333333.... It's the same in base 2, some numbers "looks" nice in base 2 (usually when composed of fractions of 2) and other not. When the computer stores number internally, it may not be able to store it "exactly" and store the closest possible representation, even if the number looked "finite" in decimal. So yes, in this case, it "drifts" a little bit. If you do that again and again, you may lose precision. But there is no other way (unless using special math libs able to store fractions)

The problem arise when the computer tries to give you back in base 10 the number you gave it. Then the computer may gives you 1.299999 instead of the 1.3 you were expected.

That's also the reason why you should never compare floats with ==, <, >, but instead use the special functions islessgreater(a, b) isgreater(a, b) etc.

So the actual function you use (sprintf) is fine and as exact as it can, it gives you correct values, you just have to know that when dealing with floats, 1.2999999 at maximum precision is OK if you were expecting 1.3

Now if you want to "pretty print" those numbers to have the best "human" representation (base 10), you may want to use a special library, like your grisu3 which will try to undo the drift that may have happen and align the number to the closest base 10 representation.

Now the library cannot use a crystal ball and find what numbers were drifted or not, so it may happen that you really meant 1.2999999 at maximum precision as stored in the computer and the lib will "convert" it to 1.3... But it's not worse nor less precise than displaying 1.29999 instead of 1.3.

If you need a good readability, such lib will be useful. If not, it's just a waste of time.

Hope this help !

陪我终i 2024-11-26 06:45:33

您可能想要使用 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< /a>):

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

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

You might want to use something like Grisu (or a faster method) because it gives you the shortest decimal representation with round trip guarantee unlike sprintf which only takes a fixed precision. The good news is that C++20 includes std::format that gives you this by default. For example:

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

prints 0.29999999999999999 while

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

prints 0.3 (godbolt).

In the meantime you can use the {fmt} library, std::format is based on. {fmt} also provides the print function that makes this even easier and more efficient (godbolt):

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

Disclaimer: I'm the author of {fmt} and C++20 std::format.

山田美奈子 2024-11-26 06:45:33

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

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

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

The best way to do this in any reasonable language is:

  1. Use your language's runtime library. Don't ever roll your own. Even if you have the knowledge and curiosity to write it, you don't want to test it and you don't want to maintain it.
  2. If you notice any misbehavior from the runtime library conversion, file a bug.
  3. If these conversions are a measurable bottleneck for your program, don't try to make them faster. Instead, find a way to avoid doing them at all. Instead of storing numbers as strings, just store the floating-point data (after possibly controlling for endianness). If you need a string representation, use a hexadecimal floating-point format instead.

I don't mean to discourage you, or anyone. These are actually fascinating functions to work on, but they are also shocking complex, and trying to design good test coverage for any non-naive implementation is even more involved. Don't get started unless you're prepared to spend months thinking about the problem.

南城旧梦 2024-11-26 06:45:33

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

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

In C++ why aren't you using iostreams? You should probably be using cout for the console and ostringstream for string-oriented output (unless you have a very specific need to use a printf family method).

You shouldn't worry about formatting performance unless actual profiling shows that CPU is the bottleneck (compared to say I/O).

故乡的云 2024-11-26 06:45:33
void outputdouble( ostringstream & oss, double d )
{
    oss.precision( 5 );
    oss << d;
}

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

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文