冒着把这个问题投票为副本,甚至关闭它的风险,我已经提出了这个问题。
背景
在“正常”数据类型(如int,long long等等)中,要将二进制数值转换为十进制字符串,您将执行以下操作(使用伪代码):
Set length = 0
Set divisor to largest base10 value the data type will hold (Divisor).
Loop
Divide number in question by divisor.
Place result in a string at position length.
Increment the length by 1.
Divide the divisor by 10.
Reverse the string.
Print the string.
(大多数)任何语言的实际实现都非常简单。
问题
我遇到的上述方法的问题是,对于大整数(也称为任意精度算术),开始时没有最大的基数10值。所以问题是“如果无法知道该值是什么,如何将除数初始化为最大可能的base10值?”
我尝试过什么
仍在尝试起草解决方案。
研究
我在此处找到的一些链接包括以下内容:
C: print a BigInteger in base 10
Fastest way to convert a BigInteger to a decimal (Base 10) string?
Google搜索发现了其他内容,但没有任何具体回答我的问题。
观
我认为可能工作的一种方法如下(在伪代码中):
Define p_divisor as previous divisor.
Set divisor = 1
Loop:
if divisor < dividend
then
Set p_divisor = divisor
divisor = divisor * 10
else
end loop
Loop:
Divide number in question by divisor.
Place result in a string at position length.
Increment the length by 1.
Divide the divisor by 10.
if divisor == 1 then end loop
Reverse the string.
Print the string.
这是正确的方法吗?我有一个大整数库和工作(包括乘法和除法)所以它不会那么难以实现。我在这个方法中看到的一个大问题是性能,因为你必须运行一个乘法序列来得到初始除数,然后你必须为每个base10位置分两次。一个用于实际除法,另一个用于除数。
答案 0 :(得分:3)
执行此操作的一种(相当常见)方法,无论是大整数还是普通整数类型,都是重复将数字除以10,将余数保存为下一个数字(从最低位开始)。继续前进,直到数字达到零。由于找到的第一个数字是最不重要的,你可能需要在结尾处反转字符串,或者在你去的时候反向构建它。
使用普通unsigned int
的示例可能如下所示:
void printUInt(unsigned x) {
char buf[(sizeof(x) * CHAR_BIT) / 3 + 2]; // slightly oversize buffer
char *result = buf + sizeof(buf) - 1; // index of next output digit
// add digits to result, starting at
// the end (least significant digit)
*result = '\0'; // terminating null
do {
*--result = '0' + (x % 10); // remainder gives the next digit
x /= 10;
} while (x); // keep going until x reaches zero
puts(result);
}
对于一个大整数,这个过程几乎是一样的 - 尽管如果可以的话,最好进行除法并一步找到余数。
上面的示例从缓冲区的末尾构建字符串(因此result
最终指向缓冲区的中间位置),但您也可以从开始构建它并在之后反转它。
如果您可以确定原始数字中使用的位数(每3位大约增加一位数 - 稍微少一些),您可以估算输出所需的大小。
答案 1 :(得分:0)
已接受的答案已经为您提供了一种简单的方法。这很好,并给你一个很好的结果。但是,如果您确实需要将大值转换为字符串,则有更好的方法。
我不会详细介绍,因为我的解决方案是用Delphi编写的,很多读者都不会轻易阅读,而且很长(100多行代码中的几个函数,使用其他函数等等) 。这在一个简单的答案中无法解释,特别是因为转换处理不同的数字基数不同)。
但原则是将数字分成两个几乎相等大小的一半,乘以一个10的幂。为了转换它们,recursivley再次将它们分成两个较小的部分,以较小的10次幂等等。直到部件的大小达到某种下限(例如,32位),然后您最终转换为传统方式,即在接受的答案中。
然后将部分转换连接起来&#34; (实际上,数字直接放在正确地址的单个缓冲区中),所以在最后,你得到一个巨大的数字串。
这有点棘手,我只提到那些想要对极大数字进行调查的人。对于少于100位数的数字,这没有意义。
这确实是一种递归方法,但不是简单地除以10的方法。
通过执行类似
之类的操作,可以预先计算缓冲区的大小bufSize = myBigInt.bitCount() * Math.log10(2) + some_extra_to_be_sure;
我使用预先计算的表来表示不同的数字基础,但这是一个实现细节。
对于非常大的数字,这将比重复除以10的循环快 多 ,特别是因为这样,整个数字必须除以10全部时间,它只会变得非常缓慢。分而治之的算法只划分越来越少的数字,并且用于削减部分的(昂贵的)划分总数要低得多(log N而不是N,是我的猜测)。因此,(平均而言)数量的分数更少。
比照布伦特,齐默尔曼,&#34;现代计算机算术&#34;,算法1.26
如果您想了解其工作原理,可以在此处找到我的代码和说明:BigIntegers unit
答案 2 :(得分:0)
我遇到了类似的问题,并没有找到任何我喜欢的解决方案,所以想出了我的自己。我们的想法是将BigInt
使用任何基数转换为另一个BigInt
,使用10
的幂,尽可能大但仍小于当前基数。您可以使用系统调用通过“数字”转换,并连接结果。所以没有涉及明确的划分,只隐藏在系统库函数中。整体复杂性仍然是二次的(就像其他基于分部的解决方案一样)。
friend std::ostream& operator<<(std::ostream& out, const BigInt_impl& x){
using Big10 = BigInt_impl<char32_t, uint64_t, 1000000000>; // 1e9 is the max power of 10 smaller then BASE
auto big10 = Big10(0);
auto cm = Big10(1);
for(size_t i = 0; i < x.digits.size(); ++i, cm *= BASE){
big10 += cm*x.digits[i];
}
out << big10.digits.back();
for(auto it = next(big10.digits.rbegin()); it != big10.digits.rend(); ++it){
out << std::setfill('0') << std::setw(9) << *it;
}
return out;
}
在此解决方案中注意魔法常量1e9 - 这仅适用于BASE = 2^32
的情况。懒得做得好。
(对不起,对于C ++,我刚刚意识到qustion是关于C的,但仍然希望将代码留在这里,也许是为了说明想法)
答案 3 :(得分:-1)
这是正确的方法吗?
第二种方法不适用于C中的所有整数值。if divisor < dividend
依赖于创建divisor
作为比dividend
更大(或相等)10的幂。由于大多数整数系统具有有限范围,因此当dividend
不可能时,创建比dividend == INTEGER_MAX
大10(或相等)的幂。 (除非INTEGER_MAX
是10的幂)。
递归方法的工作原理是重复除以10并推迟数字赋值,直到确定更有效的数字为止。当目标缓冲区的大小未知但足够时,这种方法很有效。
以下处理签名int
并且也适用于INT_MIN
,但没有未定义的行为。
// Return location of next char to write
// Note: value is expected to be <= 0
static char *itoa_helper(char *s, int value) {
if (value/10) {
s = itoa_helper(s, value/10);
}
*s = '0' - value % 10; // C99
return s+1;
}
void itoa(int n, char *s) {
if (n < 0) {
*s++ = '-';
} else {
n = -n;
}
*itoa_helper(s, n) = '\0';
}
#define INT_SIZEMAX ((CHAR_BIT*sizeof(int) - 1)*28/93 + 3)
char buf[INT_SIZEMAX];
itoa(INT_MIN, buf);
在大多数系统上,-INT_MIN
失败,而不是将负数转换为正数,而不是将负数转换为正数。