代码之家  ›  专栏  ›  技术社区  ›  scx

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

  •  3
  • scx  · 技术社区  · 7 年前

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

    由于IEEE的诡计,浮动类型不能递增( ++ )在他们的末端。见 this question 了解更多详细信息。答案是:

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

    但千万别提什么 n 是。

    迭代所有可能值从+0.0到+Infinity的解决方案如下: blog post . 这项技术包括使用一个带有 int 走不同的价值观 float . 这是因为文章中解释了以下属性,尽管它们只对正数有效。

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

    他的+0.0到+无穷大的解( 0.f 到 std::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 回复  |  直到 7 年前
        1
  •  4
  •   Johannes Overmann    7 年前

    通过使用32位的所有值,可以遍历所有32位表示法 unsigned int . 那你真的要走了 全部的 表示,正的和负的,包括空值(有两个)和所有 不是一个数字 陈述(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
  •  2
  •   scx    7 年前

    帕斯卡·库克正确指出 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
  •  1
  •   Eric Postpischil    7 年前

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

    (此代码假定浮点类型具有子法线,并且不会将其刷新为零。代码也可以随时调整以支持这些备选方案。)

    推荐文章