sqrt, идеальные квадраты и ошибки с плавающей запятой

на sqrt функция большинства языков (хотя здесь меня больше всего интересует C и Haskell), есть ли какие-либо гарантии, что квадратный корень идеального квадрата будет возвращен точно? Например, если я сделаю sqrt(81.0) == 9.0, это безопасно или есть шанс, что sqrt вернет 8.99999999998 или 9.00000003?

Если числовая точность не гарантируется, каков был бы предпочтительный способ проверить, что число является идеальным квадратом? Возьмите квадратный корень, получите пол и потолок и убедиться, что они вернутся к исходному номеру?

спасибо!

4 ответов


в IEEE 754 с плавающей запятой, если значение двойной точности x является квадратом неотрицательного представимого числа y (т. е. y*y == x и вычисление y*y не включает округление, переполнение или утечку), то sqrt (x) вернет y.

это все потому, что sqrt должен быть правильно округлен стандартом IEEE 754. То есть sqrt (x), для любой 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, а именно feclearexcept и fetestexcept. Вероятно, это хорошая идея, чтобы посмотреть их man страницы.

еще одна стратегия, которую вы могли бы сделать, это вычислить квадратный корень, проверить, установил ли он биты в низких 26 битах мантиссы, и пожаловаться, если это так. Я попробую этот подход ниже.

и мне нужно было проверить, есть ли d равна нулю, потому что в противном случае он может возвратить true на -0.0.

редактировать: Эрик Постпишил предположил, что хакерство с мантиссой может быть лучше. С учетом что выше 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 назад, когда эти биты были ясны в первую очередь.


по данным этой статье, в котором обсуждается доказательство правильности квадратного корня с плавающей запятой IEEE:

стандарт IEEE-754 для двоичной плавающей точки Арифметика [1] требует, чтобы результат деления или квадрата корневая операция рассчитывается с бесконечной точностью, и затем округляется до одной из двух ближайших плавающих точек номера заданной точности, которые окружают бесконечно точный результат

поскольку идеальный квадрат, который может быть представлен точно в с плавающей запятой, является целым числом, а его квадратный корень-целым числом, которое может быть точно представлено, квадратный корень идеального квадрата всегда должен быть точно правильным.

конечно, нет никакой гарантии, что ваш код будет выполняться с соответствующей библиотекой с плавающей запятой IEEE.


@tmyklebu отлично ответил на вопрос. В качестве дополнения давайте рассмотрим, возможно, менее эффективную альтернативу для тестирования идеального квадрата фракций без директивы asm.

предположим, что у нас есть IEEE 754 совместимый sqrt, который округляет результат правильно.
Предположим, что исключительные значения (Inf / Nan) и нули ( + / - ) уже обработаны.
Давайте разложим sqrt(x) на I*2^m здесь I - нечетное число.
И где!--4--> пядей 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.
As I нечетное, I^2 тоже нечетно и, таким образом, охватывает > P бит.
Таким образом I не является точным квадратным корнем любой представимой плавающей точки с такой точностью.

но учитывая I^2<2^p, можно сказать, что x был идеальный квадрат?
Ответ очевиден-нет. Расширение Тейлора дай!--23-->

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, почти идеальный квадрат любой фракции...

таким образом, мы можем объединить оба теста:

#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: Если мы хотим ограничить случай целых чисел, мы также можем проверить, что floor(sqrt(x))==sqrt(x) или использовать грязный хаки в squared_significand_fits_in_precision...


вместо sqrt(81.0) == 9.0 попробуй 9.0*9.0 == 81.0. Это всегда будет работать до тех пор, пока квадрат находится в пределах величины с плавающей запятой.

Edit: мне, вероятно, было неясно, что я имел в виду под"величиной с плавающей точкой". Я имею в виду, чтобы сохранить число в диапазоне целочисленных значений, которые могут храниться без потери точности, менее 2**53 для двойника IEEE. Я также ожидал, что будет отдельная операция, чтобы убедиться, что квадратный корень был целое число.

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