可以std :: numeric_limits :: quiet_NaN double / float存储一些额外的信息

时间:2018-12-05 10:08:47

标签: c++ c++11 nan

在我的数据采集项目中存储双精度数据时,我使用std::numeric_limits::quiet_NaN()标识所有“丢失”的数据。但是,我想存储一些额外的信息,以了解为什么数据“丢失”(数据传输丢失,校验和不正确,未完成测量,内部错误...),因此我需要在数据库中使用许多不同的“ nan”值结束。并且必须通过任何旧版代码(x!=x)将它们全部识别为NaN。

我在IEEE 754-1985中看到NaN分数可以是“除全0比特外的任何东西(因为全0比特表示无穷大)。”可以使用fraction安全地存储一些额外的信息吗?如果是,我应该怎么做?在所有平台上以及任何编译器下,这都是完全安全的吗?

这就是我在想什么:

double GetMyNaN1()
{
    double value = std::numeric_limits<double>::quiet_NaN();
    // customize it!
    return value;
}

double GetMyNaN2()
{
    double value = std::numeric_limits<double>::quiet_NaN();
    // customize it!
    return value;
}

bool IsMyNan1( double value )
{
    // return true if value was created by GetMyNaN1() 
}

bool IsMyNan2( double value )
{
    // return true if value was created by GetMyNaN2() 
}

int main()
{
    double regular_nan = std::numeric_limits<double>::quiet_NaN();
    double my_nan_1 = GetMyNaN1();
    double my_nan_2 = GetMyNaN2();

    assert( std::isnan( regular_nan ) && !IsMyNan1( regular_nan ) && !IsMyNan2( regular_nan ) );
    assert( std::isnan( my_nan_1 ) && IsMyNan1( my_nan_1 ) && !IsMyNan2( my_nan_1 ) );
    assert( std::isnan( my_nan_2 ) && !IsMyNan1( my_nan_2 ) && IsMyNan2( my_nan_2 ) );
    return 0;
}

该代码必须在所有平台上均可使用。

2 个答案:

答案 0 :(得分:2)

这被称为NaN-boxing。它已经被广泛使用,但由于没有指定位布局,因此没有语言定义的方式。在实际的实现中,请小心地通过明显的位操作来获得正确的行为,即使它在形式上是未定义的(如果您通过reinterpret_cast或联合使用类型punning),或者最好是未指定的(如果您使用{{1} }或memcpy)。

答案 1 :(得分:0)

使用Davis推荐的NaN-boxing,我可以在Windows(MSVC)和Linux(gcc)下运行的代码轻松实现此目标。足够满足我的需求。

#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;
}

此代码假定quiet_NaN始终为: 01111111111110000000000000000000000000000000000000000000000000000,将11位设置为1,然后1000000000000000000000000000000000000000000000000000

该代码可适用于:

  • 通过模板实现同时支持float / double
  • 支持大/小耐力(决定应在何处使用面罩)
  • 支持任何nan表示形式(假设我的后8位是0,并且可以用作掩码,IEEE 754-1985可以用不同的方式表示nan,例如:0111111111110000000000000000000000000000000000000000000000000001,然后使用最后8位作为掩码将是一个坏主意)。但是,总会有一种自定义分数的方法,因为只要您不将所有位都设置为0(那么它将代表+Inf而不是{{1 }}。

编辑:请注意,此实现的效果不佳,因为从float转换为double时扩展信息会丢失。有关更安全的其他实现,请参见my answerstd::num_put issue with nan-boxing due to auto-cast from float to double