vitaut vitaut - 1 month ago 9
C++ Question

Long double is printed incorrectly with iostreams on MinGW

Consider the code

#include <iostream>

int main() {
std::cout << 4.2L;
}


Compiling it on MinGW and running results in the following output:

> g++ test.cc
> a.exe
-7.89773e-278


Is it a bug in MinGW and is there a fix or workaround?

Update:

There is a similar issue with
printf
described in this question:

#include <cstdio>

int main() {
std::printf("%Lg", 4.2L); // prints -7.89773e-278
}


However, the issue with
printf
can be fixed by defining
__USE_MINGW_ANSI_STDIO
while this one can't, so I think it deserves a separate question.

Answer

This is not a MinGW bug ... controversial as that statement may seem, the reality is that it is a limitation of Microsoft's C/C++ runtime library, upon which MinGW is dependent. As a developer, it is incumbent on you to understand the limitations of your tools, such as this one, and to work within those limitations.

The problem you are experiencing is due to Microsoft's lack of any distinct implementation of a long double data type in MSVC, and the consequent lack of effective support for that data type within the I/O subsystem provided by MSVCRT.DLL; (and, before you tell me, perhaps indignantly, that "of course MSVC supports long double", I know that it does syntactically, but semantically it has no distinct implementation, simply behaving by effectively ignoring the long qualifier, such that long double becomes a synonym for bare double).

Conversely, GCC and hence MinGW does have an implementation of long double, which is distinct from double; the former is an 80-bit entity, whereas the latter is 64-bit, and is an exact analogue of MSVC's 64-bit implementation of both data types. This is great when you need the greater precision of 80-bit floating point computation, but it can lead to problems, such as you are experiencing, when it comes to output of results; (the I/O translator is handed an 80-bit raw data representation of a long double data entity, where it expects 64-bit; the internal representations are incompatibly different, so garbage ensues when part of the mantissa is interpreted as exponent).

As you've noted, whereas MSVCRT.DLL supports only output of 64-bit double values, MinGW does offer an alternative implementation of C's printf style I/O, which can correctly translate the 80-bit format; however, this does not extend to supporting C++ style I/O. Thus, in C++ code, you cannot simply exploit the MinGW alternative I/O implementation, while continuing to use C++ I/O semantics; you have to recognize the MSVCRT.DLL limitation, and code your application accordingly. Some options you might consider include:--

  1. Forego the use of the long double data type, and perform your computations as double; (this is the only option which is effectively available to users of Microsoft's compiler, because it doesn't really have a distinct long double data type implementation to begin with).
  2. Perform the computation as long double, but cast the result to double for output.
  3. Use C style I/O functions instead of the C++ I/O semantics, and enable MinGW's alternative printf implementation by compiling with any of -posix, -D_GNU_SOURCE, -D_BSD_SOURCE, -D_XOPEN_SOURCE=700, or -D_POSIX_C_SOURCE=200809L, (or better, add #define for any of the latter four to your source, before any #include). If preferred, you may also substitute any earlier compliance level for _XOPEN_SOURCE or _POSIX_C_SOURCE; (however, please ignore the incredibly bad advice, which may be offered by some commentators, to compile with -D__USE_MINGW_ANSI_STDIO; the double underscore, which introduces that macro name, marks it as "implementation reserved", and therefore not something which you, as an end user of the compiler implementation, should be referring to directly).
  4. Use C's snprintf function to convert long double data to a C string representation, then use C++ semantics to output that instead of leaving C++ to translate the raw form of the long double entity directly. (IIRC, Microsoft don't provide snprintf -- they provide _snprintf instead -- so if you are careful to use the ANSI function name, you get 80-bit long double support automatically).