我必须列举一个等式的解,我知道y < x *( sqrt(n) - 1 )
,其中 x , y 和 n 是整数。
我天真的做法是寻找 y 小于或等于floor( x * ( sqrt( (float)n ) - 1 ) )
。
我应该担心近似误差吗?
例如,如果我的表达式有点大于整数 m ,我应该担心最后会得到 m-1 吗? / p>
我怎么能发现这样的错误?
答案 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 ]是整数平方根。