sqrt, perfect squares and floating point errors

后端 未结 4 1012
逝去的感伤
逝去的感伤 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.

    0 讨论(0)
  • 2020-12-18 00:54

    Instead of doing sqrt(81.0) == 9.0, try 9.0*9.0 == 81.0. This will always work as long as the square is within the limits of the floating point magnitude.

    Edit: I was probably unclear about what I meant by "floating point magnitude". What I mean is to keep the number within the range of integer values that can be held without precision loss, less than 2**53 for a IEEE double. I also expected that there would be a separate operation to make sure the square root was an integer.

    double root = floor(sqrt(x) + 0.5);  /* rounded result to nearest integer */
    if (root*root == x && x < 9007199254740992.0)
        /* it's a perfect square */
    
    0 讨论(0)
  • 2020-12-18 00:58

    According to this paper, which discusses proving the correctness of IEEE floating-point square root:

    The IEEE-754 Standard for Binary Floating-Point Arithmetic [1] requires that the result of a divide or square root operation be calculated as if in infinite precision, and then rounded to one of the two nearest floating-point numbers of the specified precision that surround the infinitely precise result

    Since a perfect square that can be represented exactly in floating-point is an integer and its square root is an integer that can be precisely represented, the square root of a perfect square should always be exactly correct.

    Of course, there's no guarantee that your code will execute with a conforming IEEE floating-point library.

    0 讨论(0)
  • 2020-12-18 01:15

    @tmyklebu perfectly answered the question. As a complement, let's see a possibly less efficient alternative for testing perfect square of fractions without asm directive.

    Let's suppose we have an IEEE 754 compliant sqrt which rounds the result correctly.
    Let's suppose exceptional values (Inf/Nan) and zeros (+/-) are already handled.
    Let's decompose sqrt(x) into I*2^m where I is an odd integer.
    And where I spans n bits: 1+2^(n-1) <= I < 2^n.

    If n > 1+floor(p/2) where p is floating point precision (e.g. p=53 and n>27 in double precision)
    Then 2^(2n-2) < I^2 < 2^2n.
    As I is odd, I^2 is odd too and thus spans over > p bits.
    Thus I is not the exact square root of any representable floating point with this precision.

    But given I^2<2^p, could we say that x was a perfect square?
    The answer is obviously no. A taylor expansion would give

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

    Thus, for e=ulp(I^2) up to sqrt(ulp(I^2)) the square root is correctly rounded to rsqrt(I^2+e)=I... (round to nearest even or truncate or floor mode).

    Thus we would have to assert that sqrt(x)*sqrt(x) == x.
    But above test is not sufficient, for example, assuming IEEE 754 double precision, sqrt(1.0e200)*sqrt(1.0e200)=1.0e200, where 1.0e200 is exactly 99999999999999996973312221251036165947450327545502362648241750950346848435554075534196338404706251868027512415973882408182135734368278484639385041047239877871023591066789981811181813306167128854888448 whose first prime factor is 2^613, hardly a perfect square of any fraction...

    So we can combine both tests:

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

    EDIT: If we want to restrict to the case of integers, we can also check that floor(sqrt(x))==sqrt(x) or use dirty bit hacks in squared_significand_fits_in_precision...

    0 讨论(0)
提交回复
热议问题