27.01.2007, 02:44
общий
это ответ
Здравствуйте, Mystic!
дада, ни float, ни double, ни даже extended тебе тут не помогут :)
придется реализовывать свою арифметику (это по-любому). Я хочу предложить тебе другой вариант вычисления корня. Дело в том, что деление реализовывать весьма трудно (намного труднее умножения!) в то время как умножение при умелом подходе можно заменить сложением. Короче, двоичный алгоритм вычисления квадратного корня такой:
пусть есть число А = 2^n, удовлетворяющее следующим условиям:
1. A*A < 2 (т.е. А не превосходит корня из двух)
2. (А*2)*(А*2) > 2 (т.е. если мы возьмем А = 2^(n + 1), то это число уже будет превосходить корень из двух).
Тогда можно смело сказать, что А – это первое двоичное приближение корня из двух. Очевидно, что А = 1, т.к. 1*1 = 1, а 2*2 = 4. Т.е. первое двоичное приближение корня из двух – это единица. Теперь непосредственно алгоритм.
Пусть n – это номер шага, а s – приблизительное значение корня из 2 на этом шаге. Тогда на нулевом (начальном шаге) n = 0, s [0] = 1 = 2^0. На каждом следующем шаге алгоритма мы будем определять один следующий бит результата. Т.е. на первом шаге мы определим бит 2^(-1), на втором – 2^(-2) и т.п. Правило определения бита такое: если (s [n – 1] + 2^(-n) )^2 > 2) то s [n] = s [n – 1], иначе s [n] = s [n – 1] + 2^(-n). Т.е. мы временно присваиваем тестируемому биту единицу, возводим получившееся число в квадрат и сравниваем с двойкой. Если результат оказался больше 2, то тестируемый бит никак не может быть единицей, т.к. результат уже превосходит 2. Если же результат оказывается меньше 2, то тестируемый бит должен быть единицей, т.к. его еще не хватает до получения 2. Таким образом, нам всего лишь нужна одна операция умножения на один бит результата.
Но даже это много, когда мы говорим о числе с миллионом разрядов! Значит, от операции умножения придется уйти. В данной ситуации это достаточно просто. Пусть у нас есть s [n - 1] – это приблизительное значение корня из 2-х на (n - 1)-м шаге. Значит на n-м шаге мы определяем бит 2^(-n). Т.е. нам надо посчитать (s [n - 1] + 2^(-n))^2 и сравнить его с двумя. Разложим квадрат:
(s [n - 1] + 2^(-n))^2 = (s [n - 1])^2 + 2*(s [n - 1])*2^(-n) + 2^(-2*n) = (s [n - 1])^2 + (s [n - 1])*2^(-n + 1) + 2^(-2*n)
А теперь допустим, что значение (s [n - 1])^2 у нас тоже есть (обозначим его S [n – 1]). Тогда что остается? Взять s [n – 1], сдвинуть его на (n – 1) разрядов вправо (умножение на 2^m эквивалентно сдвигу на m разрядов вправо), добавить к нему S [n – 1] (это операция сложения) и установить бит 2^(-2*n) (этот бит будет находиться далеко «вправо» от общего результата, поэтому можно смело быть уверенным, что его значение всегда изначально равно нулю, поэтому его можно просто устанавливать). Полученное число и будет (s [n - 1] + 2^(-n))^2.
Теперь его необходимо сравнить с 2-й – ну это очень просто – проверяем бит 2^1 = 2, если его значение равно единице, значит установка бита 2^(-n) искомого корня приводит к превышению произведением двух, т.е. его значение должно быть нулем. Это означает, что s [n] = s [n – 1], а S [n] = S [n – 1] соответственно. В противном же случае, s [n] = s [n – 1] + 2^(-n + 1), а S [n] равняется только что полученному произведению. Это дает возможность без дополнительных вычислений перейти к следующему шагу.
Пример вычисления по шагам.
0. n = 0, s [0] = 1.0000, S [0] = 1.0000 – начальные значения
1. n = 1, тестируемый бит 2^(-1), тестовое произведение равно S [0] + s [0]*2^(-1+1) + 2^(-2) = 1.0000 + 1.0000*1.0000 + 0.0100 = 1.0000 + 1.0000 + 0.0100 = 10.0100. Результат больше двух, значит s [1] = 1.0000, S [1] = 1.0000
2. n = 2, тестируемый бит 2^(-2), тестовое значение S [1] + s [1]*2^(-2 + 1) + 2^(-4) = 1.0000 + 1.0000*0.1000 + 0.0001 = 1.0000 + 0.1000 + 0.0001 = 1.1001. Результат меньше двух, значит s [2] = 1.0100, S [2] = 1.1001
3. n = 3, бит 2^(-3), тестовое значение S [2] + s [2]*2^(-3 + 1) + 2^(-6) = 1.1001 + 1.0100*0.0100 + 0.000001 = 1.1001 + 0.0101 + 0.000001 = 1.111001, меньше двух, s [3] = 1.0110, S [3] = 1.111001
4. n = 4, бит 2^(-4), тестовое значение S [3] + s [3]*2^(-4 + 1) + 2^(-8) = 1.111001 + 1.0110*0.0010 + 0.00000001 = 1.111001 + 0.001011 + 0.00000001 = 10.00010001, больше двух, s [4] = 1.0110, S [4] = 1.111001
5. И так далее…
Какой результат мы получили? Значение корня больше 1.0110 = 1 + 0.25 + 0.125 = 1.375, но меньше 1.0111 (это показал 4-й шаг) = 1.4375 что верно, т.к. значение корня из двух ~ 1.41.
Таким образом, остается реализовать полученный алгоритм :)
Для реализации алгоритма прежде всего необходимо посчитать, сколько двоичных цифр необходимо для представления миллиона десятичных. Это будет 1 000 000/log (2) = 3 321 929 или 415 242 байт. А в процессе работы нам понадобится 3 таких числа – одно для хранения s [n], второе – для хранения S [n] и третье для хранения промежуточного тестового результата. Размерность всех трех чисел может быть одинаковой. Но если ты внимательно фтыкал алгоритм, то мог бы заметить, что бит 2^(-2*n) увеличивает разрядность S [n] вдвое, почему же я сказал, что S [n] может быть такой же разрядности как и s [n]? Да просто потому, что как только значение бита 2^(-2*n) перестанет укладываться в разрядность s [n], его можно не учитывать (все равно не попадает в результат), т.е. все три числа могут иметь одинаковую разрядность. Но разрядность следует взять с запасом – хотя бы сотню дополнительных разрядов, чтобы случайно не потерять из-за них точность (при сдвиге s [n - 1] на (n – 1) разрядов вправо будет теряться точность). А можно взять все числа удвоенной разрядности, тогда точность не будет теряться вообще, но упадет скорость алгоритма.
Продолжение в приложени... не влазит блин
Приложение:
Теперь трудоемкость алгоритма. Очевидно, что придется сделать более 3-х миллионов итераций, каждая из которых включает сложение чисел по 450 кб. Сложение будет занимать три обращения к памяти (считать операнд 1, считать операнд 2, записать результат), т.е. за одну итерацию процессору придется обработать 450*3 = 1350 кб памяти. Итого получаем около 5 терабайт на весь процесс. Скорость современной памяти в среднем 5 гб в секунду, т.е. потребуется 1000 секунд (вполне реально), а учитывая объемы кэш памяти в 2 мб в современных процессорах (т.е. все данные уместятся в нее), скорость может повыситься еще раза в 2 - 4. Т.е. задача решаема за приемлемое время.Теперь остается только преобразовать двоичное число с 3-мя+ миллионами разрядов в десятичное :)