Шаг 1: Решите, как вы собираетесь представлять бигнум
Для этого уже есть библиотеки. Библиотека *1003* * Multiple Precision Integer *1003* является широко используемой опцией. (Но, согласно вашему редактированию, это не вариант. Вы все еще можете взглянуть на некоторых из них, чтобы увидеть, как они работают, но это не обязательно.)
Если вы хотите свернуть свои собственные, я не рекомендую хранить десятичные цифры. Если вы сделаете это, вам нужно будет преобразовывать двоичное представление в двоичное представление и обратно всякий раз, когда вы хотите выполнить арифметику с компонентами. Лучше иметь что-то вроде связанного списка uint32_t
s вместе со знаковым битом. Вы можете преобразовать из / в десятичное число, когда хотите читать и писать, но выполняйте математику в двоичном формате.
Шаг 2: реализовать возведение в степень
Здесь я буду предполагать реализацию связанного списка bignum; Вы можете адаптировать алгоритмы по мере необходимости.
Если вы просто рассчитываете степень 2, это легко. За ним следует 1, за которым следуют N 0, поэтому, если в каждом блоке хранится М битов, и вы хотите представить 2^N
, то просто укажите floor(N/M)
блоков всех 0 и сохраните 1 << (N % M)
в самом значимом блоке.
Если вы хотите эффективно возвести возведение в степень с произвольными основаниями, вы должны использовать возведение путем возведения в квадрат . Идея заключается в том, что если вы хотите вычислить 3 ^ 20, вы не умножаете 3 * 3 * 3 * ... * 3. Скорее, вы вычисляете 3^2 = 3 * 3
. Тогда 3^4 = 3^2 * 3^2. 3^8 = 3^4 * 3^4. 3^16 = 3^8 * 3^8
. И вы сохраняете каждый из этих промежуточных результатов на ходу. Затем, как только вы достигнете точки, в которой возведение в квадрат снова приведет к большему числу, чем вы хотите, вы прекратите возводить в квадрат и собрать окончательный результат из имеющихся у вас фигур. В этом случае 3^20 = 3^16 * 3^4
.
Этот подход вычисляет окончательный результат за 5 шагов вместо 20, и, поскольку время является логарифмическим с точки зрения показателя степени, выигрыш в скорости становится более выраженным, чем больше показатель степени. Даже вычисление 3 ^ 100000 требует только 21 умножения.
Не существует умного подхода к умножению, о котором я знаю; вы, вероятно, можете просто сделать что-то в соответствии с базовым алгоритмом умножения длинных символов, который вы изучили в начальной школе, но на уровне блоков: причина, по которой мы использовали uint32_t
s раньше вместо uint64_t, заключается в том, что мы можем привести операнды к большего типа и умножьте их без риска потери битов переноса для переполнения.
Преобразовать из двоичного в десятичное для печати
Сначала найдите наибольшее значение, кратное 10, меньше вашего числа.
Я оставляю выполнение этого эффективно в качестве упражнения для читателя, но вы, вероятно, можете управлять им, возводя возведение в квадрат, возводя в квадрат, чтобы найти верхнюю границу, а затем вычитая различные сохраненные промежуточные значения, чтобы перейти к действительному значению быстрее, чем вы делите на 10 раз.
Или вы можете просто найти число, многократно умножив на 10; остальное будет линейным независимо от того, как обрабатывается первая часть.
Но как бы вы его не получили, у вас есть q
, такое, что q = k * 10, 10 * q > n, q <= n
, вы можете просто циклически проходить одну десятичную цифру за раз:
for (; q; q /= 10) {
int digit = n / q; //truncated down to floor(n/q)
printf("%d", digit);
n -= digit * q;
}
Возможно, в литературе где-то есть более эффективный метод, но я не знаком с одним из них. Но это не главное, если нам нужно только сделать неэффективную часть при написании вывода; это медленно независимо от алгоритма. Я имею в виду, что для печати всех 100 000 цифр может потребоваться миллисекунда или две. Это не имеет значения, когда мы показываем число для потребления человеком, но если бы нам пришлось ждать миллисекунду как часть вычисления в цикле, это сложилось бы и стало бы ужасно неэффективным. Вот почему мы никогда не храним числа в десятичном представлении: представляя их как двоичное внутренне, мы выполняем неэффективные части один раз на входе и один раз на выходе, но все между ними быстро.