sqrt,完美正方形和浮点错误

时间:2013-01-04 05:45:56

标签: c haskell floating-point

在大多数语言的sqrt函数中(虽然这里我主要对C和Haskell感兴趣),是否有任何保证完美正方形的平方根将被准确返回?例如,如果我sqrt(81.0) == 9.0,那是否安全,或sqrt是否有可能返回8.999999998或9.00000003?

如果无法保证数值精度,那么检查数字是否为完美正方形的首选方法是什么?取平方根,拿地板和天花板,确保它们回到原始数字?

谢谢!

4 个答案:

答案 0 :(得分:12)

在IEEE 754浮点数中,如果双精度值x是非负表示数y的平方(即y * y == x且y * y的计算不涉及任何舍入,溢出或下溢),然后sqrt(x)将返回y。

这都是因为要求sqrt通过IEEE 754标准正确舍入。也就是说,对于任何 x,sqrt(x)将是与x的实际平方根最接近的两倍。那个sqrt适用于完美的正方形是这个事实的简单推论。

如果你想检查一个浮点数是否是一个完美的正方形,这是我能想到的最简单的代码:

int issquare(double d) {
  if (signbit(d)) return false;
  feclearexcept(FE_INEXACT);
  double dd = sqrt(d);
  asm volatile("" : "+x"(dd));
  return !fetestexcept(FE_INEXACT);
}

我需要依赖于asm volatile的空dd块,否则您的编译器可能会很聪明并“优化”dd的计算。

我使用了fenv.h中的几个奇怪的函数,即feclearexceptfetestexcept。查看他们的man页面可能是个好主意。

你可能能够做的另一个策略是计算平方根,检查它是否在尾数的低26位中设置了位,并且如果有则抱怨。我在下面尝试这种方法。

我需要检查d是否为零,否则可以true返回-0.0

编辑:Eric Postpischil建议用尾数进行黑客攻击可能会更好。鉴于上述issquare在另一个流行的编译器clang中不起作用,我倾向于同意。我认为以下代码有效:

int _issquare2(double d) {
  if (signbit(d)) return 0;
  int foo;
  double s = sqrt(d);
  double a = frexp(s, &foo);
  frexp(d, &foo);
  if (foo & 1) {
    return (a + 33554432.0) - 33554432.0 == a && s*s == d;
  } else {
    return (a + 67108864.0) - 67108864.0 == a;
  }
}

67108864.0添加和减去a具有擦除尾数的低26位的效果。当这些位首先清楚时,我们会准确地回到a

答案 1 :(得分:6)

根据this paper,它讨论了证明IEEE浮点平方根的正确性:

  

IEEE-754二进制浮点标准   算术[1]需要除法或平方的结果   root操作被计算为无限精度,和   然后四舍五入到最近的两个浮点之一   周围指定精度的数字   无限精确的结果

由于可以精确表示为浮点的完美平方是一个整数,其平方根是一个可以精确表示的整数,因此完美平方的平方根应始终完全正确。

当然,无法保证您的代码将使用符合IEEE的浮点库执行。

答案 2 :(得分:1)

@tmyklebu完美地回答了这个问题。作为补充,让我们看看一个可能效率较低的替代品,用于在没有asm指令的情况下测试完美的分数平方。

假设我们有一个符合IEEE 754标准的sqrt,它可以正确地舍入结果 假设已经处理了异常值(Inf / Nan)和零(+/-) 让我们将sqrt(x)分解为I*2^m,其中I是一个奇整数 I跨越n位:1+2^(n-1) <= I < 2^n

如果n > 1+floor(p/2)其中p是浮点精度(例如p = 53且n> 27,则为双精度)
然后2^(2n-2) < I^2 < 2^2n
由于I是奇数,I^2也是奇数,因此跨越&gt; p位。
因此I不是具有此精度的任何可表示浮点的精确平方根。

但鉴于I^2<2^p,我们可以说x是一个完美的广场吗? 答案显然是否定的。泰勒的扩张会给予

sqrt(I^2+e)=I*(1+e/2I - e^2/4I^2 + O(e^3/I^3))

因此,对于e=ulp(I^2)最多sqrt(ulp(I^2)),平方根正确舍入为rsqrt(I^2+e)=I ...(舍入到最近的偶数或截断或平面模式)。

因此,我们必须断言sqrt(x)*sqrt(x) == x 但上述试验是不充分的,例如,假设IEEE 754双精度,sqrt(1.0e200)*sqrt(1.0e200)=1.0e200,其中1.0e200正是99999999999999996973312221251036165947450327545502362648241750950346848435554075534196338404706251868027512415973882408182135734368278484639385041047239877871023591066789981811181813306167128854888448其第一素因数为2^613,几乎没有任何部分的一个完美的正方形... < / p>

所以我们可以结合两种测试:

#include <float.h>
bool is_perfect_square(double x) {
    return sqrt(x)*sqrt(x) == x
        && squared_significand_fits_in_precision(sqrt(x));
}
bool squared_significand_fits_in_precision(double x) {
    double scaled=scalb( x , DBL_MANT_DIG/2-ilogb(x));
    return scaled == floor(scaled)
        && (scalb(scaled,-1)==floor(scalb(scaled,-1)) /* scaled is even */
            || scaled < scalb( sqrt((double) FLT_RADIX) , DBL_MANT_DIG/2 + 1));
}

修改 如果我们想限制整数的情况,我们也可以检查floor(sqrt(x))==sqrt(x)或使用sqared_significand_fits_in_precision中的脏位黑客......

答案 3 :(得分:0)

而不是sqrt(81.0) == 9.0,请尝试9.0*9.0 == 81.0。只要平方在浮点幅度范围内,这将始终有效。

编辑:我可能不清楚“浮点数”的含义。我的意思是将数字保持在可以保持而没有精度损失的整数值范围内,对于IEEE双精度小于2 ** 53。我还预计会有一个单独的操作来确保平方根是一个整数。

double root = floor(sqrt(x) + 0.5);  /* rounded result to nearest integer */
if (root*root == x && x < 9007199254740992.0)
    /* it's a perfect square */