c++visual-c++floating-pointroundingostream

Why is 0.0 printed as 0.00001 when rounding upward?


If in a C++ program, I activate upward rounding mode for floating-point numbers and print some double-precision value already rounded to an integer, e.g.:

#include <cfenv>
#include <iostream>

int main() {
    std::fesetround(FE_UPWARD);
    std::cout << 0.0 << '\n';
}

I expect to see the value as is (0 here) since no additional rounding is actually necessary to show the exact value.

But the program prints 0.00001 if compiled in MSVC on Compiler Explorer.

Is the implementation correct here?


Solution

  • TL;DR: This is a bug in Windows UCRT (Universal C Run Time), introduced in Windows 10 version 2004 (build 19041), and should have been fixed in Windows 11 version 22H2 (build 22621).

    Before Windows 10 build 19041, UCRT's printf does not respect the rounding mode. It always rounds up when the first discarded digit is 5. (This behavior is documented.) Build 19041 fixes this for programs built using VS 2019 version 16.2 and later by deciding whether to round up based on the result of fegetround(), but it has a bug in the new behavior for FE_UPWARD and FE_DOWNWARD. Build 22621 modifies the behavior again to fix this bug.

    Compiler Explorer probably runs on Windows Server 2022, which has build number 20348, and thus has this bug.


    Technical details: In build 19041, if fegetround() returns FE_UPWARD, the output is rounded up if the sign is not '-' and the first discarded digit is not '0'. In the case of 0.0, that character is considered to be '\0', which is not equal to '0', so it is (incorrectly) decided that the output needs to be rounded up.

    In build 22621, UCRT instead searches for a non-zero discarded digit. In the case of 0.0, it is correctly decided that there's no such character and the output should not be rounded up.

    The relevant source code is available as a nuget package:

    The source code of UCRT version 10.0.22621.3 is also available under the MIT license at Microsoft.Windows.SDK.CRTSource.