代码之家  ›  专栏  ›  技术社区  ›  jpo38

std::numeric\u limits::quiet\u NaN double/float是否可以存储一些额外的信息

  •  3
  • jpo38  · 技术社区  · 7 年前

    std::numeric_limits::quiet_NaN() . 但是,我想存储一些额外的信息,以了解数据“丢失”的原因(数据传输丢失、校验和错误、未进行测量、内部错误……),因此最终我需要许多不同的“nan”值。而且它们都必须被任何遗留代码标识为NaN( x!=x ).

    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 回复  |  直到 7 年前
        1
  •  2
  •   Davis Herring    7 年前

    这被称为 NaN-boxing 非常 广泛使用,但没有语言定义的方法,因为(通常)没有指定位布局。在实际实现中,您可以通过明显的位操作获得正确的行为,即使在形式上它是未定义的(如果您通过 reinterpret_cast memcpy 或 bit_cast

        2
  •  0
  •   jpo38    7 年前

    使用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;
    }
    

    此代码假定“安静”总是: 0111111111111000000000000000000000000000000000000000000000000000 : 0 ,11位设置为 1 那么 1000000000000000000000000000000000000000000000000000

    该守则可适用于:

    • float / double
    • 并且可以用作掩码,IEEE 754-1985使以不同方式表示nan成为可能,例如: 0111111111110000000000000000000000000000000000000000000000000001 ,然后使用最后8位作为掩码将是一个坏主意)。但是总会有一种方法来定制分数,因为它总是被认为是一个NaN,只要你不把所有的位都设置为0(这将代表 +Inf NaN

    编辑:注意这个实现不是很好,因为当从float转换为double时,扩展信息丢失了。看见 my answer 到 std::num_put issue with nan-boxing due to auto-cast from float to double