0

So I was recently upgrading an old c++ project that was built using the Visual Studio 2012 - Windows XP (v110_xp) platform toolset. In the code of this project, there is some very precise double calculations happening to require up to 20 characters of precision. These doubles were then saved to a string and printed off using the printf APIs. Here is an example of what something that would happen in this project:

        double testVal = 123.456789;

        // do some calculations on testVal

        char str[100] = { 0 };

        sprintf(str, "%.20le", testVal);

After this operation str = "1.23456789000...000e+02", which is what is expected.

However, once I update the project to be compatible with Visual Studio 2019, using Visual Studio 2019 (v142) platform Toolset, with c++ 17, the above-mentioned code produces different outputs for str. After the call to sprintf to format the value to a string, str = "1.23456789000...556e+02". This problem isn't localized to this one value, there are even more aggregious problems. For example, one of the starting values of "2234332.434322" after the sprintf formatting gets changed to "2.23433324343219995499e+07"

From all the documentation I've read with the "l" format code, it should be the correct character for converting long doubles to the string. This behavior feels like textbook float->double conversion though.

I tried setting the projects floating-point model build an argument to precise, strict, and then fast to see if any of these options would help, but it does not have an effect on the problem.

Does anyone know why this is happening?

Ali Kianoor
  • 1,167
  • 9
  • 18
Rodan
  • 1
  • 1
  • Are you concerned about why the change has happened, or do you think the new results are incorrect? – john Jun 30 '20 at 15:48
  • 2
    IIRC correctly a double has only about 17 decimal digits of precision, so 3 'garbage' digits at the end are only to be expected. – john Jun 30 '20 at 15:50
  • I think the new results are incorrect. The different values are causing some problems further down the line in the application. – Rodan Jun 30 '20 at 15:52
  • 2
    You may want to read these links: [Is floating point math broken?](https://stackoverflow.com/q/588004/5910058) , [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). Also, there's no guarantee that floats or doubles converted to strings will roundtrip correctly back to floats with zero error (*especially not* across platforms or compilers). – Jesper Juhl Jun 30 '20 at 15:54
  • @john I think you are correct, but this explanation would only fit the first value not the second. If that's what the cause is, I would expect the second value to be something like "2.23433324343220000XXXe+07" – Rodan Jun 30 '20 at 15:57
  • You might find this site helpful https://www.exploringbinary.com/floating-point-converter/ That site agrees the first new result is correct but the second is wrong. – john Jun 30 '20 at 15:59
  • Unfortauntely C++ has never guaranteed that conversions from floating point to a decimal representation have to be accurate. – john Jun 30 '20 at 16:00
  • @JesperJuhl thank you for that reference, I am reading that now – Rodan Jun 30 '20 at 16:01
  • @Rodan google have a library that guarantees accurate conversions, maybe you could switch to using that. https://github.com/google/double-conversion – john Jun 30 '20 at 16:04
  • Your number was never actually accurate to that many digits in the first place btw. – Jesper Juhl Jun 30 '20 at 16:05
  • Thanks for the information. So I guess the fact that there wasn't any issues before was just a happy little accident and now I'm facing the cold hard reality – Rodan Jun 30 '20 at 16:18
  • 1
    @john the newer Visual Studio is actually giving the more accurate value. `123.456789` is actually `123.4567890000000005557012627832591533660888671875` when rounded to the nearest [IEEE-754 double](https://en.wikipedia.org/wiki/IEEE_754). Unfortunately x64 (and thus Visual Studio) don't provide a larger floating point type. – Mark Ransom Jun 30 '20 at 16:30

1 Answers1

2

Use the brand new Ryu (https://github.com/ulfjack/ryu) or Grisu-Exact (https://github.com/jk-jeon/Grisu-Exact) instead which are much faster than sprintf and guaranteed to be roundtrip-correct (and more), or the good old Double-Conversion (https://github.com/google/double-conversion) which is slower than the other two but has the same guarantees, still much faster than sprintf, and is battle-tested.

(Disclaimer: I'm the author of Grisu-Exact.)

I'm not sure if you really need to print out exactly 20 decimal digits, because I personally had rare occasions where the number of digits mattered. If the sole purpose of having 20 digits is just to not lose any precision, then the above mentioned libraries will definitely provide you better (and shorter) results. If the number of digits must be precisely 20 for some reasons, then well, Ryu still provides such a feature (it's called Ryu-printf) which again has the roundtrip-guarantee and much faster than sprintf.

EDIT

To elaborate more on the last sentence, note that in general it is impossible to have the roundtrip guarantee if the number of digits is fixed, because, well, if that fixed number is too small, say, 3, then there is no way to distinguish 0.123 and 0.1234. However, 20 is big enough so that the best approximation of the true value (which is what Ryu-printf produces) is always guaranteed to be roundtrip-correct.

Junekey Jeon
  • 1,496
  • 1
  • 11
  • 18