Why Did Microsoft Abandon Long Double Data Type

Why did Microsoft abandon long double data type?

I'm not sure why you think that long double was "abandoned", as it is part of the C++ Standard and therefore a compliant implementation must, well, implement it.

What they did "abandon" is long double overloads of mathematical functions, and they did this because:

In Win32 programming, however, the long double data type maps to the double, 64-bit precision data type.

which, in turn, along with long double in older VS versions being 80-bit, is because:

FP code generation has been switching to the use of SSE/SSE2/SSE3 instruction sets instead of the x87 FP stack since that is what both the AMD and Intel recent and future chip generations are focusing their performance efforts on. These instruction sets only support 32 and 64 bit FP formats.

Still, that they chose not to support these overloads, even with same-sized double and long double types (both could have been made 64-bit), is a shame because they are also part of the C++ Standard. But, well, that's Microsoft for you. Intently stubborn.

[n3290: 26.8]: In addition to the double versions of the math
functions in <cmath>, C++ adds float and long double overloaded
versions of these functions, with the same semantics.

However, although these overloads are essentially deprecated in Visual Studio, they are still available, so you should still be able to use them:

The Microsoft run-time library provides long double versions of the
math functions only for backward compatibility.


Is there any alternative to use? (I would also be happy with an alternative out of the boost library).

It sounds to me like you have been relying on long double to support a specific range of numeric values, and have consequently run into regression issues when that has changed in a different toolchain.

If you have a specific numeric range requirement, use fixed-range integral types. Here you have a few options:

  • stdint.h - a C99 feature that some C++ toolchains support as an extension;
  • stdint.h - a C99 feature that Boost re-implements as a library;
  • cstdint - a C++0x feature that may be of use if you are writing C++0x code.

Why are double and long double completely the same on my 64 bit machine?

In MSVC++, long double is a synonym for double as you've found out. Apparently this is to take advantage of SSE/SSE2/SSE3 instruction sets which are limited to 64-bit operations.

See also here for more information.

Performance implications of long double. Why does C choose 64-bits instead of the hardware's 80-bit for its default?

The answer, according to Mysticial, is that Microsoft uses SSE2 for its double data-type. The Floating point unit (FPU) x87 is seen as outdated and slow in comparison to modern CPU extensions. SSE2 does not support 80-bit, hence the compiler's choice of 64-bit precision.

On 32-bit x86 architecture, since all CPUs don't have SSE2 yet, Microsoft still uses the floating point unit (FPU) x87 unless the compiler switch /arch:SSE2 is given. Which makes the code incompatible with those older? CPUs.

Casting from long double to unsigned long long appears broken in the MSVC C++ compiler

You are seeing undefined behaviour because, as pointed out in the comments, a long double is the same as a double in MSVC and the 'converted' value of your 0xFFFFFFFFFFFFFFFF (or ULLONG_MAX) actually gets 'rounded' to a slightly (but significantly) larger value, as can be seen in the following code:

int main(int argc, char* argv[])
{
long double test = 0xFFFFFFFFFFFFFFFF;
cout << 0xFFFFFFFFFFFFFFFFuLL << endl;
cout << fixed << setprecision(16) << endl;
cout << test << endl;
return 0;
}

Output:

18446744073709551615
18446744073709551616.0000000000000000

Thus, when converting that floating-point value back to an unsigned long long, you are falling foul of the conversion rules specified in this Microsoft document:

  • For conversion to unsigned long or unsigned long long, the result of converting an out-of-range value may be some value other than the
    highest or lowest representable value. Whether the result is a
    sentinel or saturated value or not depends on the compiler options and
    target architecture. Future compiler releases may return a saturated
    or sentinel value instead.

This UB can be further 'verified' (for want of a better term) by switching to the clang-cl compiler that can be used from within Visual Studio. For your original code, this then gives 0 for the values on both the "2" and "3" output lines.

Assuming that the clang (LLVM) compiler is not bound by the aforementioned "Microsoft Rules," we can, instead, fall back on the C++ Standard:

7.10 Floating-integral conversions      [conv.fpint]

1     A prvalue of a floating-point type
can be converted to a prvalue of an integer type. The conversion
truncates; that is, the fractional part is discarded. The behavior is
undefined if the truncated value cannot be represented in the
destination type.

Long Double vs Long Long

long long is integer (possibly with more range than long)

long double is floating point (possibly with more range/precision than double)

long float does not exist.

The integer types sorted by range are

  • _Bool
  • char or signed char or unsigned char
  • short (or short int) or short unsigned
  • int or unsigned
  • long (or long int) or long unsigned
  • long long (or long long int) or long long unsigned

The floating-point types sorted by range/precision are

  • float
  • double
  • long double

Wrong C++ long double min/max values using numeric_limits in Visual Studio 2019

The c++ standard only requires long double to have at least the precision of double, so there's nothing wrong with the output of your program.

Quote from the standard (§3.9.1, lit 8):

There are three floating point types: float,double, and long double.The type double provides at least as much precision as float, and the type long double provides at least as much precision as double.The set of values of the type float is a subset of the set of values of the type double; the set of values of the type double is a subset of the set of values of the type long double. The value representation of floating-point types is implementation-defined. Integral and floating types are collectively called arithmetic types. Specializations of the standard template std::numeric_limits (18.3) shall specify the maximum and minimum values of each arithmetic type for an implementation.

Long double is printed incorrectly with iostreams on MinGW

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).


Related Topics



Leave a reply



Submit