4

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?

vitaut
  • 49,672
  • 25
  • 199
  • 336
unj2
  • 52,135
  • 87
  • 247
  • 375
  • 4
    You probably don't need to modify your code. Go and think about something more urgent instead. – Kerrek SB Jul 06 '11 at 18:24
  • 2
    What exactly do you mean by "best"? – Keith Thompson Aug 07 '11 at 00:27
  • 1
    As a person who recently used Grisu2: if you needed to modify your code, you'd know exactly why from profiling. And even then coding a simple fixed point double output routine would yield better performance than using Grisu{2,3}, although it's not always applicable – Alexei Averchenko Jan 12 '17 at 13:06

6 Answers6

4

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.

Florian Loitsch
  • 7,698
  • 25
  • 30
  • Following the specification link today, I suppose sections have been renumbered. It may be that section 7.1.12.1 "NumberToString ( m )" on page 103 of the ECMA-262 9th Edition / June 2018 standard is the current version of what Florian was referring to. – egyik Feb 11 '19 at 23:10
2

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.

vitaut
  • 49,672
  • 25
  • 199
  • 336
  • 1
    Thanks, appreciated! I am writing a stdlib for my new language & compiler and ran into this answer. The `fmt` library makes C++ code look pretty nice!. :-) – Per Lundberg Aug 30 '23 at 19:30
2

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 !

Offirmo
  • 18,962
  • 12
  • 76
  • 97
  • lol I read my own answer after a while and have difficulty to understand it ^^ Must improve my writing ^^ – Offirmo Sep 01 '11 at 14:21
1

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.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • [opinion]The fact that something is complex makes me want to understand it more, not less. Blindly using the runtime library - or any library for that matter - leads to dozens of assumptions about behavior, which is in my opinion a mistake waiting to happen. IMO The question here that you really want to be asking yourself is: do you want to be the average programmer that makes these assumptions, or do you want to be a brilliant programmer that _understands_ most of the assumptions you're making. In that context, a few months of work really don't matter; pointers in the right direction do help. – atlaste Mar 15 '15 at 13:48
0

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).

Mark B
  • 95,107
  • 10
  • 109
  • 188
-1
void outputdouble( ostringstream & oss, double d )
{
    oss.precision( 5 );
    oss << d;
}

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

Rob K
  • 8,757
  • 2
  • 32
  • 36
  • 1
    Nice: not only did you completely ignore the question, but you also managed to code a 2 line function with an unexpected side-effect! – Alexei Averchenko Jan 12 '17 at 13:03
  • @AlexeiAverchenko I didn't completely ignore the question, dumbass, I gave an off-the-cuff trivial implementation which generally answered the question "What is the best way to render double precision numbers as strings in C++?" AND a link to reference information so he could get more information. – Rob K Jan 16 '17 at 20:37