Какую структуру данных следует использовать для создания собственного класса BigInteger?

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

Это будет для произвольно длинных целых чисел, даже сотен цифр.

Выполняя математику с этими числами, цифра за цифрой несложна, как вы думаете, какая структура данных будет лучшей для представления моего «BigInteger»?

Сначала я рассматривал возможность использования массива, но потом подумал, что потенциально могу переполниться (исчерпать слоты массива) после большого сложения или умножения. Будет ли это хорошим случаем для использования связанного списка, поскольку я могу добавлять цифры с временной сложностью O (1)?

Есть ли какая-то другая структура данных, которая подошла бы еще лучше, чем связанный список? Должен ли тип, который хранится в моей структуре данных, быть наименьшим возможным целочисленным типом, доступным мне?

Кроме того, должен ли я быть осторожным с тем, как я храню свою переменную переноса? Должен ли он сам быть моего типа «BigInteger»?


person Fifa89    schedule 08.02.2010    source источник
comment
(a) Я не думаю, что вам следует использовать связанный список, я уверен, что некоторые операции потребуют (или выиграют от) произвольного доступа. Кроме того, связанные списки медленны со всеми выделениями памяти. (b) Если вы используете наименьшее целое число, вы будете использовать наименьший объем памяти, но если вы используете все, что соответствует размеру слова (например, int), вы будете работать быстро. Так что это зависит от того, что вас больше всего беспокоит. Очевидная возможность — сделать целочисленный тип параметром шаблона вашего класса. (c) Проверьте библиотеку GNU MP, вы не ошибетесь, если скопируете некоторые из их дизайнерских решений.   -  person Manuel    schedule 08.02.2010
comment
Вот исходный код класса Java BigInteger: kickjava.com/src/java/math /BigInteger.java.htm   -  person Manuel    schedule 08.02.2010


Ответы (10)


Прочтите книгу Дэвида Р. Хэнсона Интерфейсы и реализации C. В нем есть 2 главы по этому вопросу, посвященные векторной структуре, размеру слова и многим другим вопросам, с которыми вы, вероятно, столкнетесь.

Он написан для C, но большая его часть применима к C++ и/или Java. И если вы используете C++, это будет немного проще, потому что вы можете использовать что-то вроде std::vector для управления распределением массива для вас.

person finnw    schedule 08.02.2010

Всегда используйте наименьший тип int, который будет выполнять нужную вам работу (в байтах). Связанный список должен работать хорошо, так как вам не придется беспокоиться о переполнении.

person captncraig    schedule 08.02.2010
comment
Я бы написал это в базе 2 ** (machine_word-1) (база 65536 для 32-битных машин). Зачем тратить время на обработку отдельных байтов, если можно обрабатывать целые слова сразу. - person Tadeusz A. Kadłubowski; 08.02.2010
comment
Переполнение на самом деле не является проблемой, он может использовать вектор или двухстороннюю очередь, которые будут намного эффективнее, чем связанный список для этого приложения. - person Manuel; 08.02.2010
comment
Я отказываюсь верить, что 2**31 равно 65536. - person Ponkadoodle; 08.02.2010
comment
std::list использует два указателя ссылки на узел. В 64-битной системе это будет 16 байтов ссылок и 1 байт данных… которые дополняются до 8. Подходящим типом для этого задания является int, который может быть 64 или 32 (или даже 16) бит — класс должен определить, какой из них, и настроить себя, если это необходимо. Кроме того, связанный список совершенно неуместен. - person Potatoswatter; 09.02.2010
comment
зачем вообще список? почему бы просто не иметь 1 целое число и 1 счетчик, когда целое число достигает своего максимального значения, установить его обратно в 0 и увеличить счетчик... например: целое число + счетчик * Max_Integer. это будет вывод в виде строки. - person Anthony Raimondo; 18.04.2013

Если вы используете бинарные деревья (чьи листья — целые числа), вы получаете все преимущества связанного списка (неограниченное количество цифр и т. д.) с более простыми алгоритмами «разделяй и властвуй». В этом случае у вас не одна база, а множество, в зависимости от уровня, на котором вы работаете.

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

С тем же успехом я мог бы сказать: было бы ужасной тратой времени использовать основание 10, когда можно использовать 2^30 или 2^31.

person Pascal Cuoq    schedule 08.02.2010

Доступ к элементам связанных списков происходит медленно. Я думаю, что массивы - это путь, с большим количеством проверок привязок и изменением размера массива во время выполнения по мере необходимости.


Пояснение. Обход связанного списка и обход массива — это операции O(n). Но обход связанного списка требует отложенного указателя на каждом шаге. Тот факт, что два алгоритма имеют одинаковую сложность, не означает, что они оба выполняются одинаково. Накладные расходы на выделение и освобождение n узлов в связанном списке также будут намного больше, чем на управление памятью одного массива размером n, даже если размер массива необходимо изменить. несколько раз.

person mob    schedule 08.02.2010
comment
Как они медленные? Я не делаю произвольный доступ ни к одной из этих операций, только последовательно. Это сделало бы связанный список таким же быстрым, как и вектор для последовательного доступа, и более высокой скоростью для вставок. - person Fifa89; 08.02.2010
comment
@ Fifa89 - связанный список быстрее для вставок в середине, но для того, что вы хотите сделать, вы будете добавлять элементы в конце, и для этого вектор или очередь будут такими же быстрыми. - person Manuel; 08.02.2010
comment
@ Мануэль, я продолжаю видеть, как ты это говоришь, но не мог бы ты дать ответ, показывающий это? Насколько мне известно, связанный список обеспечивает время вставки O (1), а вектор обеспечивает амортизированную вставку O (1). Можете ли вы уточнить, что делает вектор быстрее? - person Fifa89; 08.02.2010
comment
@ Fifa89 - я предполагал, что вы будете добавлять элементы только в конце (с помощью push_back). Связанный список будет выделяться при каждом push_back, тогда как вектор будет выделять каждые N push_back. Для такого чувствительного к производительности класса, как BigNum, это будет иметь огромное значение. - person Manuel; 08.02.2010
comment
@ Fifa89. Вы уверены, что обход связанного списка выполняется так же быстро, как та же операция с вектором? Я уверен, что для больших BigNums вектор вызовет гораздо меньше промахов кеша. - person Manuel; 08.02.2010
comment
@ Fifa89, @Manuel: пока число помещается в кеш, оба будут линейными по времени без промахов кеша. Однако связанный список будет ограничен количеством обращений L1, необходимых для его обхода, поскольку вам необходимо получить доступ ко всем ссылкам. Кроме того, связанный список заполнит кеш и вызовет промахи при меньшем количестве, потому что он забивает кеш указателями. - person Potatoswatter; 09.02.2010

Вау, здесь есть несколько… интересных ответов. Я бы рекомендовал прочитать книгу, а не пытаться разобраться во всех этих противоречивых советах.

Тем не менее, C/C++ также плохо подходит для этой задачи. Большое целое число — это своего рода математика повышенной точности. Большинство ЦП предоставляют инструкции для обработки математических операций с повышенной точностью на сравнимой или такой же скорости (бит на инструкцию), как и при обычной математике. Когда вы добавляете 2 ^ 32 + 2 ^ 32, ответ равен 0 ... но есть также специальный вывод переноса из ALU процессора, который программа может прочитать и использовать.

C++ не может получить доступ к этому флагу, как и в C. Вы должны использовать ассемблер.

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

person Potatoswatter    schedule 08.02.2010
comment
Если бы он хотел использовать существующую библиотеку, он бы просто использовал класс BigInteger, который он упомянул в своем посте. Иногда люди делают что-то, чтобы учиться, а не внедрять в какую-то производственную среду. Я думаю, что это хорошее задание по компьютерным наукам, которое в какой-то момент должен выполнить каждый студент компьютерных наук. - person mmcdole; 09.02.2010
comment
@simucal: есть также изучение небольшого ассемблера, необходимого для расширенного умножения и сложения, что тоже является образовательным… - person Potatoswatter; 09.02.2010

Я бы сказал, массив целых чисел.

person fastcodejava    schedule 08.02.2010
comment
Не могли бы вы хотя бы решить проблемы, которые я упомянул в отношении этой реализации? - person Fifa89; 08.02.2010

Массив действительно подходит естественным образом. Я думаю, допустимо бросать OverflowException, когда у вас не хватает места в памяти. Учитель увидит внимание к деталям.

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

Перенос — это не более чем однозначное длинное число при умножении (9*9 = 1, перенос 8). Один int будет делать.

person Tadeusz A. Kadłubowski    schedule 08.02.2010
comment
В результате умножения получается примерно digit_a + digits_b + [0-2] цифры в произведении... что не более чем в два раза превышает число в большем вводе, но может быть значительно меньше этого. - person dmckee --- ex-moderator kitten; 08.02.2010

std::vector<bool> или std::vector<unsigned int>, вероятно, то, что вам нужно. Вам придется push_back() или resize() на них, так как вам нужно больше места для умножения и т. д. Кроме того, не забудьте отодвинуть назад правильные биты знака, если вы используете два комплимента.

person Inverse    schedule 08.02.2010
comment
Честно говоря, я бы не рассматривал std::vector‹bool› как вариант - person Manuel; 08.02.2010

я бы сказал std::vector of char (поскольку он должен содержать только 0-9) (если вы планируете работать в BCD)

Если не BCD, используйте вектор int (вы не дали понять)

Гораздо меньше места над списком ссылок

И все советы говорят: «Используйте вектор, если у вас нет веских причин».

person pm100    schedule 08.02.2010
comment
ммм... Я думаю, что двоичная математика была бы лучше, чем работа с числами с основанием 10... - person Inverse; 08.02.2010
comment
ммм - BCD - это обычная реализация bigint. Он не сказал, что он собирался сделать - person pm100; 08.02.2010

Как правило, используйте std::vector вместо std::list, если только вам не нужно очень часто вставлять элементы в середину последовательности. Векторы, как правило, работают быстрее, поскольку они хранятся непрерывно и, таким образом, выигрывают от лучшей пространственной локализации (основной фактор производительности на современных платформах).

Убедитесь, что вы используете элементы, которые естественны для платформы. Если вы хотите быть независимым от платформы, используйте long. Помните, что если у вас нет каких-либо специальных встроенных функций компилятора, вам потребуется тип как минимум в два раза больше для выполнения умножения.

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

Убедитесь, что вы прочитали Искусство компьютерного программирования Кнута, алгоритмы, относящиеся к арифметике произвольной точности, описаны там в значительной степени.

person avakar    schedule 08.02.2010
comment
Могу я спросить, почему долго? Есть ли гарантии, что это будет соответствовать размеру слова архитектуры? - person Manuel; 09.02.2010
comment
Нет, это просто обоснованное предположение (оно соответствует естественному размеру слова в msvc и gcc на Intel). - person avakar; 09.02.2010