In our C++ codebase, we have a default formatting method to convert double floating point numbers into strings, that is used notably for JSON serialization and for debug logs. For that default number formatting, I have the following contradictory requirements:
- Favor human readability. Prefer
1000to1e3or0.125to1.25e-1. - Keep the precision. Prefer
3.1415926535897931to3.14. - Avoid spurious digits for decimal numbers. Prefer
0.1to0.10000000000000001.
Up to now, the best tradeoff I found is to use the equivalent of printf("%.15g", value) formatting. It fulfills requirements 1 and 3, but not completely 2. There is a loss of precision of about 4 bits.
Other people use a default formatting based on "%.17g", which fulfills requirements 1 and 2, but not 3. The number 0.2 is for example formatted as 0.20000000000000001.
In between, the format "%.16g" is close to fulfill requirements 2 and 3, but not always for both.
As an illustration, I wish 0.3 to be formatted as 0.3, but 0.1+0.2, which is slightly bigger due to rounding errors, to be formatted as 0.30000000000000004 to see the difference.
I wrote the following function that format floating point numbers the way I wish, as a proof of concept. However it is unacceptable on the performance point of view, since it can make up to 34 conversions between double and strings, for a limited precision gain over the current implementation with "%.15g".
std::string doubleToString(double number)
{
char buffer[32];
long long intVal = static_cast<long long>(number);
if(intVal == number)
{
sprintf(buffer, "%lld", intVal);
}
else
{
for(int i=1; i<=17; i++)
{
sprintf(buffer, "%.*g", i, number);
double readBack = atof(buffer);
if(readBack == number)
break;
}
}
return buffer;
}
I just realized that Python is already formatting numbers the way I want:
$ python3
Python 3.8.6 (default, Oct 8 2020, 14:06:32)
[Clang 12.0.0 (clang-1200.0.32.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.3
0.3
>>> 0.1+0.2
0.30000000000000004
>>>
Is there a way to have the same behavior in C++ without sacrificing too much of performance ?