Are long doubles broken using MinGW-w64?
Asked Answered
A

0

8

I'm just starting to learn C moving from C++, and I was just trying out a ton of variable types. I am using the MingGW-w64 toolset with the GCC compiler. This version supposedly uses UCRT runtime instead of MSVCRT. I've heard long doubles are the same as doubles using MSVCRT, but UCRT appears to use 128 bits for long double and 64 for doubles. However, I am having a very strange issue with long doubles. I am using the correct "%Lg" for printf according to Google. I have tried "%llf," "%Lf," and "%Le," but they all return the same. I first defined PI as #define M_PI 3.1415926535897932384626433832795028841971693993751L since "math.h" does not appear to have M_PI defined. Casting it to a double appears to return the correct value, but the long double value is extremely small. What's going on? I realize a double will be good enough, but I am just wondering what the issue is. I realize I don't need to create a const variable for the define. I was just messing around.

The only mention I can find is that long double is broken in MinGW, but that is for the MSVCRT version, not UCRT.

#include <stdio.h>
#include <math.h>

#define M_PI 3.1415926535897932384626433832795028841971693993751L

int main(void) {

    const long double PI = M_PI;

    printf("Size of double: %d\n", (int)sizeof(double));
    printf("Size of long double: %d\n", (int)sizeof(long double));

    printf("PI == %.6Lg\n", PI);

    return 0;

}

Output:

Size of double: 8
Size of long double: 16
PI == 3.10817e-317

The tasks.json file I am using in Visual Studio Code is as follows. Note that I am a newb at compiling like this, so I may have made some mistakes.

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: gcc.exe build active file",
            "command": "C:\\mingw64\\bin\\gcc.exe",
            "args": [
                "-m64",
                "-std=c11",
                "-fdiagnostics-color=always",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "compiler: C:\\mingw64\\bin\\gcc.exe"
        }
    ]
}
Allomerism answered 12/1, 2024 at 13:8 Comment(17)
printf("PI == %.6Lg %.6Lg %.6Lg %.6Lg %.6Lg %.6Lg\n", PI);???!? How many % format specifiers are in that format string? How many values of PI are you passing? Are those numbers the same: "If there are insufficient arguments for the format, the behavior is undefined."Stanwin
@AndrewHenle I removed that. I was messing with other specifiers. I edited my post. Sorry about that. I'm still learning. But it's still different. The value of PI is VERY small.Allomerism
I tried acos(-1.0L) which returns a long double, but it still appears as 3.10817e-317.Allomerism
It looks like the problem is with printf. But try assigning the long double value back to a normal double and then print the result, to confirm it's correct. If so, then I'd say your printf thinks long double is 64 bits (or at least, something different from what your compiler is using).Gabriel
@TomKarzes Yes. Casting to a double returns the correct value. So I guess it's printf? I'll just avoid using long double then unless another compiler is good.Allomerism
@KennethClark I think there's a mismatch between the library and the compiler. The compiler is using one format, and the library is expecting another.Gabriel
@TomKarzes After searching a lot more, I believe I found a fix. MinGW has its own printf implementation which fixes the problem. It's explained in: https://mcmap.net/q/274539/-printf-and-long-doubleAllomerism
Apparently the MinGW define is deprecated now, but there are alternatives. osdn.net/projects/mingw/lists/archive/users/2019-January/…Allomerism
UCRT assumes long double has the same binary representation as double, but MinGW-w64 uses extended precision long double. So UCRT's printf and scanf family functions are incompatible with MinGW-w64's long double type.Morrison
@Kenneth Clark, "but the long double value is extremely small" --> I suspect the printed value is noise and not dependent on PI - perhaps surrounding memory?. An interesting test would be to pad the surrounding memory of PI with zeros. It might make for a zero final output. const long double PI = M_PI; --> const char a[16] = { 0 }; const long double PI = M_PI; const char b[16] = { 0 };Moor
@IanAbbott: So for compat with UCRT, you need -mlong-double-64 (gcc.gnu.org/onlinedocs/gcc/…). (which makes long double use the same binary format as double, although for the strict-aliasing rule double* and long double* are still not compatible.) Does MinGW come with or have available a C library that knows how to printf an 80-bit x87 extended-precision float, so the OP can actually play with it?Schleswigholstein
Semi-related with some info on using legacy x87 for 80-bit vs. 64-bit FP, and some other useful links: Did any compiler fully use Intel x87 80-bit floating point?Schleswigholstein
I have fixed it using MinGW's own printf function "__mingw_printf("%Lg", PI)" which prints the correct value. I just set "#define printf_ld __wingw_printf" so I have a function to print long doubles. Note that this print function seems to be slightly slower than normal printf, so I did not override printf with the function.Allomerism
@chux-ReinstateMonica: In a 64-bit build with the Windows x64 calling convention, a variadic function like printf("%Lf\n, (long double)foo) will look for a 64-bit double as its second arg in RDX (which the caller needs to set equal to XMM1, so variadic functions don't need to know which arg has which type when dumping args to shadow space to create an array of 8-byte args contiguous with the stack args). If the caller thought it was passing a long double on the stack, it won't have set RDX to anything. Try printf("%Lf", 0x4002000000000000ULL) (2.25) godbolt.org/z/foPWYvEK6Schleswigholstein
@PeterCordes I don't know about the dire warnings of changing the -mlong-double=BITS option to be different from the target ABI. TBH, I don't know why they even chose to use extended precision for MinGW in the first place.Morrison
@IanAbbott: Yeah, it seems weird. Until recently finding out different, I'd assumed the MinGW default would be -mlong-double-64 for ABI compat with MSVC, like they do for every other type and for struct layout I think.Schleswigholstein
Does this answer your question? Long double is printed incorrectly with iostreams on MinGWGherardo

© 2022 - 2025 — McMap. All rights reserved.