使用sqrt和floor时的逼近错误

时间:2015-03-11 17:45:27

标签: c++ numerical approximation

我必须列举一个等式的解,我知道y < x *( sqrt(n) - 1 ),其中 x y n 是整数。

我天真的做法是寻找 y 小于或等于floor( x * ( sqrt( (float)n ) - 1 ) )


  • 我应该担心近似误差吗?

  • 例如,如果我的表达式有点大于整数 m ,我应该担心最后会得到 m-1 吗? / p>

  • 我怎么能发现这样的错误?

1 个答案:

答案 0 :(得分:3)

你绝对应该担心近似误差,但是担心的程度取决于你所关注的 x n 的值范围。

IEEE 4字节浮点表示中的计算大致在2 ^ 23到2 ^ 24的一个部分的顺序上有错误;对于8字节的表示(即,double),它将大致是2 ^ 52到2 ^ 53中的一部分。您可能希望使用double而不是float来获得32位整数 x n ,即使double也不足以支持64位整数。

作为示例,请考虑代码:

template <typename F,typename V>
F approxub(V x,V n) {
    return std::floor(x*std::sqrt(F(n))-x);
}

uint64_t n=1000000002000000000ull; // (10^9 + 1)^2 - 1
uint64_t x=3;
uint64_t y=approxub<double>(x,n);

这给出的值 y = 3000000000,但正确的值是2999999999。

x 很大且 n 很小时更糟糕的是:大的64位整数在IEEE double中无法准确表示:

uint64_t n=9;
uint64_t x=5000000000000001111; // 5e18 + 1111
uint64_t y=approxlb<double>(x,n);

y的正确值(将 n 是一个完美的正方形的问题放在一边 - 在这种情况下,真正的上限将减少一个)是2 x = 10000000000000002222,即1e19 + 2222.计算出的 y 是10000000000000004096。

避免浮点近似值

假设你有一个函数isqrt,它精确地计算了整数平方根的整数部分。然后你可以说

y = isqrt(x*x*n) - x

如果产品x*x*n适合你的整数类型,那么你将有一个精确的上限(或者如果 n 是一个完美的正方形,则会比上限多一个。)编写isqrt函数的方法不止一种;这是基于material at code codex

的示例实现
template <typename V>
V isqrt(V v) {
    if (v<0) return 0;

    typedef typename std::make_unsigned<V>::type U;
    U u=v,r=0;

    constexpr int ubits=std::numeric_limits<U>::digits;
    U place=U(1)<<(2*((ubits-1)/2));

    while (place>u) place/=4;
    while (place) {
        if (u>=r+place) {
            u-=r+place;
            r+=2*place;
        }
        r/=2;
        place/=4;
    }
    return (V)r;
}

如果 x 太大,该怎么办?例如,如果我们最大的整数类型具有64位,并且 x 大于2 ^ 32。最直接的解决方案是进行二进制搜索,将 xr - x xr 作为边界,其中 r = [√ n ]是整数平方根。