从最低值开始遍历所有可能的浮点值

Iterate though all possible floating-point values, starting from lowest

我正在为数学函数编写单元测试,我希望能够"walk"所有可能的floats/doubles。

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

one can only add multiples of 2^(n-N)

但从未提及 n 是什么。

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

  1. Adjacent floats have adjacent integer representations
  2. Incrementing the integer representation of a float moves to the next representable float, moving away from zero

他对 +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<float>::lowest()0.f)的解决方案?

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

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

您可以使用 32 位 unsigned int 的所有值遍历所有 32 位位表示。然后你会真正地走 all 表示,正的和负的,包括两个空值(有两个)以及所有 not a number 表示(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)。这没有区别。

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?
}

迭代所有 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);
}

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