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

时间:2016-06-04 15:49:10

标签: c biginteger bigint format-conversion

冒着把这个问题投票为副本,甚至关闭它的风险,我已经提出了这个问题。

背景

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

我尝试过什么

仍在尝试起草解决方案。

研究

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

Convert a "big" Hex number (string format) to a decimal number (string format) without BigInteger Class

C: print a BigInteger in base 10

Fastest way to convert a BigInteger to a decimal (Base 10) string?

Convert a "big" Hex number (string format) to a decimal number (string format) without BigInteger Class

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

4 个答案:

答案 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失败,而不是将负数转换为正数,而不是将负数转换为正数。