第 1 步:决定如何表示 bignums
已经存在为此的库。 GNU Multiple Precision Integer library 是一个常用的选项。 (但根据您的编辑,这不是一个选项。您可能仍会瞥一眼其中的一些以了解他们如何做事,但这不是必需的。)
如果你想自己滚动,我确实不建议存储十进制数字。如果这样做,则每次要对组件进行算术运算时,都需要在二进制表示形式之间进行转换。最好有一个类似uint32_ts 的链接列表,以及一个符号位。当你想读写时,你可以从/到十进制转换,但是用二进制做你的数学。
第 2 步:实现求幂
我将在这里假设链表 bignum 实现;您可以根据需要调整算法。
如果您只是计算 2 的幂,那很容易。它是一个 1 后跟 N 个 0,因此如果每个块存储 M 位并且您想表示 2^N,那么只需将 floor(N/M) 块全为 0,并将 1 << (N % M) 存储在最重要的块中。
如果您希望能够以有效的方式对 任意 基进行求幂,则应使用 exponentiation by squaring。这背后的想法是,如果你想计算 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_ts 而不是 uint64_t`s 的原因是我们可以将操作数转换为较大的类型并将它们相乘而不会有丢失进位溢出的风险。
从二进制转换为十进制以进行打印
首先,找出比你的数小 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 个数字可能需要一两毫秒。当我们显示供人类消费的数字时,这无关紧要,但如果我们不得不在某个地方的循环中等待一毫秒作为计算的一部分,它会加起来并变得非常低效。这就是我们从以十进制表示形式存储数字的原因:通过在内部将其表示为二进制,我们在输入和输出时执行一次低效的部分,但介于两者之间的一切都很快。