Смекни!
smekni.com

Реализация класса больших чисел (стр. 1 из 3)

Пояснительная записка

«Реализация класса больших чисел»

Введение

Постановка задачи

Реализовать средствами языка С++ класс больших целых чисел. Для написания класса были выделены следующие задачи:

· Организовать чтение из консоли и печать в консоль целых чисел, длина которых превышает 232 разрядов (стандартный тип long)

· Реализовать выполнение арифметических операции с данными числами:

o Сложение

o Вычитание

o Произведение

o Нахождение целой части от деления

o Нахождение остатка от деления

o Возведение в степень

Факториал

Ход решения

Первым делом была выбрана структура класса больших целых чисел. Так как число может быть как положительным, так и отрицательным было введено символьное поле, отвечающее за знак числа «+» или «–». Само число решено было записывать с помощью очереди с двусторонним доступом (deque) – контейнер из стандартной библиотеки шаблонов (STL). Очередь представляет собой динамический массив с множеством стандартных методов для его обработки. «Цифру» каждого разряда большого числа мы будем помещать в соответствующую ячейку массива. Например, число 12345, записанное с помощью deque<int> mas; будет выгледеть как набор элементов этого массива: mas[0] = 1, mas[1] = 2, mas[2] = 3, mas[3] = 4, mas[4] = 5. Также класс будет содержать некоторое количество методов, для решения поставленных задач. Класс будет иметь название BigInteger. Структура класса изображена на рисунке 1.

Рис. 1. Схема класса BigInteger

Далее перешли к разработке методов класса. Сначала для непосредственной работы с большими числами были реализованы методы для чтения числа из консоли и печати в консоль – chtenie() и vector_print(BigInteger) соответственно.

Метод chtenie() считывает в виде строки данные введенные в консоль. Затем проверяет первый символ строки на наличие знака «–». Потом посимвольно, используя вспомогательную строку, содержащую цифры («0123456789»), заносит каждую цифру в конец очереди-массива. На выходе мы получаем большое число, содержащее знак «», либо «–». Также в методе используется вспомогательный метод dell_null(BigInteger), который возвращает число, удаляя впереди стоящие ничего не значащие нули (т.е. 00123 –> 123).

Метод vector_print(BigInteger) выполняет печать в консоль числа, предварительно задействовав, описанный выше вспомогательный метод dell_null(BigInteger). Сначала происходит печать знака числа, затем, используя цикл, выполняется печать каждого элемента очереди-массива. Результаты работы методов представлены на рисунке 2.

Рис. 2. Ввод-вывод большого положительного и отрицательного числа в консоль

Затем был реализован метод сложения двух больших чисел summa (BigIneger, BigInteger). Сначала метод проверяет знаки переданных ему чисел, если знаки разные, он, немного модифицируя знаки, передает числа методу, вычисляющему разность двух больших чисел, речь о котором пойдет дальше. Если же знаки чисел одинаковые, непосредственно происходит операция их сложения. Принцип основан на сложении «столбиком». Находим «длину» минимального по разрядам числа, затем складываем поразрядно числа в пределах этой «длины», добавляя 1, если сумма на предыдущем шаге была > 9, находим остаток от деления полученного числа на 10 (int% 10) и записываем его в разряд, над которым выполнялась операция. После того, как разряды у одного из чисел закончатся, «добиваем» результирующее число цифрами из оставшихся разрядов большего числа. Естественно, чтобы произвести операцию с первым разрядом, нужно обратиться к последнему элементу нашей очереди массива. Пример сложения представлен на рисунке 3.

Рис. 3. Операция сложения

Следующей операцией над большими числами стала операция вычитания, представленная методом rasnost (BigInteger, BigInteger). Вначале метод проверяет знаки переданных ему чисел, если знаки чисел равные (с учетом знака разности), то метод передает числа методу summa (BigInteger, BigInteger). Например, 12 – (-7). Метод передаст 12 и 7 для обработки методу summa (BigInteger, BigInteger). Если знаки чисел разные, то происходит операция вычитания. Принцип основан на вычитании «столбиком». Находим число с меньшим количеством разрядов (меньшей «длиной»), ставим его вторым. И начинаем поразрядно вычитать. При этом, если в разряде первого числа значение больше, чем в разряде второго числа, прибавляем к разности этих чисел 10. Но на следующем шаге вычитаем 1 из следующего полученного результата поразрядного вычитания. Выполняем эти действия, пока не закончатся разряды наименьшего числа, затем «добиваем» результирующее число цифрами из оставшихся нетронутыми разрядов большего числа. Пример вычитания представлен на рисунке 4.

Рис. 4. Операция вычитания

После того, как были реализованы операции сложения и вычитания, перешли к написанию операции умножения, которую выполняет метод proisvedenie (BigInteger, BigInteger). Также в его основе лежит принцип умножения «столбиком». Выбираем одно из больших чисел, и поэтапно умножаем числа из каждого разряда этого большого числа, на другое большое число. В результате на каждом шаге у нас получаются «промежуточные» большие числа, суммируя которые с помощью описанного выше метода summa (BigInteger, BigInteger) мы получаем необходимый нам результат. При этом не забываем в зависимости от разряда множителя «добивать» начальные разряды «промежуточных» больших чисел нулями. Как и в предыдущих операциях при поразрядном перемножении в результат мы записываем остаток от деления на 10 и, если результат поразрядного перемножения больше либо равен 10, то на следующем шаге перемножения мы прибавляем число равное целой части от деления предыдущего результата на 10. Также для ускорения процесса перемножения были выделены особые случаи – умножение на 0 и на 1. Пример перемножения двух больших чисел представлен на рисунке 5.


Рис. 5. Операция умножения

После реализации метода перемножения двух больших чисел операции возведения числа в степень и операция взятия факториала числа не представляют большой трудности, так как могут быть выражены через умножение. Метод stepen (BigInteget, int), представляющий операцию возведения в степень большого числа, принимает в качестве аргументов само большое число и целое число, задающее степень, в которую необходимо возвести большое число. Метод вызывает операцию перемножения числа на само себя в цикле, количество шагов которого равно заданной степени. После выполнения данного цикла получаем нужную нам степень большого числа. Метод factorial(BigInteger), представляющий операцию получения факториала большого числа, мог быть выполнен двумя способами: используя рекурсию или используя итерацию, т.е. цикл. Был выбран второй вариант, так как он более производительный и не требует повторного вызова метода factorial(BigInteger), что замедляло бы работу программы. Для перемножения здесь использовалось вспомогательное большое число, которое изначально приравнивалось к числу, факториал которого нужно было найти. Затем на каждом шаге итерации оно уменьшалось на 1. На каждом шаге это число умножалось на ранее полученный результат, т.е. получалась выражение вида N!=((N*N-1)*N-2*)…*1. После выполнения данного цикла получаем факториал заданного числа.


Рисунок 6. Вычисление факториала 1000

Построение проекта осуществлялось в режиме Release. Технические характеристики компьютера, на котором выполнялись расчеты, представлены на рисунке 7.

Рисунок 7. Технические характеристики

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


Таблица 1. Время вычисления факториала 1000

Порядковый номер вычисления Время вычисления, сек
1 2,446
2 2,448
3 2,426
4 2,451
5 2,441
6 2,442
7 2,442
8 2,443
Среднее время вычисления 2,442

Таким образом, программа вычисляет факториал 1000 в среднем за 2,442 секунды.

Пожалуй, самой сложной для реализации, является операция деления и нахождение остатка от деления двух больших чисел. Методы соответствующие данным операциям были названы delenie (BigInteger, BigInteger) и ostasok_delenie (BigInteger, BigInteger) соответсвенно. В основе лежит принцип деления «столбиком». Пример работы алгоритма приведен на рисунке 8.

Рис. 8. Операция деления и нахождение остатка от деления

Ход алгоритма следующий: сравниваем делитель с делимым, прибавляя поразрядно по одной цифре к делителю в случае, если получившийся делитель меньше делимого, при этом в частное записываем 0. На рисунке 8 видно этот этап: 2<7985, в частное записываем 0, затем 21<7985, в частное записываем 0, и так далее пока не поменяется знак неравенства 21367>7985. После этого запускается цикл по нахождению следующей цифры частного. На каждом шаге делитель прибавляется на величину равную самому делителю, пока он не станет больше либо равен нашему промежуточному делимому, т.е. 21367. Шаг цикла, на котором выполнится данное условие, и будет искомой цифрой для частного. Затем вычитаем из промежуточного делимого полученное в ходе цикла число и получаем промежуточный остаток. Так как он точно меньше делителя (в связи с предыдущими условиями), добавляем к нему следующую не задействованную цифру делимого и переходим к первому шагу алгоритма. Алгоритм считается выполненным, если получается остаток, меньший делителя и не осталось ни одной незадействованной цифры делимого. В зависимости от задачи, метод возвращает либо частное, либо остаток от деления.