【问题标题】:C: print a BigInteger in base 10C:以 10 为底打印一个 BigInteger
【发布时间】:2010-12-05 21:50:08
【问题描述】:

我正在使用这个结构来表示 128 位整数:

typedef struct {
    uint64_t low, high;
} uint128;

(除非你能指向一个快速的 128 位整数库,否则我无法更改)

现在我想使用printf 以 10 为底打印这样的值。我可能需要除以 10 才能做到这一点,但还没有实现除法。

我该怎么做?该解决方案不必非常高效,只要有效即可。

编辑:我喜欢你提出的所有解决方案。你太棒了。

【问题讨论】:

  • 您使用的是什么操作系统?例如,Solaris 有一个内置的大整数库。你也可以用谷歌搜索 BIGNUM 库。
  • GCC 4.6.0 将有 __int128
  • 我更喜欢独立于平台和编译器的解决方案。

标签: c biginteger division integer-division


【解决方案1】:
void printu128(uint128 n) {
  int d[39] = {0}, i, j;
  for (i = 63; i > -1; i--) {
    if ((n.high >> i) & 1) d[0]++;
    for (j = 0; j < 39; j++) d[j] *= 2;
    for (j = 0; j < 38; j++) d[j+1] += d[j]/10, d[j] %= 10;
  }
  for (i = 63; i > -1; i--) {
    if ((n.low >> i) & 1) d[0]++;
    if (i > 0) for (j = 0; j < 39; j++) d[j] *= 2;
    for (j = 0; j < 38; j++) d[j+1] += d[j]/10, d[j] %= 10;
  }
  for (i = 38; i > 0; i--) if (d[i] > 0) break;
  for (; i > -1; i--) putchar('0'+d[i]);
}

【讨论】:

  • 这是一个简洁的算法,我将尝试解释它的作用: 1) 跟踪 39 个十进制数字(最初都是 0)。 2) 从最高位开始,对于数字的每一位执行以下操作: a) 如果设置了位,则增加最低十进制数字。 b) 如果后面有更多位,请将十进制数乘以 2。通过将所有数字加倍来实现。 c) 处理溢出的数字。 3) 找出大于零的最高位。 4) 从这里开始打印数字。
  • 我将其描述为“通过 BCD 移位”,但是是的。太酷了,+1
【解决方案2】:

如果您不想为 128 位值实现除法,您可以预先计算几个 (~40) 个表示 10 次方的 128 位值,并使用减法。

这种方式实际上只处理了较高的qword,因为较低的部分可以使用printf("%I64d")

编辑:这是一个示例(它将仅使用 char 上的算术来打印 short):

unsigned char pow10[][2] = {
    {0,   1},   // 1
    {0,  10},   // 10
    {0, 100},   // 100
    {3, 0xE8},  // 1k
    {0x27, 0x10}};  // 10k == 0x2710

#define HIGH 0
#define LOW  1

void print_dec(unsigned char H, unsigned char L){
    unsigned char L1;
    int pwr = 4, ctr = 0;
    while (pwr >= 0){
        int c = pow10[pwr][LOW] > L;
        L1 = L - pow10[pwr][LOW];
        if (H >= pow10[pwr][HIGH] + c){     
            H -= pow10[pwr][HIGH] + c;
            L = L1;
            ctr++;
        } else {
            printf("%d", ctr);
            ctr = 0;
            pwr--;
            //here we could add a check for H to be 0, so that we could use
            //printf() for lower half. we just have to be careful with 
            //leading zeroes in L, the simpliest way is to use printf("%03d",L)
        };
    };
    printf("\n");
};

int main(){
    unsigned short n = 12345;
    printf("%d should be ", n);
    print_dec((n >> 8) & 0xFF, n & 0xFF);
    return 0;
};

您可以使用较少数量的预先计算的 10 次幂,但这会使其变慢(例如,将它们以 100 为步长将使 ctr 处于 0..99 范围内。

【讨论】:

  • @ephemient:比较是针对0的,减法真的很简单(和除法比较)。
  • 我不明白这应该如何工作。 1 &lt;&lt; 64 不是 10 的幂,所以高 64 位的内容会影响用于表示低 64 位的十进制数字。
  • @Karl:常量是 128 位的,所以你用进位从两个 qwords 中减去。只有当更高的qword变为0时,你才能认为你只有一个qword。
【解决方案3】:

假设您已经实现了对 uint128 执行数学运算的函数,您可以将数字分成 3 部分并使用 printf 的内置 64 位打印功能。由于最大的 64 位数字是 20 位长,这意味着所有 19 位的十进制数字都可以这样打印,但是由于最大的 128 位数字是 39 位长,我们不能把它分成两部分,因为我们最终可能会得到一个比最大的 64 位数字大的 20 位数字。

这是一种方法,首先除以 1020 得到不大于 3,402,823,669,209,384,634 的商。然后我们将余数(本身不大于 1020)除以 1010 得到另一个商,余数均小于 1020,即两者都适合 64 位整数。

void print_uint128(uint128 value)
{
    // First power of 10 larger than 2^64
    static const uint128 tenToThe20 = {7766279631452241920ull, 5ull};
    static const uint128 tenToThe10 = {10000000000ull, 0ull};

    // Do a 128-bit division; assume we have functions to divide, multiply, and
    // subtract 128-bit numbers
    uint128 quotient1 = div128(value, tenToThe20);
    uint128 remainder1 = sub128(value, mul128(quotient, tenToThe20));
    uint128 quotient2 = div128(remainder1, tenToThe10);
    uint128 remainder2 = sub128(remainder1, mul128(remainder1, tenToThe10));

    // Now print out theresult in 3 parts, being careful not to print
    // unnecessary leading 0's
    if(quotient1.low != 0)
        printf("%llu%010llu%010llu", quotient1.low, quotient2.low, remainder2.low);
    else if(quotient2.low != 0)
        printf("%llu%010llu", quotient2.low, remainder2.low);
    else
        printf("%llu", remainder2.low);
}

【讨论】:

    【解决方案4】:

    您可以使用乘法来打印一个数字。

    1. 由于 2128 大约是 340E36,首先通过比较数字与 100E36、200E36 和 300E36 的边界来确定前导数。写一个数字并减去最近的较小边界。例如。如果数字是 234.6776E36,那么最近的小边界是 200E36,数字是“2”,减去后你应该得到 34.6776E36。

    2. 现在通过与数字 10E36...90E36 进行比较来获取下一个数字。当然是128位比较。写一个数字并减去上面的最小边界。 (对于上面的 34.6776E36,数字是 '3',边界是 30E36,余数是 4.6776E36)

    3. 然后将数字乘以 10 并从第 2 阶段开始重复,总共打印 38 次以打印每个数字。 (4.6776E36 -> 46.776E36...)

    UPD:添加了我首先错过的减法。还有例子。

    UPD2:第一个数字需要专门的步骤是因为如果你乘它旁边的数字你应该得到溢出,如果余数大于34E36。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-14
      相关资源
      最近更新 更多