【问题标题】:define double constant as hexadecimal?将双常量定义为十六进制?
【发布时间】:2022-03-25 19:02:06
【问题描述】:

我希望将低于 1.0 的最接近的数字作为浮点数。通过阅读维基百科关于IEEE-754 的文章,我发现1.0 的二进制表示是3FF0000000000000,所以最接近的双精度值实际上是0x3FEFFFFFFFFFFFFF

我知道用这个二进制数据初始化双精度的唯一方法是:

double a;
*((unsigned*)(&a) + 1) = 0x3FEFFFFF;
*((unsigned*)(&a) + 0) = 0xFFFFFFFF;

使用起来比较麻烦。

如果可能的话,有没有更好的方法来定义这个双数?

【问题讨论】:

  • 唯一的方法是……除非你的 C++ 实现支持 64 位整数。
  • 这只是吹毛求疵,但它是 IEEE-754-1985(不是 IEEE-745)。

标签: c++ floating-point floating-accuracy ieee-754 notation


【解决方案1】:

确实存在十六进制浮点数和双精度字面值。 语法为 0x1.(尾数)p(十进制指数) 在你的情况下,语法是

double x = 0x1.fffffffffffffp-1

【讨论】:

  • 我以前从未听说过这种语法。你有参考吗?
  • 我认为它是 C99 标准的一部分。它适用于 GNU 编译器,我不关心其他编译器。
  • @Mark Ransom:我最近写了一篇关于这个的文章:exploringbinary.com/hexadecimal-floating-point-constants
  • @Mark Ransom:在 C99 中添加。 printf/scanf 也支持 %a 格式说明符。到目前为止,在 C 中指定浮点值的最佳方式。
【解决方案2】:

这不安全,但类似:

double a;
*(reinterpret_cast<uint64_t *>(&a)) = 0x3FEFFFFFFFFFFFFFL;

但是,这取决于系统上浮点数的特定字节顺序,所以不要这样做!

相反,只需将DBL_EPSILON 放入&lt;cfloat&gt;(或在另一个答案中指出,std::numeric_limits&lt;double&gt;::epsilon())就可以很好地使用。

【讨论】:

  • 将其视为整数应该使其与字节序无关(除非您拥有那些奇怪的“混合字节序”系统之一)
  • @Rick Regan:谁说你们平台的浮点类型表示的“字节顺序”与整数类型的表示是一致的?
  • 理论上你是对的——但是你有一个例子吗(除了混合端的“软浮点数”)?
【解决方案3】:
#include <iostream>
#include <iomanip>
#include <limits>
using namespace std;

int main()
{
    double const    x   = 1.0 - numeric_limits< double >::epsilon();

    cout
        << setprecision( numeric_limits< double >::digits10 + 1 ) << fixed << x
        << endl;
}

【讨论】:

    【解决方案4】:

    如果你制作一个bit_cast并使用fixed-width integer types,它可以安全地完成:

    template <typename R, typename T>
    R bit_cast(const T& pValue)
    {
        // static assert R and T are POD types
    
        // reinterpret_cast is implementation defined,
        // but likely does what you expect
        return reinterpret_cast<const R&>(pValue);
    }
    
    const uint64_t target = 0x3FEFFFFFFFFFFFFFL;
    double result = bit_cast<double>(target);
    

    虽然你可能只需要subtract epsilon from it

    【讨论】:

    • 不知道为什么你可以直接使用reinterpret_cast&lt;double&amp;&gt; 来定义bit_cast 的麻烦。似乎仍然是一个不错的解决方案。
    • @Mark:这不会静态断言这两种类型都是 POD 类型,并且更容易打破别名规则。 (诚​​然,我给出了一个比要求更通用的解决方案;在这种情况下,直接做就可以了。)
    【解决方案5】:

    这有点过时,但您可以使用union。 假设 long longdouble 在您的系统上都是 8 字节长:

    typedef union { long long a; double b } my_union;
    
    int main()
    {
        my_union c;
        c.b = 1.0;
        c.a--;
        std::cout << "Double value is " << c.b << std::endl;
        std::cout << "Long long value is " << c.a << std::endl;
    }
    

    这里不需要提前知道1.0的位表示是什么。

    【讨论】:

    • 严格来说,这导致了UB。
    【解决方案6】:

    0x1.fffffffffffffp-1 语法很棒,但仅限于 C99 或 C++17。

    但是有一个解决方法,没有(指针)转换,没有 UB/IB,只是简单的数学运算。

    double x = (double)0x1fffffffffffff / (1LL << 53);
    

    如果我需要一个 Pi,并且 Pi(double) 是十六进制的 0x1.921fb54442d18p1,只需写

    const double PI = (double)0x1921fb54442d18 / (1LL << 51);
    

    如果你的常数有大或小指数,你可以使用函数exp2代替移位,但exp2是C99/C++11 ...使用pow进行救援!

    【讨论】:

      【解决方案7】:

      最直接的解决方案是从math.h 中使用nextafter(),而不是所有的位杂耍。因此:

      #include <math.h>
      double a = nextafter(1.0, 0.0); 
      

      读作:1.0 之后的下一个浮点值,在0.0 的方向上;原始问题中“低于 1.0 的最接近的数字”的几乎直接编码。

      【讨论】:

        【解决方案8】:

        https://godbolt.org/z/MTY4v4exz

        typedef union { long long a; double b; } my_union;
        
        int main()
        {
            my_union c;
            c.b = 1.0;
            c.a--;
            std::cout << "Double value is " << c.b << std::endl;
            std::cout << "Long long value is " << c.a << std::endl;
        }
        

        【讨论】:

          猜你喜欢
          • 2013-09-27
          • 2018-04-21
          • 1970-01-01
          • 2012-06-10
          • 1970-01-01
          • 2021-05-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多