sqrt, perfect squares and floating point errors

后端 未结 4 1011
逝去的感伤
逝去的感伤 2020-12-18 00:45

In the sqrt function of most languages (though here I\'m mostly interested in C and Haskell), are there any guarantees that the square root of a perfect square

4条回答
  •  隐瞒了意图╮
    2020-12-18 00:54

    In IEEE 754 floating-point, if the double-precision value x is the square of a nonnegative representable number y (i.e. y*y == x and the computation of y*y does not involve any rounding, overflow, or underflow), then sqrt(x) will return y.

    This is all because sqrt is required to be correctly-rounded by the IEEE 754 standard. That is, sqrt(x), for any x, will be the closest double to the actual square root of x. That sqrt works for perfect squares is a simple corollary of this fact.

    If you want to check whether a floating-point number is a perfect square, here's the simplest code I can think of:

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

    I need the empty asm volatile block that depends on dd because otherwise your compiler might be clever and "optimise" away the calculation of dd.

    I used a couple of weird functions from fenv.h, namely feclearexcept and fetestexcept. It's probably a good idea to look at their man pages.

    Another strategy that you might be able to make work is to compute the square root, check whether it has set bits in the low 26 bits of the mantissa, and complain if it does. I try this approach below.

    And I needed to check whether d is zero because otherwise it can return true for -0.0.

    EDIT: Eric Postpischil suggested that hacking around with the mantissa might be better. Given that the above issquare doesn't work in another popular compiler, clang, I tend to agree. I think the following code works:

    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;
      }
    }
    

    Adding and subtracting 67108864.0 from a has the effect of wiping the low 26 bits of the mantissa. We will get a back exactly when those bits were clear in the first place.

提交回复
热议问题