我正在构建一个大整数的C库。基本上,我正在寻找一个快速的algorythm来将二进制表示中的任何整数转换为十进制表示
我看到了JDK的Biginteger.toString()
实现,但它对我来说看起来很沉重,因为它是将数字转换为任何基数(它为每个数字使用一个除法,在处理数千时应该相当慢)数字)。
因此,如果您有任何文件/知识可以分享,我会很高兴看到它。
编辑:关于我的问题更精确:
设P内存地址
设N是P
如何转换由地址P处的N个字节表示的整数(让我们说在小端以使事情更简单),转换为C字符串
示例:
N = 1
P =存储'00101010'的一些随机存储器地址
out string =“42”
感谢您的回答
答案 0 :(得分:3)
BigInteger.toString方法看起来很重的原因是在块中进行转换。
一个简单的算法将采用最后的数字,然后将整个大整数除以基数,直到没有任何东西为止。
这个问题的一个问题是大整数除法非常昂贵,因此将数字细分为可以使用常规整数除法处理的块(与BigInt除法相对):
static String toDecimal(BigInteger bigInt) {
BigInteger chunker = new BigInteger(1000000000);
StringBuilder sb = new StringBuilder();
do {
int current = bigInt.mod(chunker).getInt(0);
bigInt = bigInt.div(chunker);
for (int i = 0; i < 9; i ++) {
sb.append((char) ('0' + remainder % 10));
current /= 10;
if (currnet == 0 && bigInt.signum() == 0) {
break;
}
}
} while (bigInt.signum() != 0);
return sb.reverse().toString();
}
那就是说,对于一个固定的基数,你可能会更好地根据你的需要移植“双重”算法,如评论中所述:https://en.wikipedia.org/wiki/Double_dabble
答案 1 :(得分:0)
我最近遇到了打印一个大梅森素数的挑战:2**82589933-1。在我的 CPU 上,apcalc 需要大约 40 分钟,python 2.7 需要大约 120 分钟。这是一个有 2400 万位数和一点点的数字。
这是我自己的用于转换的小 C 代码:
// print 2**82589933-1
#include <stdio.h>
#include <math.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
const uint32_t exponent = 82589933;
//const uint32_t exponent = 100;
//outputs 1267650600228229401496703205375
const uint32_t blocks = (exponent + 31) / 32;
const uint32_t digits = (int)(exponent * log(2.0) / log(10.0)) + 10;
uint32_t num[2][blocks];
char out[digits + 1];
// blocks : number of uint32_t in num1 and num2
// num1 : number to convert
// num2 : free space
// out : end of output buffer
void conv(uint32_t blocks, uint32_t *num1, uint32_t *num2, char *out) {
if (blocks == 0) return;
const uint32_t div = 1000000000;
uint64_t t = 0;
for (uint32_t i = 0; i < blocks; ++i) {
t = (t << 32) + num1[i];
num2[i] = t / div;
t = t % div;
}
for (int i = 0; i < 9; ++i) {
*out-- = '0' + (t % 10);
t /= 10;
}
if (num2[0] == 0) {
--blocks;
num2++;
}
conv(blocks, num2, num1, out);
}
int main() {
// prepare number
uint32_t t = exponent % 32;
num[0][0] = (1LLU << t) - 1;
memset(&num[0][1], 0xFF, (blocks - 1) * 4);
// prepare output
memset(out, '0', digits);
out[digits] = 0;
// convert to decimal
conv(blocks, num[0], num[1], &out[digits - 1]);
// output number
char *res = out;
while(*res == '0') ++res;
printf("%s\n", res);
return 0;
}
转换是破坏性的并且是尾递归的。在每一步中,它将 num1
除以 1_000_000_000 并将结果存储在 num2
中。余数被添加到 out
。然后它用 num1
和 num2
切换并经常缩短一(blocks
递减)来调用自己。 out
从后向前填充。您必须为其分配足够大的空间,然后去除前导零。
Python 似乎使用类似的机制将大整数转换为十进制。
想要做得更好吗?
对于像我这样的大数字,每个除以 1_000_000_000 需要相当长的时间。在一定的规模下,分而治之的算法做得更好。在我的情况下,第一次除法是除以 10 ^ 16777216 将数字分成被除数和余数。然后分别转换每个部分。现在每个部分仍然很大,所以在 10 ^ 8388608 处再次拆分。递归地继续拆分,直到数字足够小。说每个可能是 1024 位数字。那些使用上面的简单算法进行转换。 “足够小”的正确定义必须经过测试,1024 只是一个猜测。
虽然对两个大整数进行长除法的开销很大,比除以 1_000_000_000 的开销大得多,但可以节省时间,因为每个单独的块需要用 1_000_000_000 进行除法才能转换为十进制的次数要少得多。
如果您已将问题拆分为单独且独立的块,那么距离将块分散到多个内核中仅一步之遥。这将真正加快转换的另一个步骤。看起来 apcalc 使用分治而不是多线程。