在我的数据采集项目中存储双精度数据时,我使用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;
}
该代码必须在所有平台上均可使用。
答案 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始终为:
0111111111111000000000000000000000000000000000000000000000000000
:
0
,将11位设置为1
,然后1000000000000000000000000000000000000000000000000000
该代码可适用于:
float
/ double
0
,并且可以用作掩码,IEEE 754-1985可以用不同的方式表示nan,例如:0111111111110000000000000000000000000000000000000000000000000001
,然后使用最后8位作为掩码将是一个坏主意)。但是,总会有一种自定义分数的方法,因为只要您不将所有位都设置为0(那么它将代表+Inf
而不是{{1 }}。编辑:请注意,此实现的效果不佳,因为从float转换为double时扩展信息会丢失。有关更安全的其他实现,请参见my answer至std::num_put issue with nan-boxing due to auto-cast from float to double。