Patrick
Patrick

Reputation: 23619

Rounding values in C++. Why do printf, iostream and round function [could] behave differently, depending on the Visual Studio and Windows version?

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://learn.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?

Upvotes: 3

Views: 1127

Answers (1)

Jeaninez - MSFT
Jeaninez - MSFT

Reputation: 4032

round, roundf, roundl:Rounds a floating-point value to the nearest integer value.

printf, _printf_l, wprintf,_wprintf_l:Starting in Windows 10 version 2004 (build 19041), the printf family of functions prints exactly representable floating point numbers according to the IEEE 754 rules for rounding.In previous versions of Windows, exactly representable floating point numbers ending in '5' would always round up. IEEE 754 states that they must round to the closest even digit (also known as "Banker's Rounding").This change only affects programs built using Visual Studio 2019 version 16.2 and later. To use the legacy floating point rounding behavior, link with legacy_stdio_float_rounding.obj.

std::fixed << std::setprecision(0):The so-called rounding six to five leaving a double means that if it happens to be 0.5, it will be approximated to make the previous digit an even number.

Upvotes: 1

Related Questions