Какое сравнение с плавающей запятой является более точным и почему?
Я экспериментирую с различными реализациями метода Ньютона для вычисления квадратных корней. Одним из важных решений является прекращение работы алгоритма.
Очевидно, что он не будет использовать абсолютную разницу между y*y
и x
, где y
- текущая оценка квадратного корня из x
, потому что при больших значениях x
это может не можно представить свой квадратный корень с достаточной точностью.
Поэтому я должен использовать относительные критерии. Наивно я бы использовал что-то вроде этого:
static int sqrt_good_enough(float x, float y) {
return fabsf(y*y - x) / x < EPS;
}
И это работает очень хорошо. Но недавно я начал читать Kernighan и Plauger The Elements of Programming Style, и они дают программу Fortran для того же алгоритма в главе 1, критерии завершения которой переведены на C:
static int sqrt_good_enough(float x, float y) {
return fabsf(x/y - y) < EPS * y;
}
Оба являются математически эквивалентными, но есть ли причина для предпочтения одной формы над другой?
Ответы
Ответ 1
Эти два фактически не эквивалентны математически, если вы не написали fabsf (y * y - x)/(y * y) < EPS для первого. (извините за опечатку в моем оригинальном комментарии)
Но я думаю, что ключевым моментом является то, чтобы выражение соответствовало вашей формуле для вычисления y в итерации Newton. Например, если ваша y-формула y = (y + x/y)/2, вы должны использовать стиль Kernighan и Plauger. Если это y = (y * y + x)/(2 * y), вы должны использовать (y * y - x)/(y * y) EPS.
Обычно критерии завершения должны заключаться в том, что abs (y (n + 1) - y (n)) достаточно мала (т.е. меньше y (n + 1) * EPS). Вот почему два выражения должны совпадать. Если они не совпадают точно, возможно, что тест завершения решает, что остаток не является достаточно маленьким, а разница в y (n) меньше, чем ошибка с плавающей точкой, из-за различного масштабирования. Результатом будет бесконечный цикл, потому что y (n) перестает меняться и критерии завершения никогда не выполняются.
Например, следующий код Matlab - это точно такой же решатель Newton, что и ваш первый пример, но он работает вечно:
x = 6.800000000000002
yprev = 0
y = 2
while abs(y*y - x) > eps*abs(y*y)
yprev = y;
y = 0.5*(y + x/y);
end
Версия C/С++ имеет ту же проблему.
Ответ 2
Они все еще не эквивалентны; нижняя часть математически эквивалентна fabsf(y*y - x) / (y*y) < EPS
. Проблема, которую я вижу с вашей, заключается в том, что если y*y
переполняется (возможно, потому что x
есть FLT_MAX
и y
выбрано несчастливо), то завершение может никогда не произойти. В следующем взаимодействии используются двойники.
>>> import math
>>> x = (2.0 - 2.0 ** -52) * 2.0 ** 1023
>>> y = x / math.sqrt(x)
>>> y * y - x
inf
>>> y == 0.5 * (y + x / y)
True
EDIT: в качестве комментария (теперь удалено) указано, что также полезно обмениваться операциями между итерацией и тестом на завершение.
EDIT2: у обоих, вероятно, есть проблемы с субнормальным x
. специалисты нормализуют x
, чтобы избежать осложнений обеих крайностей.