What started as a very simple problem, is now getting a nightmare. Rounding values in C++ behaves differently depending on quite some factors.
Start with the following simple piece of code, where you pass values just in the middle of 2 integer values to other functions:
#include <stdio.h>
extern void print(double d);
extern void stream(double d);
extern void rounding(double d);
int main()
{
for (auto i=0;i<10;++i)
print(i+0.5);
printf("\n");
for (auto i=0;i<10;++i)
stream(i+0.5);
printf("\n");
for (auto i=0;i<10;++i)
rounding(i+0.5);
printf("\n");
}
The 3 functions print out the values in 3 different ways: using printf, using operator<< and using the round function:
#include <stdio.h>
#include <iomanip>
#include <iostream>
void print(double d)
{
printf("%.0lf ",d);
}
void stream(double d)
{
std::cout << std::fixed << std::setprecision(0) << d << " ";
}
void rounding(double d)
{
auto r = round(d);
printf("%.0lf ",r);
}
In all these cases I want to print out the value without digits after the decimal point.
I get all these combinations:
Compile with Visual Studio 2015 or 2017, run on Windows Server 2019, build 14393:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
Compile with Visual Studio 2015 or 2017, run on Windows 10, build 19041:
1 2 3 4 5 6 7 8 9 10
0 2 2 4 4 6 6 8 8 10
1 2 3 4 5 6 7 8 9 10
As you can see, using iostreams, operator<< suddenly decides to use Bankers Rounding starting from this Windows version.
Compile with Visual Studio 2019, run on Windows Server 2019, build 14393:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
Compile with Visual Studio 2019, run on Windows 10, build 19041:
0 2 2 4 4 6 6 8 8 10
0 2 2 4 4 6 6 8 8 10
1 2 3 4 5 6 7 8 9 10
Now the printf function also starts to use Bankers Rounding (which wasn't the case when compiled with VS2015 or VS2017).
The page https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fprintf-fprintf-l-fwprintf-fwprintf-l?view=msvc-160 states that you can get the old behavior back if you link in the legacy_stdio_float_rounding.obj object file. And indeed, if you link this in, then you get this:
Compile with Visual Studio 2019, link with legacy object file, run on Windows 10, build 19041:
1 2 3 4 5 6 7 8 9 10
0 2 2 4 4 6 6 8 8 10
1 2 3 4 5 6 7 8 9 10
Unfortunately, I can't seem to get the old behavior back for the streaming output operator.
Is anyone else struggling with this problem as well?
What is the best solution to get a consistent rounding?
Since the C standard clearly specifies how the round function should behave (round upwards to +/- infinity depending on the sign of the value), it seems logical to have printf and operator<< to behave like this as well. So should we tell our developers to prevent using the output operator (more specifically, std::fixed and std::setprecision) when streaming floating point values?
To make things even worse: some external modules are written in JavaScript, which has even a different way of rounding (always rounds towards +infinity, even for negative numbers). As I said in the beginning: what started as a simple problem, is now becoming a consistency nightmare.
Did you encounter the same problem? And how did you handle it?