Как реализовать big int в C++
Я хотел бы реализовать большой класс int в C++ как упражнение программирования-класс, который может обрабатывать числа больше, чем длинный int. Я знаю, что уже есть несколько реализаций с открытым исходным кодом, но я хотел бы написать свою собственную. Я пытаюсь понять, что такое правильный подход.
Я понимаю, что общая стратегия-получить число в виде строки, а затем разбить его на меньшие числа (например, одинарные цифры) и поместить их в массив. На этот момент должен быть относительно простым для реализации различных операторов сравнения. Моя главная забота - как я буду реализовывать такие вещи, как сложение и умножение.
Я ищу общий подход и рекомендации в отличие от фактического рабочего кода.
15 ответов
вещи, которые следует учитывать для большого класса int:
математические операторы: +, -, /, * , % Не забывайте, что ваш класс может находиться по обе стороны оператор, что операторы могут быть прикован, что один из операндов может быть int, float, double и т. д.
операторы ввода-вывода:>>,
преобразования / приведения: выяснить какие типы / классы вашего big int класс должен быть конвертируемым, и как правильно обращаться с преобразование. Быстрый список включите двойник и поплавок, и смогите включить int (с правильными границами проверка) и сложный (предполагая это смогите отрегулировать ряд).
весело вызов. :)
Я предполагаю, что вам нужны целые числа произвольной длины. Предлагаю следующий подход:
рассмотрим двоичную природу типа данных "int". Подумайте об использовании простых двоичных операций для эмуляции того, что схемы в вашем процессоре делают, когда они добавляют вещи. В случае, если вы заинтересованы более подробно, рассмотреть чтение эта статья Википедии о полу-аддерах и полных аддерах. Вы будете делать что-то подобное, но вы можете спуститься так низко - но, будучи ленивым, я думал, что просто откажусь и найду еще более простое решение.
но прежде чем переходить к любым алгоритмическим деталям о добавлении, вычитании, умножении, давайте найдем некоторую структуру данных. Простой способ, конечно, хранить вещи в std:: vector.
template< class BaseType >
class BigInt
{
typedef typename BaseType BT;
protected: std::vector< BaseType > value_;
};
вы можете рассмотреть, хотите ли вы сделать вектор фиксированного размера и если предварительно выделить его. Причина в том, что для различных операций, вам придется пойти через каждый элемент вектора-O (n). Возможно, вы захотите узнать, насколько сложной будет операция, и фиксированное n делает именно это.
но теперь к некоторым алгоритмам по работе с числами. Вы можете сделать это на логическом уровне, но мы будем использовать эту магическую мощность процессора для вычисления результатов. Но то, что мы возьмем из логической иллюстрации Half-и FullAdders, - это то, как он имеет дело с переносами. В качестве примера рассмотрим, как вы реализуете оператор+=. Для каждого числа в BigInt:: value вы добавляете их и видите, производит ли результат какую-то форму переноса. Мы не будем делать это немного мудро, но полагаться на природу нашего базового типа (будь то длинный или 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-функторы std:: plus и std:: minus, std:: times и std:: divides,... но не забывай о переноске. :) Вы также можете реализовать умножение и деление, используя свои операторы плюс и минус, но это очень медленно, потому что это пересчитает результаты, которые вы уже рассчитывали в предыдущих вызовах плюс и минус в каждой итерации. Есть много хороших алгоритмов для этой простой задачи использовать Википедия или в интернете.
и конечно, вы должны применять стандартные операторы, такие как operator<<
(просто сдвиньте каждое значение в value_ влево для n бит, начиная с value_.size()-1
... О, И помните о переноске:),operator<
- вы даже можете немного оптимизировать здесь, проверяя приблизительное количество цифр с size()
первый. И так далее. Затем сделайте свой класс полезным, befriendig std:: ostream operator<<
.
надеюсь, что этот подход полезен!
есть полный раздел об этом: [искусство компьютерного программирования, vol.2: Seminumerical Algorithms, section 4.3 Multiple Precision Arithmetic, PP. 265-318 (ed.3)]. Вы можете найти другой интересный материал в главе 4, арифметика.
Если вы действительно не хотите смотреть на другую реализацию, подумали ли вы о том, что вы хотите узнать? Есть бесчисленные ошибки, которые нужно сделать, и раскрытие их поучительно и также опасно. Есть также проблемы в выявлении важных вычислительных экономик и наличия соответствующих структур хранения для избежания серьезных проблем с производительностью.
вопрос для вас: как вы собираетесь проверить свою реализацию и как вы предлагаете продемонстрировать, что это арифметика правильная?
вам может понадобиться другая реализация для тестирования (не глядя на то, как она это делает), но потребуется больше, чтобы иметь возможность обобщать, не ожидая excrutiating уровень испытания. Не забудьте рассмотреть режимы сбоя(из-за проблем с памятью, из стека, работает слишком долго и т. д.).
удачи!
сложение, вероятно, должно быть сделано в стандартном алгоритме линейного времени
но для умножения вы можете попробовать http://en.wikipedia.org/wiki/Karatsuba_algorithm
Как только у вас есть цифры числа в массиве, вы можете делать сложение и умножение точно так же, как вы делали бы их от руки.
Не забывайте, что вам не нужно ограничиваться цифрами 0-9, т. е. использовать байты в качестве цифр (0-255), и вы все равно можете выполнять арифметику длинной руки так же, как и для десятичных цифр. Вы даже можете использовать массив long.
Я не уверен, что использование строки-правильный путь , хотя я никогда не писал код сам, я думаю, что использование массива базового числового типа может быть лучшим решением. Идея в том, что вы просто расширяете то, что у вас уже есть, так же, как CPU расширяет один бит в целое число.
например, если у вас есть структура
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;
}
это немного упрощенный пример, но должно быть довольно очевидно, как расширить структуру, которая имела переменный номер любого базового числового класса, который вы используете.
Как говорили другие, сделайте это старомодным длинным способом, но держитесь подальше от всего этого в базе 10. Я бы предложил сделать все это в базе 65536 и хранить вещи в массиве лонгов.
Если ваша целевая архитектура поддерживает BCD (двоично-десятичное) представление чисел, вы можете получить некоторую аппаратную поддержку для длинного умножения/сложения, которое вам нужно сделать. Получение компилятора для испускания инструкции BCD-это то,что вам придется прочитать...
чипы серии Motorola 68K имели это. Не то чтобы мне было горько.
используйте алгоритмы, которые вы узнали в 1-м-4-м классе.
Начните с одной колонки, затем с десяток и так далее.
мой старт будет иметь произвольный размер массива целых чисел, используя 31 бит и 32н бы как переполнение.
стартовый op будет ADD, а затем MAKE-NEGATIVE, используя дополнение 2. После этого вычитание течет тривиально, и как только у вас есть add/sub, все остальное выполнимо.
есть, вероятно, более сложные подходы. Но это был бы наивный подход из цифровой логики.
можно попробовать реализовать что-то вроде этого:
http://www.docjar.org/html/api/java/math/BigInteger.java.html
вам нужно только 4 бита для одной цифры 0-9
таким образом, значение Int позволит до 8 цифр каждый. Я решил, что буду придерживаться массива символов, поэтому я использую двойную память, но для меня она используется только 1 раз.
также при хранении всех цифр в одном 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
вычесть 48 из строки целого числа и распечатать, чтобы получить количество больших цифр. затем выполните основную математическую операцию . в противном случае я приведу полное решение.
C++ класс BigInt, который позволяет пользователю работать с целыми числами произвольной точности. http://sourceforge.net/projects/cpp-bigint/