Квадратный корень.
(c) Alone Coder
На эту тему я потратил лучшую неделю :)
Всё-таки расколоть безбашенный алгоритм
нахождения целой части квадратного корня,
где что-то сдвигается-вычитается-сдвигает-
ся-вычитается... и почему-то получается
правильный результат :)
Надеюсь, все видели компилятор MCODER2
или хотя бы что-то другое, где без коммен-
тариев приведён этот SQR? (А то вдруг я
так радуюсь зря, а читатели смотрят на эти
большие буквы большими глазами, прикиды-
вая, чего это я съел, что такой счастли-
вый? ;)
Вот процедура:
;HL=аргумент N
OR A
LD A,L
LD L,H
LD DE,64
LD H,D
LD B,8
sqr0 SBC HL,DE
JR NC,$+3
ADD HL,DE
CCF
RL D
ADD A,A
ADC HL,HL
ADD A,A
ADC HL,HL
DJNZ sqr0
;D=результат q=[√N].
;конец фильма. спасибо за внимание ;)
Вы поймёте процедуру в этом виде? Пра-
вильно,не поймёте.И я не пойму.И тем более
будете недоверчиво относиться к идее её
использования, особенно не проверив прави-
льность результат на всех числах от 0 до
32767. А увеличивать разрядность результа-
та вы тем более не возьмётесь, не зная,как
программа будет работать после этого.
Первый шаг.
Всем уважающим себя программистам изве-
стно,что квадрат натурального числа q мож-
но представить в виде суммы последователь-
ных нечётных чисел, от единицы до 2q-1.
Только извлекать через это квадратный
корень (методом вычитания возрастающих не-
чётных, пока они не перестанут вычитаться)
слишком, пожалуй, расточительно.
Читаем книжку.
Первый намёк я увидел здесь:
(Ж.Арсак "Программирование игр и головоло-
мок", дословно)
"Можно обобщить предыдущий алгоритм,ис-
пользуя свойства десятичной записи чисел.
Данное число разделяется на куски по две
цифры, начиная справа; затем мы начинаем
вычитать последовательные нечётные числа
из крайнего слева куска:
1909 1809 1509 1009
- 1 - 3 - 5 - 7
---- ---- ---- ----
1809 1509 1009 309
Если это нельзя продолжать дальше, то
последнее вычитаемое число увеличивается
на единицу,сдвигается на один шаг вправо,и
следом за ней приписывается единица. Это -
первое нечётное число, которое следует вы-
читать из предыдущего остатка.
В приведённом выше примере 7+1=8; при-
писывая 1, получаем 81 и продолжаем:
309 228 145
- 81 - 83 - 85
---- ---- ----
228 145 60
Поскольку продолжать дальше нельзя (по-
следнее возможное вычитание из остатка -
это крайнее справа), то последнее из вычи-
таемых чисел нужно увеличить на 1, а затем
разделить на 2, чтобы получить корень. По-
следний остаток и есть остаток квадратного
корня:
85+1=86, 86/2=43,
1909=(43)¤+60.
Этот алгоритм достаточно прост для про-
граммирования при длинных числах,и он даёт
вполне разумное время вычисления.
У вас много возможностей представлять
свои данные.Так как мы оперируем с кусками
из двух цифр, то вы можете задавать свои
данные таблицами целых чисел в интервале
от 0 до 99.
Вы можете представлять свои целые числа
как цепочки символов, где используются
только численные символы (цифры) от 0 до
9. Выбор способа зависит от ваших предпоч-
тений и от возможностей вашей машины опе-
рировать с таблицами и цепочками. Тщатель-
но рассмотрите, какие операции нужно сде-
лать. Вы ничем не ограничены: почему бы
не запрограммировать и сравнить два разных
решения?
Я предложил вам алгоритм без доказате-
льства. Поэтому постарайтесь его прове-
рить...
Я предложил вам алгоритм для десятичной
системы счисления. Можно предложить похо-
жий алгоритм для двоичной системы. Тогда
не возникает цикл вычитаний последователь-
ных нечётных чисел из каждого куска, пос-
кольку в куске только одно нечётное число:
1. Алгоритм упрощается: если можно вычесть
нечётное число - мы его вычитаем, в проти-
вном случае не делаем ничего. Затем сдви-
гаем, добавляем 1 и приписываем 1 в кон-
це... Этот алгоритм намного проще реализо-
вать. Но тогда нужно сначала перейти к ос-
нованию 2, а затем преобразовать двоичный
результат в десятичный. Вам следует посмо-
треть, что более эффективно..."
Несмотря на неудачный перевод и отсутс-
твие объяснений, "почему", то бишь, откуда
следует правильность алгоритма,всё же оче-
видно, что алгоритм - наш. Имеется в виду,
тот же,что реализован выше. То есть,теперь
мы получили его словесное описание в пере-
ложении на систему счисления с произволь-
ным основанием.
Но нам потребуется именно двоичный слу-
чай. (Невыгодность основания системы счис-
ления, большего 2, следует хотя бы из того
факта, что при бесконечном основании зада-
ча извлечения корня сводится опять-таки к
последовательному вычитанию нечётных чи-
сел.)
Объяснение.
В чём же суть метода?
Совершенно понятно,что первую слева ци-
фру результата мы можем получить всегда и
легко, используя всего лишь две левые циф-
ры исходного числа.
Метод состоит в том,что мы получаем ко-
рень "из первых четырёх цифр", используя
уже полученный корень "из первых двух
цифр" и так далее.
Перейдя сразу к двоичному случаю, мы
можем упростить понимание.
Представьте себе числовую ось, на кото-
рой помечены отрезки нечётных длин, увели-
чивающиеся слева направо, из каковых отре-
зков и "состоит" наше исходное число.Пусть
также число N не является точным квадра-
том, тогда оно окажется на оси правее ква-
драта корня q=[√N], отличаясь от него на
некоторый остаток квадратного корня.
Можно также обратить внимание на тот
факт, что последнее прибавленное нечётное
равно 2q-1, а следующее было бы 2q+1, но N
оказалось слишком мало для этого. Поэтому
очевидно, что остаток квадратного корня не
превышает 2q.
Пусть нам стало известно,что предыдущее
приближение корня (то есть целая часть ко-
рня из N/4 ) равно r, а нам нужно получить
ещё один бит результата, то есть выяснить,
чему равно q: 2r или же 2r+1.
Мы можем это узнать всего лишь одним
сравнением. Если N меньше, чем 4r¤+4r+1
(где 4r+1 - это "следующее прибавляемое
нечётное" к квадрату числа 2r, оно же "по-
следнее прибавляемое нечётное" к квадрату
числа 2r+1), то q=2r, иначе q=2r+1. Логич-
но?
Доказательство.
Особо головастых товарищей вполне уст-
роит нижеприведённое доказательство :)
Дано N Є 0
_
Найти q = [√N]
0 ___
┌ / N ┐
Определим q как │√ ─── │
i └ 4^i ┘
(квадратные скобки - взятие целой части,
знак ^ - возведение в степень)
Очевидно, такое q должно обладать свойст-
вами: i
N
(ё) q ¤ є ───
i 4^i
N
(ж) (q +1)¤ > ───
i 4^i
Обозначим через imax число пар бит в двои-
чном представлении N.
imax > log N
4
Опять-таки, очевидно, что
_____
┌ / N ┐
q = │√ ───── │ = 0
imax └ 4^imax┘
Начинаем проводить доказательство индукци-
ей по i. Пусть известно q (q известно)
i imax
Докажем, что q , получаемое по следующей
i-1
формуле, удовлетворяет свойствам (ё) и (ж)
(тогда q и будет решением).
0
i i-1
┌ 2q , если N-4 q ¤< (2(2q )+1)·4
│ i i i
q = ┤
i-1 │ 2q +1 в противном случае (...Є...)
└ i
Давайте запишем эти условия ещё раз в
следующем виде:
верхнее:
i 1 i
(ь) если N-4 q ¤< (q + ─)·4
i i 4
нижнее:
i 1 i
(ъ) если N-4 q ¤ Є (q + ─)·4
i i 4
Докажем верхний случай.
N
1. Докажем (ё) для q : q ¤є ───────
i-1 i-1 4^(i-1)
что, пользуясь q = 2q , можно записать в
виде: i-1 i
N N
4q ¤ є ───────, т.е. q ¤ є ───,
i 4^(i-1) i 4^i
что и известно по свойству (ё) для q .
i
2. Докажем (ж) для q , т.е.
i-1
N
(q +1)¤ > ───────, т.е.
i-1 4^(i-1)
N
q ¤+2q +1 > ───────, т.е. (пользуясь
i-1 i-1 4^(i-1)
выражением q = 2q ):
i-1 i
N
4q ¤+4q +1 > ───────. Умножим на 4^(i-1):
i i 4^(i-1)
i i i-1
4 q ¤+4 q +4 > N, т.е.:
i i
i i i-1
N-4 q ¤ < q ·4 +4
i i
что известно по условию (ь).
Теперь докажем нижний случай.
N
3. Докажем (ё) для q : q ¤є ───────
i-1 i-1 4^(i-1)
что, пользуясь q = 2q +1, можно записать
в виде: i-1 i
N
4q ¤+4q +1 є ───────. Умножим на 4^(i-1):
i i 4^(i-1)
i i i-1
N-4 q ¤ Є q ·4 +4
i i
что известно по условию (ъ).
4. Докажем (ж) для q , т.е.
i-1
N
(q +1)¤ > ───────, т.е. (пользуясь выра-
i-1 4^(i-1)
жением q = 2q +1 ):
i-1 i
N N
(2q +2)¤ > ───────, т.е. (q +1)¤ > ───,
i 4^(i-1) i 4^i
что опять-таки известно по свойству (ж)
для q .
i
На этом доказательство окончено. (Возг-
ласы: "Что-то ты не то доказал, дядя!! Где
НАШ-ТО алгоритм???" ;))
Начинаем упрощать!
i 1 i
┌ 2q , если N-4 q ¤< (q + ─)·4
│ i i i 4
q = ┤
i-1 │ 2q +1 в противном случае (...Є...)
└ i
i
Обозначим M =N-4 q ¤. В частности, M =N.
i i imax
Получаем уже две формулы:
1 i
┌ 2q , если M < (q + ─)·4
│ i i i 4
q = ┤
i-1 │ 2q +1 в противном случае (...Є...)
└ i
1 i
┌ M , если M < (q + ─)·4
│ i i i 4
M = ┤ 1 i
i-1 │ M -(q + ─)·4 в противном случае.
└ i i 4
Формулы для M доказываются просто.
верхняя:
i-1 i-1 i
M = N-4 q ¤ = N-4 (2q )¤ = N-4 q ¤
i-1 i-1 i i
что и является записью для M .
i
нижняя:
i-1 i-1
M = N-4 q ¤ = N-4 (2q +1)¤ =
i-1 i-1 i
i-1 i i i-1
= N-4 (4q ¤+4q +1) = N-4 q ¤-4 q -4 =
i i i i
i i-1 1 i
= M -4 q -4 = M -(q + ─)·4
i i i i 4
что и требовалось доказать.
1
Теперь обозначим R = q + ─. Тогда q =[R ].
i i 4 i i
1
И, в частности, R = ─.
imax 4
Получаем выражения:
1 i
┌ 2[R ]+ ─, если M < R ·4
│ i 4 i i
R = ┤ 5
i-1 │ 2[R ]+ ─, в противном случае.
└ i 4
i
┌ M , если M < R ·4
│ i i i
M = ┤ i
i-1 │ M -R ·4 , в противном случае.
└ i i
Теперь избавимся от операции возведения
i N
в степень. Обозначим NS = M /4 = ─── -q ¤,
i i 4^i i
N
в частности, NS = ──────.
imax 4^imax
NS в общем случае дробное, не более 2·imax
цифр после запятой.
Остаются две формулы,которыми уже можно
пользоваться:
1
┌ 2[R ]+ ─, если NS < R
│ i 4 i i
R = ┤ 5
i-1 │ 2[R ]+ ─, в противном случае.
└ i 4
┌ 4·NS , если NS < R
│ i i i
NS = ┤
i-1 │ 4(NS -R ), в противном случае.
└ i i
Верхнее выражение для NS выведено так:
i-1 i-1 i
NS = M /4 = M /4 = 4·M /4 = 4·NS .
i-1 i-1 i i i
Нижнее - так:
i-1 i i-1
NS = M /4 = (M -4 R )/4 =
i-1 i-1 i i
i-1
= M /4 -4·R = 4·NS -4·R = 4(NS -R ).
i i i i i i
Итак, последовательность вычислений та-
кая: присваиваем NS и R исходные значения
(NS = N и R = 1/4 соответственно);
imax imax
крутим цикл imax раз, каждый раз сравнивая
NS и R. Вычитаем,если вычитается. Сдвигаем
NS на 2 бита,а [R] на 1 бит влево. Дробная
часть R всегда равна 1/4. На каждом шаге
получаем очередной бит результата. Исполь-
зуются только операции сдвига и сложения/
вычитания.
После окончания вычислений результат
лежит в целой части R. В NS лежит остаток
квадратного корня.
Теперь выясним, какая разрядная сетка
нам необходима для вычислений. Выше уже
указывалось, что N имеет 2imax цифр, соот-
ветственно, NS имеет 2imax цифр после за-
пятой. А сколько до запятой? Оказывается,
всего лишь imax цифр. Дело в том, что если
бы целая часть NS (остатка квадратного ко-
рня) оказалась больше, то... В общем,двумя
главами выше вы уже узнали,что остаток ко-
рня не может превышать 2q, где q - сам ко-
рень.Иначе бы извлёкся корень не q, а q+1.
А разрядность корня у нас всего imax бит.
Соответственно, разрядность целой части NS
не превышает разрядности 2q, т.е. imax+1
бит. Причём за переполнением после оконча-
ния последнего прохода цикла можно не сле-
дить,а максимальное значение 2q до после-
i
днего прохода не превышает q, т.е. одним
битом NS можно пожертвовать. В нашем слу-
чае (если N имеет 16 бит), нужно 8 бит NS
в целой части и 16 в дробной. Для R нужно
8 бит в целой части и ещё 2 фиктивных би-
та, чтобы хранить дробную часть 1/4.
Если увеличить разрядную сетку, можно
продолжать вычисления и получать цифры ре-
зультата после запятой!
Надеюсь,я доступно изложил свои рассуж-
дения... Иначе просто не стоило старать-
ся... К слову, этот же алгоритм в десятич-
ном виде использовался на счётах,когда они
ещё были популярны :)
Как видно, сложность этого алгоритма не
выше, чем у операции деления, даже проще,
так как число проходов цикла вдвое меньше.
Существуют и другие методы извлечения
квадратного корня, точные и неточные. Их
описывали в своих статьях Cardinal/BDA,
Baze/3SC, Dark Raven/Magic org. Попробуйте
найти эти статьи. Например, интересно, что
если число хранится в логарифмированном
виде, его всего лишь надо разделить на два
:)