Can std::numeric_limits::quiet_NaN double/float store some extra info

核能气质少年 提交于 2019-12-05 21:46:42

This is known as NaN-boxing. It’s very widely used, but there’s no language-defined way of doing it since (as usual) the bit layout isn’t specified. On real implementations, with care you can get the right behavior via the obvious bit operations even though formally it’s undefined (if you use type punning via reinterpret_cast or a union) or at best unspecified (if you use memcpy or bit_cast).

Using NaN-boxing as recommanded by Davis, I could easily implement this with code working under Windows (MSVC) and Linux (gcc). Which is good enough for my needs.

#include <iostream>
#include <assert.h>
#include <limits>
#include <bitset>
#include <cmath>

void showValue( double val, const std::string& what )
{
    union udouble {
      double d;
      unsigned long long u;
    };
    udouble ud;
    ud.d = val;
    std::bitset<sizeof(double) * 8> b(ud.u);
    std::cout << val << " (" << what << "): " << b.to_string() << std::endl;
}

double customizeNaN( double value, char mask )
{
    double res = value;
    char* ptr = (char*) &res;
    assert( ptr[0] == 0 );
    ptr[0] |= mask;
    return res;
}

bool isCustomNaN( double value, char mask )
{
    char* ptr = (char*) &value;
    return ptr[0] == mask;
}

int main(int argc, char *argv[])
{
    double regular_nan = std::numeric_limits<double>::quiet_NaN();
    double myNaN1 = customizeNaN( regular_nan, 0x01 );
    double myNaN2 = customizeNaN( regular_nan, 0x02 );

    showValue( regular_nan, "regular" );
    showValue( myNaN1, "custom 1" );
    showValue( myNaN2, "custom 2" );

    assert( std::isnan(regular_nan) );
    assert( std::isnan(myNaN1) );
    assert( std::isnan(myNaN2) );

    assert( !isCustomNaN(regular_nan,0x01) );
    assert( isCustomNaN(myNaN1,0x01) );
    assert( !isCustomNaN(myNaN2,0x01) );

    assert( !isCustomNaN(regular_nan,0x02) );
    assert( !isCustomNaN(myNaN1,0x02) );
    assert( isCustomNaN(myNaN2,0x02) );

    return 0;
}

This code assumes quiet_NaN is always: 0111111111111000000000000000000000000000000000000000000000000000: 0, 11 bits set to 1,then 1000000000000000000000000000000000000000000000000000

The code could be adapted to:

  • Support both float/double through a template implementation
  • Support big/little endianess (to decide where the mask should be applied)
  • Support any nan representation (with my assumption last 8 bits are 0 and can be used as a mask, IEEE 754-1985 makes it possible to represent nan differently, for instance: 0111111111110000000000000000000000000000000000000000000000000001, then using last 8 bits as a mask would be a bad idea). But there will always be a way to customize the fraction as it will always be considered as a NaN as far as you don't end up with all bits set to 0 (which would then represent +Inf instead of NaN).

Edit: Note that this implementation is not that good as the extended info is lost when casting from float to double. See my answer to std::num_put issue with nan-boxing due to auto-cast from float to double for anoother implementation that is safer.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!