Квадратный корень с вавилонским методом или методом Герона?

Я реализую в java метод Babylonian/Heron для получения квадратного корня числа на основе Информация из Википедии

В настоящее время у меня есть:

public static void main(String[] args) {
    System.out.println(Sqrt.sqrt1(8));
    //System.out.println(Sqrt.sqrt2(8));   //Infinite loop
    System.out.println(Sqrt.sqrt3(8));
    System.out.println(Sqrt.sqrt4(8));
}

static float sqrt1(float x) {
    float b = 0, h = x;

    while (b != h) {
        b = (h + b) / 2;
        h = x / b;
    }
    return b;
}

static double sqrt2(double x) {
    double b = x, h = 0;

    while (b != h) {
        b = (h + b) / 2;
        h = x / b;
    }
    return b;
}

static double sqrt3(double x) {
    double b = x, h = 0;

    while (Math.abs(b - h) > 0.000000000001) {
        b = (h + b) / 2;
        h = x / b;
    }
    return b;
}

static double sqrt4(double x) {
    double r = x, t = 0;

    while (t != r) {
        t = r;
        r = (x / r + r) / 2;
    }
    return r;
}

Результат будет:

2.828427

2.82842712474619

2.82842712474619

Метод sqrt2 будет зацикливаться бесконечно, но это происходит только с числами типа double, потому что метод sqrt1 отлично работает с числами с плавающей запятой. Я не знаю, почему это. Таким образом, метод sqrt3 выглядит как способ, если я хочу работать с двойными значениями.

Я немного запутался в том, какие методы я реализую. «Вавилонский метод такой же, как метод Герона?».

Насколько я понимаю, вавилонский метод основан на том факте, что сторона квадрата представляет собой квадратный корень из площади квадрата (x). Таким образом, вы можете начать с прямоугольника с размерами b.h, получить среднее значение двух сторон (b=b+h/2), а затем рассмотреть этот результат как сторону меньшего прямоугольника и, конечно же, получить другую сторону (h=x /б). Прямоугольник начнет приближаться к нужному квадрату. Вот что я сделал в методах sqrt1, sqrt2 и sqrt3:

while (b != h) {
    b = (h + b) / 2;
    h = x / b;
}

С другой стороны, ссылка в Википедии говорит, что метод вавилонян / цапель такой же, и описывает его как:

«Основная идея заключается в том, что если x является завышенной оценкой квадратного корня из неотрицательного действительного числа S, то S/x будет заниженной оценкой, и поэтому можно разумно ожидать, что среднее значение этих двух чисел обеспечит лучшее приближение».

Вы можете увидеть эту реализацию в методе sqrt4:

    while (t != r) {  
        t = r;  
        r = (x/r + r) / 2;  
    }  

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

Что-то интересное, что я не могу сделать: while (b != h) { когда b и h удваиваются, как показано в методе sqrt2, потому что он будет зацикливаться навсегда. Вместо этого я использовал while (Math.abs(b - h) > 0.000000000001) {

Однако я могу сделать: while (t != r) { с b и h удваивается, как показано в sqrt4.

Я был бы признателен, если бы кто-нибудь объяснил это поведение.

----------- РЕДАКТИРОВАТЬ:---------------------------------- --------------------------

Как и предполагалось, оба алгоритма одинаковы, но реализованы по-разному. Однако следующие предлагаемые циклы кода всегда повторяются, как sqrt2 с x = 8:

static double sqrt5(double x) {
    double b = x;

    while (b != x/b) {
        b = (x / b + b) / 2;
    }
    return b;
}

Итак... в чем отличие sqrt4 от других реализаций? Почему он не зацикливается навсегда, как другие?


person Wyvern666    schedule 13.03.2013    source источник


Ответы (3)


Обновление: sqrt4 имеет больше шансов в конечном итоге выйти из цикла, чем sqrt5, потому что его условие остановки сравнивает приближение одной итерации с приближением предыдущей итерации.

Вычисление имеет тенденцию уменьшать ошибку, так что в конечном итоге значение, вычисленное для b, настолько близко к точному квадратному корню, что x/b отличается от b только последним битом. В этот момент значение, вычисленное для (x / b + b) / 2 с доступной конечной точностью, будет равно b, и итерация остановится.

Например, если мы вычисляем sqrt(2) и достигли приближения b = 1,414213562373095, мы имеем:

>>> b
1.414213562373095
>>> 2/b                     # Close but not quite equal to b, 
1.4142135623730951          # iteration in sqrt5 continues
>>> (2/b + b)/2        
1.414213562373095           # Exactly equal to b, sqrt4 stops

Как вы можете видеть, как только b достигнет 1,414213562373095, его значение не изменится при вычислении в цикле. Поскольку b и 2/b все еще различаются последней цифрой, sqrt5 никогда не выйдет.


Вавилонский метод и метод Герона представляют собой один и тот же алгоритм, и он совпадает с методом Ньютона-Рапсона для решения x²=a. Разница между различными реализациями, которые у вас есть, заключается в условии остановки. Например:

while (b != h) {
    b = (h + b) / 2;
    h = x / b;
}

такой же как:

while (b != x/b) {
    b = (x / b + b) / 2;
}

Конечно, b != x/b не очень хорошее условие остановки, потому что b может никогда не стать математически точным квадратным корнем из x: если b не является точным квадратным корнем, x/b не равно b. Например, в Питоне:

>>> sqrt(2)
1.4142135623730951
>>> 2/sqrt(2)
1.4142135623730950

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

eps = 1e-6;
while (abs(b-h)/b > eps) { ...
person Joni    schedule 13.03.2013
comment
Спасибо, хороший ответ. Вы думаете, что sqrt4 должен ограничивать относительную разницу или все в порядке? - person Wyvern666; 14.03.2013
comment
Сложно сказать. Было бы безопаснее с границей. Если деление правильно округлено, метод может сходиться для всех входных данных, но для его гарантии требуется более глубокий анализ. - person Joni; 14.03.2013

Из-за ошибок округления вы никогда не должны полагаться на равенство значений типа double или float. Вам нужен другой метод для определения того, когда ваше приближение будет достаточно хорошим.

Хорошим началом является распечатка приближения в каждом цикле, чтобы вы видели, как это происходит. Может случиться так, что разница между двумя последовательными приближениями становится все меньше и меньше, а затем внезапно становится хаотичной. Затем вы знаете, что зашли слишком далеко, и возвращаетесь к последней аппроксимации, где разница с предыдущей была меньше, чем предыдущая разница.

Обязательно используйте тестовые примеры, которые дадут значение, которое никогда не может быть представлено в двойном значении, например, sqrt(2). Обязательно тестируйте примеры, которые должны давать число, которое можно представить. Наконец, убедитесь, что ваша функция будет возвращать целочисленное значение, когда берется квадратный корень из квадрата.

Для различий с float и double: поскольку float имеют меньшую точность, вы достигаете точки, в которой разница слишком мала, и из-за ошибок округления результат равен 0. Но это просто удача, и может быть по-разному с разными входными данными. Однако чем меньше у вас битов в мантиссе, тем больше вероятность, что это так.

person Ingo    schedule 13.03.2013
comment
Что ж, в sqrt3 я заменил while (b != h) на что-то более разумное, как мне кажется: while (Math.abs(b - h) > 0.000000000001) , и, похоже, работает нормально. Вы правы в сравнении поплавков или двойников, например циклов sqrt1 (99) и sqrt2 (99) навсегда. Но я хочу знать, почему sqrt4, кажется, работает нормально, сравнивая таким же образом... - person Wyvern666; 13.03.2013
comment
@ Wyvern666 относительно sqrt4: вы уверены, что это никогда не зациклится? Сколько чисел вы проверили? Я уверен, что все, что у вас есть, это анекдотические доказательства: они не повторялись в нескольких проведенных вами тестах. - person Ingo; 13.03.2013
comment
Я попробовал for (int i = 0; i <= 1000 ; i++) и несколько десятичных знаков. Другой постер сказал, что это тот же алгоритм, но реализованный по-другому, и я вижу, что они не работают одинаково, последний, кажется, лучше, я думаю. - person Wyvern666; 13.03.2013

Как писали другие, вавилонский метод и метод Герона — это одно и то же. Его изобрели вавилоняне, но Герон был первым, кто записал его несколько сотен лет спустя, так что ему досталась заслуга. Иногда жизнь несправедлива.

Я также согласен с комментариями о точности и ошибках округления. Но у меня есть другое решение, чтобы предложить. Нормируйте число x, для которого ищется квадратный корень, в диапазоне 1 ‹= x ‹ 4, постоянно умножая или деля на 4, пока оно не окажется в диапазоне. Затем «разверните цикл», выполняющий вычисления; поскольку x находится в известном диапазоне, количество циклов можно рассчитать заранее, без необходимости выполнять вычисления, чтобы определить, когда завершить цикл. Наконец, умножьте или разделите на 2 столько раз, сколько вы делили или умножали на 4 изначально. Вот код:

function sqrt(n)
    if n < 1
        return sqrt(n * 4) / 2
    if 4 <= n
        return sqrt(n / 4) * 2
    x := (n + 1) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    x := (x + n / x) / 2
    return x

Я оставлю вам перевод приведенного выше псевдокода на Java. Для двойки достаточно пяти разворотов петли. Поскольку каждый проход цикла удваивает количество цифр точности, для чисел с плавающей запятой достаточно четырех развертываний цикла.

Я обсуждаю метод Герона в моем блоге.

person user448810    schedule 13.03.2013
comment
В своем блоге вы описываете алгоритм Герона следующим образом: если xk является хорошим приближением к √x, то среднее значение xk и x/xk является лучшим приближением; очень похоже на то же описание, которое я вставляю из википедии. Но вавилонский алгоритм - это не более геометрический подход? Я имею в виду последовательное преобразование прямоугольника в квадрат. Может быть, у меня слабая математика, и я не вижу двух одинаковых вещей, но я мог бы сказать, что у Герона производная от другой или что-то в этом роде. - person Wyvern666; 14.03.2013
comment
Более того, в испанской версии Методов вычисления квадратных корней в Википедии [ссылка]es.wikipedia.org/wiki/ в нем не упоминаются цапли, и объяснение не похоже на англоязычную версию того же метода. Вот почему я придумал две реализации, которые, кажется, генерируют одинаковое количество итераций, но имеют разные условия остановки, хотя я могу использовать одно и то же условие остановки для обоих методов. - person Wyvern666; 14.03.2013
comment
Я не историк математики, но могу сказать вам, что вавилоняне (а также греки) мыслили геометрически, потому что алгебра, необходимая им для описания их вычислений, не будет изобретена еще две тысячи лет. Я видел методы Babylonian и Heron, описанные как идентичные в нескольких источниках, которым я доверяю, поэтому я подозреваю, что любые различия в ваших реализациях связаны с небольшими различиями в описаниях алгоритмов в ваших различных источниках. Вероятно, это потому, что вавилоняне не оставили письменного описания, поэтому все, что мы знаем об их алгоритме, выведено. - person user448810; 14.03.2013