Floating point 双精度整数sqrt的精度

Floating point 双精度整数sqrt的精度,floating-point,square-root,Floating Point,Square Root,我想计算uint64\u t的整数部分。对于32位uint32\u t,通常建议首先将其强制转换为double,sqrt,然后将其强制转换回uint32\u t 鉴于double最多只能容纳2^53个数字,它是否也适用于uint64\u t?也就是说,下面的答案是否总是正确的: #include <math.h> uint64_t x = ...; uint64_t result = (uint64_t)sqrt((double)x); #包括 uint64_t x=。。。; ui

我想计算
uint64\u t
的整数部分。对于32位
uint32\u t
,通常建议首先将其强制转换为
double
sqrt
,然后将其强制转换回
uint32\u t

鉴于
double
最多只能容纳2^53个数字,它是否也适用于
uint64\u t
?也就是说,下面的答案是否总是正确的:

#include <math.h>
uint64_t x = ...;
uint64_t result = (uint64_t)sqrt((double)x);
#包括
uint64_t x=。。。;
uint64_t结果=(uint64_t)sqrt((双)x);
甚至:

#include <math.h>
uint64_t x = ...;
uint32_t result = (uint32_t)sqrt((double)x);
#包括
uint64_t x=。。。;
uint32_t结果=(uint32_t)sqrt((双)x);

根据经验,答案是否定的。输入4503599761588224的结果被错误地计算为67108865,而不是67108864

下面的代码识别这种情况。1当然,您可以删除
中断以观察其他情况

#包括
#包括
#包括
内部主(空){
对于(uint32_t y=1;y!=0;y++){
//*比完美的正方形还小*
uint64_t x=((uint64_t)y*(uint64_t)y)-1;
//我们期待最后的结果
uint32_t预期=y-1;
uint32_t结果=(uint32_t)sqrt((双)x);
如果(结果!=预期){
printf(“不正确:x=%llu,结果=%u\n”,x,结果);
打破
}
}
返回0;
}
值4503599761588224有什么特别之处?嗯,它正好是(226+1)2-1,也就是(252+227)。这可以用一个
double
来精确表示,因此错误不是由于
long
->
double
转换引起的

相反,该错误是
sqrt
实现的内部错误。这里的delta(与完美正方形相比)将平方根减小了2-27倍,这比
结果本身小了253倍。这是双精度所能处理的极限,因此我们自然希望在这一点上看到一个错误。2


一,


二,。@EricPostpischil在下面的评论中识别出了根本原因,这要归功于@EricPostpischil:)

当然,如果我们得到的数学库是好的,并且返回了一个正确的四舍五入平方根,那么我们知道结果很接近,并且可以用整数算法轻松地测试和纠正它。失败发生在2**26处的原因是sqrt(x)的导数是1/(2*sqrt(x))。因此,在(2**26)**2,减去1将平方根减少约2**-27。而平方根刚好低于2**26,因此减少约为平方根的2**-53倍,因此达到了双精度的边缘。即使数学库非常近似正确,也可以将平方根括起来并进行二进制搜索。@EricPostSchil-Nic我已经把这一点纳入了我的答案中(感谢你!)。即使对于
uint32\u t
,只有当您知道您的数学库的
sqrt
很好,并且您的C实现很好地处理浮点运算时,这才是可靠的。仅C标准并不要求这一点。有些数学库仅返回近似结果,即使是具有可表示的精确平方根的值。我编写了recoM正在对32位整数执行双精度策略,但这是对Java问题的回答。答案取决于Java特定的保证,不适用于C。