Как реализовать большой int в С++

Я хотел бы реализовать большой класс int в С++ как упражнение для программирования — класс, который может обрабатывать числа, большие, чем long int. Я знаю, что уже существует несколько версий с открытым исходным кодом, но я бы хотел написать свои собственные. Я пытаюсь понять, что такое правильный подход.

Я понимаю, что общая стратегия - получить число как строку, а затем разбить ее на меньшие числа (например, отдельные цифры) и поместить их в массив. На этом этапе должно быть относительно просто реализовать различные операторы сравнения. Моя главная проблема заключается в том, как я буду реализовывать такие вещи, как сложение и умножение.

Я ищу общий подход и рекомендации, а не фактический рабочий код.

Ответ 1

Что нужно учитывать для большого класса int:

  • Математические операторы: +, -,/, *,% Не забывайте, что ваш класс может быть по обе стороны от оператора, что операторы могут быть в цепочке, что один из операндов может быть int, float, double и т.д.

  • Операторы ввода/вывода: → , < < Это где вы выясните, как правильно создайте свой класс из пользовательского ввода и как его форматировать для вывода.

  • Конверсии/роли: выведите какие типы/классы ваши большие int класс должен быть конвертирован в как правильно обрабатывать преобразование. Быстрый список включают double и float, и может include int (с соответствующими границами проверка) и комплекс (при условии, что это может обрабатывать диапазон).

Ответ 2

Увлекательная задача.:)

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

Рассмотрим двоичный характер типа "int". Подумайте о том, как использовать простые двоичные операции для эмуляции того, что делают схемы в вашем CPU, когда они добавляют вещи. Если вам интереснее более подробно, подумайте о том, чтобы прочитать эту статью в wikipedia о полуквалификаторах и добавочных сумматорах . Вы будете делать что-то похожее на это, но вы можете пойти на такой низкий уровень, но, будучи ленивым, я подумал, что я просто откажусь и найду еще более простое решение.

Но прежде чем вдаваться в какие-либо алгоритмические подробности о добавлении, вычитании, умножении, найдем некоторую структуру данных. Простой способ, конечно, хранить вещи в std::vector.

template< class BaseType >
class BigInt
{
typedef typename BaseType BT;
protected: std::vector< BaseType > value_;
};

Возможно, вы захотите рассмотреть, хотите ли вы сделать вектор фиксированного размера и, если он предварительно его выделит. Причина в том, что для различных операций вам придется пройти через каждый элемент вектора - O (n). Возможно, вам захочется узнать, насколько сложна операция, и фиксированное n делает именно это.

Но теперь к некоторым алгоритмам работы по номерам. Вы можете сделать это на логическом уровне, но мы будем использовать эту магическую мощность процессора для вычисления результатов. Но то, что мы возьмем на себя из логической иллюстрации Half- и FullAdders, это то, как она относится к переносам. В качестве примера рассмотрим, как реализовать оператор + =. Для каждого номера в BigInt < > :: value_ вы должны добавить их и посмотреть, приведет ли результат к какой-либо форме переноса. Мы не будем делать это по-разному, но полагаемся на природу нашего BaseType (будь то длинный или int или короткий или любой другой): он переполняется.

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

template< class BaseType >
BigInt< BaseType >& BigInt< BaseType >::operator += (BigInt< BaseType > const& operand)
{
  BT count, carry = 0;
  for (count = 0; count < std::max(value_.size(), operand.value_.size(); count++)
  {
    BT op0 = count < value_.size() ? value_.at(count) : 0, 
       op1 = count < operand.value_.size() ? operand.value_.at(count) : 0;
    BT digits_result = op0 + op1 + carry;
    if (digits_result-carry < std::max(op0, op1)
    {
      BT carry_old = carry;
      carry = digits_result;
      digits_result = (op0 + op1 + carry) >> sizeof(BT)*8; // NOTE [1]
    }
    else carry = 0;
  }

  return *this;
}
// NOTE 1: I did not test this code. And I am not sure if this will work; if it does
//         not, then you must restrict BaseType to be the second biggest type 
//         available, i.e. a 32-bit int when you have a 64-bit long. Then use
//         a temporary or a cast to the mightier type and retrieve the upper bits. 
//         Or you do it bitwise. ;-)

Другая арифметическая операция аналогична. Черт, вы могли бы даже использовать stl-functors std:: plus и std:: minus, std:: times и std:: dives,..., но помните о переносе.:) Вы также можете реализовать умножение и деление, используя свои плюсы и минусы, но это очень медленно, потому что это будет пересчитывать результаты, которые вы уже рассчитали в предыдущих вызовах плюс и минус на каждой итерации. Для этой простой задачи существует много хороших алгоритмов, use wikipedia или в Интернете.

И, конечно же, вы должны реализовать стандартные операторы, такие как operator<< (просто сдвиньте каждое значение в значении_ влево для n бит, начиная с value_.size()-1... oh и запомните перенос:), operator< - вы можете даже немного оптимизировать здесь, сначала проверив грубое число цифр size(). И так далее. Затем сделайте свой класс полезным, подружив std:: ostream operator<<.

Надеемся, что этот подход будет полезен!

Ответ 3

Здесь есть полный раздел: [Искусство программирования, том 2: Семинумерные алгоритмы, раздел 4.3. Арифметика с несколькими точками, с. 265-318 (ред. 3)]. Вы можете найти другой интересный материал в главе 4 "Арифметика".

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

Вопрос для вас: как вы намерены протестировать свою реализацию и как вы предлагаете продемонстрировать правильность ее арифметики?

Вам может потребоваться протестировать другую реализацию (не глядя на то, как она это работает), но для этого может потребоваться больше, чтобы не анализировать уровень тестирования excrutiating. Не забудьте рассмотреть режимы отказа (из-за проблем с памятью, из стека, слишком долго работать и т.д.).

Удачи!

Ответ 4

добавление, вероятно, должно быть выполнено в стандартном алгоритме линейного времени
но для умножения вы можете попробовать http://en.wikipedia.org/wiki/Karatsuba_algorithm

Ответ 5

После того, как у вас есть цифры номера в массиве, вы можете делать сложение и умножение точно так же, как вы делали бы их с трудом.

Ответ 6

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

Ответ 7

Не забывайте, что вам не нужно ограничивать себя 0-9 цифрами, т.е. использовать байты в виде цифр (0-255), и вы все равно можете выполнять длинную ручную арифметику так же, как и для десятичных цифр. Вы даже можете использовать массив длинных.

Ответ 8

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

Например, если у вас есть структура

typedef struct {
    int high, low;
} BiggerInt;

Затем вы можете вручную выполнять собственные операции над каждой из "цифр" (в данном случае - высокий и низкий), помня об условиях переполнения:

BiggerInt add( const BiggerInt *lhs, const BiggerInt *rhs ) {
    BiggerInt ret;

    /* Ideally, you'd want a better way to check for overflow conditions */
    if ( rhs->high < INT_MAX - lhs->high ) {
        /* With a variable-length (a real) BigInt, you'd allocate some more room here */
    }

    ret.high = lhs->high + rhs->high;

    if ( rhs->low < INT_MAX - lhs->low ) {
        /* No overflow */
        ret.low = lhs->low + rhs->low;
    }
    else {
        /* Overflow */
        ret.high += 1;
        ret.low = lhs->low - ( INT_MAX - rhs->low ); /* Right? */
    }

    return ret;
}

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

Ответ 9

Как и другие, сделайте это до старомодного длинного пути, но избегайте делать это все в базе 10. Я бы предложил сделать все это в базе 65536 и хранить вещи в большом количестве длин.

Ответ 10

Посмотрите здесь, чтобы узнать, как GMP реализует свои алгоритмы.

Ответ 11

Используйте алгоритмы, которые вы изучили с 1-го по 4-й класс.
Начните с столбца, затем десятки и т.д.

Ответ 12

Если ваша целевая архитектура поддерживает BCD (двоично-кодированное десятичное) представление чисел, вы можете получить некоторую аппаратную поддержку для умножения/добавления длинного пальца, которое вам нужно сделать. Получение компилятора для извлечения инструкции BCD - это то, что вам нужно будет прочитать...

У чипов Motorola 68K это было. Не то, чтобы я горький или что-то еще.

Ответ 13

Мое начало было бы иметь массив произвольных размеров целых чисел, используя 31 бит и 32n'd как переполнение.

Стартер op будет ADD, а затем, MAKE-NEGATIVE, используя 2 дополнения. После этого вычитание протекает тривиально, и как только у вас есть add/sub, все остальное выполнимо.

Есть, вероятно, более сложные подходы. Но это был бы наивный подход от цифровой логики.

Ответ 14

Можно попробовать реализовать что-то вроде этого:

http://www.docjar.org/html/api/java/math/BigInteger.java.html

Вам понадобится только 4 бита для одной цифры 0 - 9

Таким образом, значение Int должно содержать до 8 цифр. Я решил, что буду придерживаться массива символов, поэтому я использую двойную память, но для меня она используется только один раз.

Также при сохранении всех цифр в одном int это усложняет его и, возможно, даже замедляет его.

У меня нет тестов скорости, но, глядя на java-версию BigInteger, похоже, что он делает очень много работы.

Для меня я делаю ниже

//Number = 100,000.00, Number Digits = 32, Decimal Digits = 2.
BigDecimal *decimal = new BigDecimal("100000.00", 32, 2);
decimal += "1000.99";
cout << decimal->GetValue(0x1 | 0x2) << endl; //Format and show decimals.
//Prints: 101,000.99

Ответ 15

немного поздний ответ, вы можете найти различные bigint в c++ здесь:

https://github.com/topics/bigint

Ответ 16

вычитайте 48 из вашей цепочки и напечатайте, чтобы получить номер большой цифры. затем выполните основную математическую операцию.  в противном случае я предоставит полное решение.

Ответ 17

С++ класс BigInt, который позволяет пользователю работать с целыми целыми целыми числами. http://sourceforge.net/projects/cpp-bigint/