9

是否有符合 C++ 标准的方法来在编译时(或运行时,作为替代方案)确定“float”、“double”和“long double”的结构?

如果我假设std::numeric_limits< T >::is_iec559 == truestd::numeric_limits< T >::radix == 2,我怀疑这可能通过以下规则:

  • 第一个 X 位是有效数字。
  • 接下来的 Y 位是指数。
  • 最后一位是符号位。

用下面的表达式模糊地像:

  • size_t num_significand_bits = std::numeric_limits< T >::digits;
  • size_t num_exponent_bits = log2( 2 * std::numeric_limits< T >::max_exponent );
  • size_t num_sign_bits = 1u;

除了我知道

  • std::numeric_limits< T >::digits包括“整数位”,无论格式是否实际明确表示它,所以我不知道如何以编程方式检测和调整这一点。
  • 我猜std::numeric_limits< T >::max_exponent总是2^(num_exponent_bits)/2

背景:我正在尝试以便携方式克服两个问题:

  • 设置/获取有效数字中的位。
  • 确定“long double”的结尾在哪里,这样我就知道不要读取具有未初始化内存的隐式填充位。
4

3 回答 3

5

简而言之,没有。如果std::numeric_limits<T>::is_iec559,那么您T或多或少地知道 的格式:您仍然需要确定字节顺序。对于其他任何事情,所有赌注都已取消。(我知道仍在使用的其他格式甚至不是基数 2:例如,IBM 大型机使用基数 16。)IEC 浮点的“标准”排列在高位上有符号,然后是指数,以及低位的尾数;如果您可以成功地将其视为 uint64_t,例如(通过memcpyreinterpret_castunion—`memcpy 保证可以工作,但效率低于其他两个),那么:

对于double

uint64_t tmp;
memcpy( &tmp, &theDouble, sizeof( double ) );
bool isNeg = (tmp & 0x8000000000000000) != 0;
int  exp   = (int)( (tmp & 0x7FF0000000000000) >> 52 ) - 1022 - 53;
long mant  = (tmp & 0x000FFFFFFFFFFFFF) | 0x0010000000000000;

对于`浮动:

uint32_t tmp;
memcpy( &tmp, &theFloat, sizeof( float ) );
bool isNeg = (tmp & 0x80000000) != 0;
int  exp   = (int)( (tmp & 0x7F800000) >> 23 ) - 126 - 24 );
long mant  = (tmp & 0x007FFFFF) | 0x00800000;

关于long double,情况更糟,因为不同的编译器对它的处理方式不同,即使在同一台机器上也是如此。名义上,它是十个字节,但出于对齐的原因,它实际上可能是 12 或 16。或者只是double. 如果它超过 10 个字节,我认为你可以指望它被打包到前 10 个字节中,这样就&myLongDouble给出了 10 个字节值的地址。但一般来说,我会避免 long double.

于 2013-03-08T19:14:28.627 回答
1

我会说唯一可移植的方法是将数字存储为字符串。这不依赖于“解释位模式”

即使您知道某事物有多少位,也不意味着它具有相同的表示形式——从零开始的指数或有偏差的指数。尾数前面有一个看不见的1吗?这同样适用于号码的所有其他部分。对于 BCD 编码或“十六进制”浮点数来说,情况会变得更糟——这些在某些架构中是可用的......

如果您担心结构(类、数组等)中未初始化的位,请使用 memset 将整个结构设置为零 [或其他一些已知值]。

于 2013-03-08T18:55:44.250 回答
0

为了后代,这就是我最终做的事情。

为了生成和测试我的 IEEE-754 信号 NaN 值,我将此模式用于“浮点”和“双精度”。

#include <cstdint> // uint32_t, uint64_t
#include <limits> // numeric_limits

union IEEE754_Float_Union
{
    float value;
    uint32_t bits;
};

float generate_IEEE754_float()
{
    IEEE754_Float_Union u = { -std::numeric_limits< float >::signaling_NaN() };
    size_t const num_significand_bits_to_set = std::numeric_limits< float >::digits
                                               - 1 // implicit "integer-bit"
                                               - 1; // the "signaling-bit"
    u.bits |= ( static_cast< uint32_t >( 1 ) << num_significand_bits_to_set ) - 1;
    return u.value;
}

bool test_IEEE754_float( float const& a_r_val )
{
    IEEE754_Float_Union const u = { a_r_val };
    IEEE754_Float_Union const expected_u = { generate_IEEE754_float() };
    return u.bits == expected_u.bits;
}

For 'long double', I use the 'double' functions with casting. Specifically, I generate the 'double' value and cast it to 'long double' before it's returned, and I test the 'long double' by casting to 'double' then testing that value. My idea is that, while the 'long double' format can vary, casting a 'double' into a 'long double', then casting it back to 'double' later on should be consistent, ( i.e. not loose any information. )

于 2013-03-11T17:35:32.917 回答