二进制到十进制(大数字)

时间:2016-04-16 13:27:16

标签: algorithm binary decimal biginteger

我正在构建一个大整数的C库。基本上,我正在寻找一个快速的algorythm来将二进制表示中的任何整数转换为十进制表示

我看到了JDK的Biginteger.toString()实现,但它对我来说看起来很沉重,因为它是将数字转换为任何基数(它为每个数字使用一个除法,在处理数千时应该相当慢)数字)。

因此,如果您有任何文件/知识可以分享,我会很高兴看到它。

编辑:关于我的问题更精确:

  • 设P内存地址

  • 设N是P

  • 分配(和设置)的字节数

如何转换由地址P处的N个字节表示的整数(让我们说在小端以使事情更简单),转换为C字符串

示例:

  • N = 1

  • P =存储'00101010'的一些随机存储器地址

  • out string =“42”

感谢您的回答

2 个答案:

答案 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。然后它用 num1num2 切换并经常缩短一(blocks 递减)来调用自己。 out 从后向前填充。您必须为其分配足够大的空间,然后去除前导零。

Python 似乎使用类似的机制将大整数转换为十进制。

想要做得更好吗?

对于像我这样的大数字,每个除以 1_000_000_000 需要相当长的时间。在一定的规模下,分而治之的算法做得更好。在我的情况下,第一次除法是除以 10 ^ 16777216 将数字分成被除数和余数。然后分别转换每个部分。现在每个部分仍然很大,所以在 10 ^ 8388608 处再次拆分。递归地继续拆分,直到数字足够小。说每个可能是 1024 位数字。那些使用上面的简单算法进行转换。 “足够小”的正确定义必须经过测试,1024 只是一个猜测。

虽然对两个大整数进行长除法的开销很大,比除以 1_000_000_000 的开销大得多,但可以节省时间,因为每个单独的块需要用 1_000_000_000 进行除法才能转换为十进制的次数要少得多。

如果您已将问题拆分为单独且独立的块,那么距离将块分散到多个内核中仅一步之遥。这将真正加快转换的另一个步骤。看起来 apcalc 使用分治而不是多线程。

相关问题