从最低点开始迭代所有可能的浮点值

时间:2019-02-09 20:30:21

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

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

由于IEEE的恶作剧,浮动类型无法在其末端增加(++)。有关更多详细信息,请参见this question。该答案指出:

  

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

但永远不会提及n的不足。

伟大的blog post提供了一种将所有可能值从+0.0迭代到+ infinity的解决方案。该技术涉及使用带有int的并集来遍历float的不同值。尽管这些属性仅对正数有效,但由于帖子中解释了以下属性,因此可以使用此功能。

  
      
  1. 相邻浮点数具有相邻的整数表示形式
  2.   
  3. 递增浮点数的整数表示将移动到下一个可表示的浮点数,从零开始移走
  4.   

他对于+0.0到+无限(从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问题?

我会接受任何形式的破解,因为这是一个单元测试。谢谢!

3 个答案:

答案 0 :(得分:4)

您可以使用32位unsigned int的所有值来遍历所有32位的表示形式。然后,您将真正遍历正负的所有所有表示形式,包括空值(有两个)和所有 not 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)。这没什么区别。

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

答案 2 :(得分:1)

可以通过简单地理解浮点表示来遍历所有float值:

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

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

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);
}

(此代码假定浮点类型具有次标准态,并且它们不会刷新为零。也可以很容易地对其进行调整以支持这些替代方式。)