12

The following code assumes that we are on an x86-compatible system and that long double maps to x87 FPU's 80-bit format.

#include <cmath>
#include <array>
#include <cstring>
#include <iomanip>
#include <iostream>

int main()
{
    std::array<uint8_t,10> data1{0x52,0x23,0x6f,0x24,0x8f,0xac,0xd1,0x43,0x30,0x02};
    std::array<uint8_t,10> data2{0x52,0x23,0x6f,0x24,0x8f,0xac,0xd1,0xc3,0x30,0x02};
    std::array<uint8_t,10> data3{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x30,0x02};
    long double value1, value2, value3;
    static_assert(sizeof value1 >= 10,"Expected float80");
    std::memcpy(&value1, data1.data(),sizeof value1);
    std::memcpy(&value2, data2.data(),sizeof value2);
    std::memcpy(&value3, data3.data(),sizeof value3);
    std::cout << "isnan(value1): " << std::boolalpha << std::isnan(value1) << "\n";
    std::cout << "isnan(value2): " << std::boolalpha << std::isnan(value2) << "\n";
    std::cout << "isnan(value3): " << std::boolalpha << std::isnan(value3) << "\n";
    std::cout << "value1: " << std::setprecision(20) << value1 << "\n";
    std::cout << "value2: " << std::setprecision(20) << value2 << "\n";
    std::cout << "value3: " << std::setprecision(20) << value3 << "\n";
}

Output:

isnan(value1): true

isnan(value2): false

isnan(value3): false

value1: 3.3614005946481929011e-4764

value2: 9.7056260598879139386e-4764

value3: 6.3442254652397210376e-4764

Here value1 is classified as "unsupported" by 387 and higher, because it has nonzero and not all-ones exponent — it's in fact an "unnormal". And isnan works as expected with it: the value is indeed nothing of a number (although not exactly a NaN). The second value, value2, has that integer bit set, and also works as expected: it's not a NaN. The third one is the value of the missing integer bit.

But somehow both numbers value1 and value2 appear printed, and the values differ exactly by the missing integer bit! Why is that? All other methods I tried, like printf and to_string give just 0.00000.

Even stranger, if I do any arithmetic with value1, in subsequent prints I do get nan. Taking this into account, how does operator<<(long double) even manage to actually print anything but nan? Does it explicitly set the integer bit, or maybe it parses the number instead of doing any FPU arithmetic on it? (assuming g++4.8 on Linux 32 bit).

Community
  • 1
  • 1
Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • This is probably similar to http://stackoverflow.com/questions/4518951/engineered-bool-compares-equal-to-both-true-and-false-why. The implementation is entitled to assume that what you give it contains a valid `long double` representation (or `bool` in the linked question). If you pass it an invalid representation, all bets are off. – T.C. Jul 26 '15 at 20:37
  • UB Is U. Why should you expect any particular behavior? – David Schwartz Jul 26 '15 at 21:06
  • 6
    “it's in fact an "unnormal". And isnan works as expected with it: the value is indeed nothing of a number” This is not what “NaN” means. NaN is a floating-point value that will propagate through computations in a specified way. An unnormal value is a historical remnant and it has been unsupported to pass one to a floating-point instruction since the 387. For practical purposes, an unnormal value should be considered a “trap representation” that invokes undefined behavior upon use, not a NaN. – Pascal Cuoq Jul 26 '15 at 21:55

2 Answers2

1

All other methods I tried, like printf and to_string give just 0.00000.

What operator<<(long double) actually does is using the num_put<> class from locale library to perform the numeric formatting, which in turn uses one of the printf-family functions (see sections 27.7.3.6 and 22.4.2.2 of the C++ standard).

Depending on the settings, printf conversion specifier used for long double by locale might be any of: %Lf, %Le, %LE, %La, %LA, %Lg or %LG.

In your (and my) case it seems to be %Lg:

printf("value1: %.20Lf\n", value1);
printf("value1: %.20Le\n", value1);
printf("value1: %.20La\n", value1);
printf("value1: %.20Lg\n", value1);
std::cout << "value1: " << std::setprecision(20) << value1 << "\n";

value1: 0.00000000000000000000

value1: 3.36140059464819290106e-4764

value1: 0x4.3d1ac8f246f235200000p-15826

value1: 3.3614005946481929011e-4764

value1: 3.3614005946481929011e-4764


Taking this into account, how does operator<<(long double) even manage to actually print anything but nan? Does it explicitly set the integer bit, or maybe it parses the number instead of doing any FPU arithmetic on it?

It prints the unnormalized value.

Conversion from binary to decimal floating point representation used by printf() may be performed without any FPU arithmetics. You can find the glibc implementation in the stdio-common/printf_fp.c source file.

0

I was trying this:

long double value = std::numeric_limits<long double>::quiet_NaN();
std::cout << "isnan(value): " << std::boolalpha << std::isnan(value) << "\n";
std::cout << "value: " << std::setprecision(20) << value << "\n";

So my assumption is that as stated here: http://en.cppreference.com/w/cpp/numeric/math/isnan value is being cast to double and not long double when evaluated by std::isnan and strictly:

std::numeric_limits<long double>::quiet_NaN() != std::numeric_limits<double>::quiet_NaN()