将大整数转换为十进制字符串

如果将这个问题投票为重复,甚至将其关闭,那么我就有了这个问题。

背景

在“正常”数据类型(如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值?”

我曾经尝试过什么

仍在尝试起草解决方案。

研究

我在这里找到的一些链接包括以下内容:

将“大”hex数字(字符串格式)转换为十进制数字(字符串格式),不带BigInteger类

C:在10号基础上打印一个BigInteger

将BigInteger转换为十进制(Base 10)字符串的最快方法?

将“大”hex数字(字符串格式)转换为十进制数字(字符串格式),不带BigInteger类

谷歌搜索发现了其他东西,但没有任何具体回答我的问题。

思路

我认为可能有效的一种方法如下(在伪代码中):

 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位置分两次。 一个用于实际除法,另一个用于除数。

无论是大整数还是普通整数类型,一种(相当常见的)方法是重复将数字除以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个附加位数 – 稍微少一点),则可以估计输出所需的大小。

已接受的答案已经为您提供了一种简单的方法。 这很好,给你一个很好的结果。 但是,如果您确实需要将大值转换为字符串,则有更好的方法。

我不会详细介绍,因为我的解决方案是用Delphi编写的,许多读者都不能轻易阅读,而且它很长(100多行代码中的几个函数,使用其他函数等等,不能在一个简单的答案中解释,特别是因为转换处理不同的数字基数不同)。

但原则是将数字分成两个几乎相等的大小的一半,乘以一个10的幂。为了转换它们,recursivley再次将它们分成两个较小的部分,用较小的10次幂等等,直到大小为止。部件达到某种下限(比如32位),然后你最终转换传统的方式,就像在接受的答案中一样。

然后,部分转换被“连接”(实际上,数字直接放在正确地址的单个缓冲区中),所以最后,你得到一个巨大的数字串。

这有点棘手,我只提到那些想要对极大数字进行调查的人。 对于数字少于100位的数字没有意义。

事实上,这是一种递归方法,但不是简单地除以10的方法。

通过执行类似操作,可以预先计算缓冲区的大小

 bufSize = myBigInt.bitCount() * Math.log10(2) + some_extra_to_be_sure; 

我使用预先计算的表来表示不同的数字基础,但这是一个实现细节。

对于非常大的数字,这将比重复除以10的循环快得多,特别是因为这样,整个数字必须始终除以10,并且它只会变得非常慢。 分而治之算法只划分越来越少的数字,切割部分的(昂贵的)划分总数要低得多(log N而不是N,是我的猜测)。 所以(平均而言)更少的分数更少。

比照 布伦特,齐默尔曼,“现代计算机算术”,算法1.26

如果你想看看它是如何工作的,我的代码和解释可以在这里找到: BigIntegers单元

我遇到了类似的问题,并没有找到任何我喜欢的解决方案,所以想出了我的自己。 我们的想法是将你的BigInt使用任何基数转换为另一个BigInt ,功率为10 ,尽可能大但仍小于你当前的基数。 您可以使用系统调用通过“数字”转换,并连接结果。 所以没有涉及明确的划分,只隐藏在系统库函数中。 整体复杂性仍然是二次的(就像其他基于分部的解决方案一样)。

 friend std::ostream& operator<<(std::ostream& out, const BigInt_impl& x){ using Big10 = BigInt_impl; // 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的,但仍然希望将代码留在这里,也许是为了说明想法)

这是正确的方法吗?

第二种方法不适用于C中的所有整数值。 if divisor < dividend依赖于将divisor设为比dividend大10(或等于)10的幂。 由于大多数整数系统具有有限范围,因此当dividend == INTEGER_MAX时,创建比dividend大10(或等于)10的幂。 (除非INTEGER_MAX是10的幂)。


递归方法通过重复除以10并推迟数字分配直到确定更有效的数字来工作。 当目标缓冲区的大小未知且足够时,此方法很有效。

下面处理signed 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失败相反。