【问题标题】:Iterate though all possible floating-point values, starting from lowest遍历所有可能的浮点值,从最低值开始
【发布时间】:2019-07-03 18:14:12
【问题描述】:

我正在为数学函数编写单元测试,我希望能够“遍历”所有可能的浮点数/双精度数。

由于 IEEE 的恶作剧,浮点类型不能在其末端递增 (++)。有关详细信息,请参阅this question。该答案指出:

一个只能加2^(n-N)的倍数

但从未提及n 是什么小东西。

在这个伟大的blog post 中给出了将所有可能的值从 +0.0 迭代到 +infinity 的解决方案。该技术涉及使用带有int 的联合来遍历float 的不同值。这是由于帖子中解释了以下属性,尽管它们仅对正数有效。

  1. 相邻的浮点数具有相邻的整数表示
  2. 递增浮点数的整数表示会移动到下一个可表示的浮点数,远离零

他对 +0.0 到 +infinity 的解决方案(0.fstd::numeric_limits<float>::max()):

union Float_t {
    int32_t RawExponent() const { return (i >> 23) & 0xFF; }
    int32_t i;
    float f;
};

Float_t allFloats;
allFloats.f = 0.0f;
while (allFloats.RawExponent() < 255) {
    allFloats.i += 1;
}

-infinity 到 +0.0(std::numeric_limits&lt;float&gt;::lowest()0.f)是否有解决方案?

我已经测试了std::nextafter and std::nexttoward,但无法让它们工作。也许这是一个 MSVC 问题?

我可以接受任何形式的 hack,因为这是一个单元测试。谢谢!

【问题讨论】:

  • nextafter 是标准的高级方法,可以完全按照您的意愿行事。有什么不好的地方?
  • @PascalCuoq 它不会增加值。我将添加一个我正在做的示例,1 秒。
  • @PascalCuoq 你是对的,nextafter 工作......我想我在其他地方有一个错误。谢谢!请随意将您的评论作为答案,我会将其标记为正确答案。
  • 即使您可以遍历所有 64 位双精度值,您的程序也不会在您的生命周期内完成。
  • 更好的开始! XD 我想我会写一个随机模糊器。欢呼

标签: c++ math floating-point numeric floating-accuracy


【解决方案1】:

您可以通过使用 32 位 unsigned int 的所有值来遍历所有 32 位位表示。然后,您将真正了解 所有 表示,正面和负面,包括空值(有两个)和所有 非数字 表示 (NaN)。您可能希望也可能不希望过滤掉 NaN 表示,或者只过滤掉信号表示并保留非信号表示。这取决于您的用例。

例子:

for (uint32_t i = 0;;)
{
    float f;
    // Type punning: Force the bit representation of i into f.
    // Type punning is hard because mostly undefined in C/C++. 
    // Using memcpy() usually avoids any type punning warning.
    memcpy(&f, &i, sizeof(f));

    // Use f here.
    // Warning: Using signaling NaNs may throw exceptions or raise signals.

    i++;
    if (i == 0)
        break;
}

相反,您也可以将 32 位 int 从 -2**31 移动到 +(2**31-1)。这没什么区别。

【讨论】:

    【解决方案2】:

    Pascal Cuoq 正确指出 std::nextafter 是正确的解决方案。我在代码的其他地方遇到了问题。对不起,不必要的问题。

    #include <cassert>
    #include <cmath>
    #include <limits>
    
    float i = std::numeric_limits<float>::lowest();
    float hi = std::numeric_limits<float>::max();
    float new_i = std::nextafterf(i, hi);
    assert(i != new_i);
    
    double d = std::numeric_limits<double>::lowest();
    double hi_d = std::numeric_limits<double>::max();
    double new_d = std::nextafter(d, hi_d);
    assert(d != new_d);
    
    long double ld = std::numeric_limits<long double>::lowest();
    long double hi_ld = std::numeric_limits<long double>::max();
    long double new_ld = std::nextafterl(ld, hi_ld);
    assert(ld != new_ld);
    
    
    for (float d = std::numeric_limits<float>::lowest();
            d < std::numeric_limits<float>::max();
            d = std::nextafterf(
                    d, std::numeric_limits<float>::max())) {
        // Wait a lifetime?
    }
    

    【讨论】:

      【解决方案3】:

      通过对浮点表示的简单理解可以完成所有float 值的迭代:

      • 连续次正常值之间的距离是最小正常乘以“epsilon”。只需使用此距离作为增量迭代所有次法线。
      • 最低指数的正常值之间的距离是相同的。以相同的增量逐步遍历它们。
      • 对于每个指数,距离根据浮点基数增加。只需将增量乘以基数,然后逐步遍历下一个指数的所有值。
      • 重复直到达到无穷大。

      观察下面代码中的内部循环很简单:

      for (; x < Limit; x += Increment)
          Test(x);
      

      这样做的好处是只使用普通的浮点运算。内部循环仅包含一个加法和一个比较(加上您要对每个数字执行的任何测试)。在循环中不调用库函数,不剖析或复制到通用寄存器或以其他方式操作表示。没有什么可以阻碍性能。

      此代码仅遍历非负数。负数可以用同样的方法单独测试,也可以通过插入调用Test(-x)来共享这段代码。

      #include <limits>
      
      
      static void Test(float x)
      {
          //  Insert unit test for value x here.
      }
      
      
      int main(void)
      {
          typedef float T;
      
          static const int Radix = std::numeric_limits<T>::radix;
          static const T Infinity = std::numeric_limits<T>::infinity();
      
          /*  Increment is the current distance between floating-point numbers.  We
              start it at distance between subnormal numbers.
          */
          T Increment =
              std::numeric_limits<T>::min() * std::numeric_limits<T>::epsilon();
      
          /*  Limit is the next boundary where the distance between floating-point
              numbers changes.  We will increment up to that limit and then adjust
              the limit and increment.  We start it at the top of the first set of
              normals, which allows the first loop to increment first through the
              subnormals and then through the normals with the lowest exponent.
              (These two sets have the same step size between adjacent values.)
          */
          T Limit = std::numeric_limits<T>::min() * Radix;
      
          /*  Start with zero and continue until we reach infinity.
              We execute an inner loop that iterates through all the significands of
              one floating-point exponent.  Each time it completes, we step up the
              limit and increment.
          */
          for (T x = 0; x < Infinity; Limit *= Radix, Increment *= Radix)
      
              //  Increment x through all the significands with the current exponent.
              for (; x < Limit; x += Increment)
      
                  //  Test with the current value of x.
                  Test(x);
      
          //  Also test infinity.
          Test(Infinity);
      }
      

      (此代码假定浮点类型具有次正规,并且它们未刷新为零。代码也可以很容易地调整以支持这些替代方案。)

      【讨论】:

      • 这是一个了不起的答案。感谢您花时间记录所有细节,但该解决方案仅适用于 0 到 +infinity,而不适用于 -infinity 到 +infinity。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-14
      • 1970-01-01
      • 2017-11-05
      • 2019-07-31
      • 1970-01-01
      • 2015-10-02
      • 1970-01-01
      相关资源
      最近更新 更多