How to print payload of a NaN?
Asked Answered
H

2

6

We have special functions like std::nanl to make a NaN with a payload. Currently here's what I have to do to print it back:

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdint>

int main()
{
    const auto x=std::nanl("1311768467463790325");
    std::uint64_t y;
    std::memcpy(&y,&x,sizeof y);
    std::cout << (y&~(3ull<<62)) << "\n";
}

This relies on the particular representation of long double, namely on it being 80-bit type of x87 FPU. Is there any standard way to achieve this without relying on such detail of implementation?

Hadleigh answered 2/4, 2016 at 11:47 Comment(1)
No. The representation of floating point types - including any support of NaNs - is implementation-defined. Not all floating point representations can support NaNs. From a C++ implementation perspective, there is therefore no guarantee that a NaN can be represented AT ALL and - if it can be - of how it will be represented.Provisional
D
1

C++ imports nan* functions from ISO C. ISO C states in 7.22.1.3:

the meaning of the n-char sequence is implementation-defined

with a comment

An implementation may use the n-char sequence to determine extra information to be represented in the NaN’s significand.

There is no method to get the stored information.

Dissoluble answered 2/4, 2016 at 12:37 Comment(4)
How would you use frexp? Glibc for instance documents that for a NaN, it returns a NaN...Sura
@MarcGlisse Sorry, I checked ISO C++ documentation and there was no description that it does not return significand for NaN. In ISO C, it returns NaN for NaN as well. There is no way how to get it then.Dissoluble
@Dissoluble In ISO C, as of the time of your answer, there is a way to get the significant for NaN using getpayload() defined in TS 18661-1:2014, intended to be put into C2x and already part of glibc. The corresponding operation would be described in IEEE 754-2019 as getPayload.Trophy
@MingyeWang this sounds like a potential answerHadleigh
P
1

I stumbled across this one here in 2023.
Things haven’t improved much.

  • C11 supports nan*() functions (if QNaN is supported on your target processor), but
  • MSVC 2022 does not actually implement payload compiling
  • Payload must specified as a string anyway, and
  • There is still no Standard way to get the data.
  • (C23 proposes the GNU extension getPayload(), but it returns yet another double, which is far less interesting than an integer would have been.)

However

It has always been possible to get a QNaN payload, assuming you have a proper IEEE 754 QNaN with payload data. It has been put to good use on systems that do in things like Javascript and Lua, for example.[citation needed]

According to Wikipedia, after discussing some dinosaurs: [link]

It may therefore appear strange that the widespread IEEE 754 floating-point standard does not specify endianness.[3] Theoretically, this means that even standard IEEE floating-point data written by one machine might not be readable by another. However, on modern standard computers (i.e., implementing IEEE 754), one may safely assume that the endianness is the same for floating-point numbers as for integers, making the conversion straightforward regardless of data type. Small embedded systems using special floating-point formats may be another matter, however.
Emphasis added

So as long as you aren’t leaking abstractions outside of internal use or playing with specialized (or ancient) hardware then you should be good to play with stuffing stuff in your QNaNs.

As this question is tagged C++ we will have to resort to slightly uglier code than strictly necessary in C, as type-punning with a union is (probably) UB in C++.[more link] The following should work in both C and C++ and produce just as well-optimized code either way.

Da codez or go home

qnan.h

#ifndef QNAN_H
#define QNAN_H

// Copyright stackoverflow.com
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  https://www.boost.org/LICENSE_1_0.txt )

#include <assert.h>
#include <math.h>
#include <stdint.h>

#ifndef NAN
  #error "IEEE 754 Quiet NaN is required."
#endif

#ifndef UINT64_MAX
  #error "uint64_t required."
#endif

static_assert( sizeof(double) == 8, "IEEE 754 64-bit double-precision is required" );

double             qnan         ( unsigned long long payload );
unsigned long long qnan_payload ( double             qnan    );

#endif

qnan.c

// Copyright stackoverflow.com
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  https://www.boost.org/LICENSE_1_0.txt )

#include <string.h>
#include "qnan.h"

double 
qnan( unsigned long long payload )
{
  double qnan = NAN;
  uint64_t n;
  memcpy( &n, &qnan, 8 );
  n |= payload & 0x7FFFFFFFFFFFFULL;
  memcpy( &qnan, &n, 8 );
  return qnan;
}

unsigned long long 
qnan_payload( double qnan )
{
  uint64_t n;
  memcpy( &n, &qnan, 8 );
  return n & 0x7FFFFFFFFFFFFULL;
}

These two functions allow you access to all 51 bits of payload data as an unsigned integer.

Note, however, that unlike the weird-o getPayload() function the qnan_payload() function does not bother to fact-check you about your choice of input — it assumes you have given it an actual QNaN.

If you are unsure what kind of double you have, the isnan() function from <math.h> works just fine to check for QNaN-ness.

Similar code will give you access to a four-byte float or a N-byte long double (which is probably just an 8-byte double, unless it isn’t, and is probably more trouble supporting than it’s worth).

Prelude answered 12/2, 2023 at 6:27 Comment(2)
"but it returns yet another double, which is far less interesting than an integer would have been" — actually, the description you linked to does specify an integer, just encoded as a double with zero fractional part.Hadleigh
"long double (which is probably just an 8-byte double, unless it isn’t, and is probably more trouble supporting than it’s worth" — not as probably: on x86 and x86_64 (major architecture today) with GCC and Clang it's not the same as double, and is actually useful, unlike on MSVC, where indeed it's just another copy of double.Hadleigh

© 2022 - 2024 — McMap. All rights reserved.