C语言 如何表达一个大整数?

mklgxw1f  于 2023-04-29  发布在  其他
关注(0)|答案(3)|浏览(84)

在C中

#include <stdio.h>
#include <string.h>
#include <math.h>

int main() {
    unsigned long long result = 20170401000ULL * 20170401000;
    printf("%llu\n", result);
    return 0;
}

输出

1016706879190864448

预期

406845076500801000000
n9vozmp4

n9vozmp41#

要处理大于标准类型unsigned long long的数字,可以使用不同的解决方案:
1.你可以使用bignum库,比如GNU的gmp。
1.如果系统上可用,可以使用更大的类型,例如__uint128_t
1.你可以将操作数切成块,标准类型可以处理这些块的结果而不会溢出或绕回。
下面是(2)的一个例子:

#include <stdio.h>

int main() {
    unsigned long long a = 20170401000ULL;
    unsigned long long b = 20170401000ULL;
    unsigned long long result[3];
    __uint128_t m = (__uint128_t)a * (__uint128_t)b;

    // handle all 128-bit values, up to 340282366920938463463374607431768211455
    result[0] = m % 1000000000000000000;
    result[1] = m / 1000000000000000000 % 1000000000000000000;
    result[2] = m / 1000000000000000000 / 1000000000000000000;

    int i;
    for (i = 2; i > 0 && result[i] == 0; i--)
        continue;
    printf("%llu", result[i]);
    while (i-- > 0)
        printf("%18llu", result[i]);
    printf("\n");
    return 0;
}

下面是(3)的一个例子,范围较小:

#include <stdio.h>

int main() {
    unsigned long long a = 20170401000ULL;
    unsigned long long b = 20170401000ULL;
    unsigned long long result[3];

    // handle results up to 18446744065119617025999999999999999999
    // slice the operand into low and high parts
    unsigned long long a_lo = a % 1000000000;
    unsigned long long a_hi = a / 1000000000;
    unsigned long long b_lo = b % 1000000000;
    unsigned long long b_hi = b / 1000000000;
    // compute the partial products
    result[0] = a_lo * b_lo;
    result[1] = a_hi * b_lo + a_lo * b_hi;
    result[2] = a_hi * b_hi;
    // normalize result (propagate carry)
    result[1] += result[0] / 1000000000;
    result[0] %= 1000000000;
    result[2] += result[1] / 1000000000;
    result[1] %= 1000000000;

    int i;
    // ignore leading zeroes
    for (i = 2; i > 0 && result[i] == 0; i--)
        continue;
    // output the leading group of digits
    printf("%llu", result[i]);
    // output the trailing groups of 9 digits
    while (i-- > 0) {
        printf("%09llu", result[i]);
    }
    printf("\n");
    return 0;
}

以及将二进制计算和基10转换结合用于整个128位范围的最终方法:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

void mul64x64(uint32_t dest[4], uint64_t a, uint64_t b) {
    // using 32x32 -> 64 multiplications
    uint64_t low = (a & 0xFFFFFFFF) * (b & 0xFFFFFFFF);
    uint64_t mid1 = (a >> 32) * (b & 0xFFFFFFFF);
    uint64_t mid2 = (b >> 32) * (a & 0xFFFFFFFF);
    uint64_t high = (a >> 32) * (b >> 32);
    dest[0] = (uint32_t)low;
    mid1 += low >> 32;
    high += mid1 >> 32;
    mid2 += mid1 & 0xFFFFFFFF;
    dest[1] = (uint32_t)mid2;
    high += mid2 >> 32;
    dest[2] = (uint32_t)high;
    dest[3] = high >> 32;
}

uint32_t div_10p9(uint32_t dest[4]) {
    uint64_t num = 0;
    for (int i = 4; i-- > 0;) {
        num = (num << 32) + dest[i];
        dest[i] = num / 1000000000;
        num %= 1000000000;
    }
    return num;
}

int main() {
    uint32_t result[4];     // 128-bit multiplication result
    uint32_t base10[5];     // conversion to base10_9: pow(10,50) > pow(2,128)
    int i;

    mul64x64(result, 20170401000ULL, 20170401000ULL);
    for (i = 0; i < 5; i++) {
        base10[i] = div_10p9(result);
    }

    // ignore leading zeroes
    for (i = 4; i > 0 && base10[i] == 0; i--)
        continue;
    // output the leading group of digits
    printf("%"PRIu32, base10[i]);
    // output the trailing groups of 9 digits
    while (i-- > 0) {
        printf("%09"PRIu32, base10[i]);
    }
    printf("\n");
    return 0;
}

输出:

406845076500801000000
wko9yo5t

wko9yo5t2#

如果您需要存储更大的值,您可以使用外部库,例如GMP(GNU Multiple Precision Arithmetic Library),它提供了mpz_t和mpq_t等数据类型,可以处理任意精度的非常大的数字。这些数据类型可以存储任何大小的整数和分数,仅受可用内存的限制。希望这对你有帮助:)

bqf10yzr

bqf10yzr3#

由于已经给出了base 10n版本,base 2n版本稍微涉及更多:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>

/*
    Unsigned arguments to make it more versatile.
    It is easy to get from signed integers to unsigend ones (just safe
    the sign somewhere if you need it later) but not so much vice versa.
 */
static void mul64x64(const uint64_t a, const uint64_t b, uint64_t *high, uint64_t *low)
{
   uint32_t ah, al, bh, bl;
   uint64_t plh, phh, pll, phl;
   uint64_t carry = 0;

   ah = (a >> 32ull) & 0xFFFFFFFF;
   al = a & 0xFFFFFFFF;

   bh = (b >> 32ull) & 0xFFFFFFFF;
   bl = b & 0xFFFFFFFF;

   plh = (uint64_t)al * bh;
   phh = (uint64_t)ah * bh;
   pll = (uint64_t)al * bl;
   phl = (uint64_t)ah * bl;

   /*
      |  high   |   low   |
           | al * bh |
      | ah * bh | al * bl |
           | ah * bl |
    */

   *low = (pll) + ((plh & 0xFFFFFFFF)<<32ull) + ((phl & 0xFFFFFFFF) << 32ull);
   carry = ((pll >> 32ull) + (plh & 0xFFFFFFFF) + (phl & 0xFFFFFFFF)) >> 32ull;
   *high = phh + (phl >> 32ull) + (plh >> 32ull) + carry;
}

/* Division of 128 bit by 32 bits */
static void div64x64by32(const int64_t high, const uint64_t low, const uint32_t denominator,
                         int64_t *quotient_high, uint64_t *quotient_low, uint64_t *remainder)
{
   uint32_t a1, a2, a3, a4, q1, q2, q3, q4;
   uint64_t w, t, b;

   /*
      |    high    |     low    |
      |  a1  |  a2 |  a3  | a4  |
   */

   a1 = ((uint64_t)high) >> 32ull;
   a2 = ((uint64_t)high) & 0xFFFFFFFF;
   a3 = low >> 32ull;
   a4 = low & 0xFFFFFFFF;

   b = (uint64_t) denominator;
   w = 0ull;

   /*
       This is explained in detail in Tom St Denis "Multi-Precision Math"
       (ask google for "tommath.pdf") and implemented in libtommath:
       https://github.com/libtom/libtommath
       That is also the library to go if you cannot use GMP or similar
       bigint-libraries for legal (license) reasons.
    */
   /* Loop unrolled because we have individual digits */
   w = (w << 32ull) + a1;
   if (w >= b) {
      t = w / b;
      w = w % b;
   } else {
      t = 0;
   }
   q1 = (uint32_t)t;

   w = (w << 32ull) + a2;
   if (w >= b) {
      t = w / b;
      w = w % b;
   } else {
      t = 0;
   }
   q2 = (uint32_t)t;

   w = (w << 32ull) + a3;
   if (w >= b) {
      t = w / b;
      w = w % b;
   } else {
      t = 0;
   }
   q3 = (uint32_t)t;

   w = (w << 32ull) + a4;
   if (w >= b) {
      t = w / b;
      w = w % b;
   } else {
      t = 0;
   }
   q4 = (uint32_t)t;

   /* Gather the results */
   *quotient_high = (int64_t)q1 << 32ull;
   *quotient_high += (int64_t)q2;
   *quotient_low = (uint64_t)q3 << 32ull;
   *quotient_low += (uint64_t)q4;
   /* The remainder fits in an uint32_t but I didn't want to complicate it further */
   *remainder = w;
}

/*
   Reverse the given string in-place.

   Fiddling that apart is an exercise for the young student.
   Why it is a bad idea to do it that way is for the commenters
   at stackoverflow.
 */
static void strrev(char *str)
{
   char *end = str + strlen(str) - 1;
   while (str < end) {
      *str ^= *end;
      *end ^= *str;
      *str ^= *end;
      str++;
      end--;
   }
}

/* Assuming ASCII */
static char *print_high_low_64(const int64_t high, const uint64_t low)
{
   int sign;
   char *output, *str, c;
   int64_t h;
   uint64_t l, remainder;
   uint32_t base;

   /* TODO: checks&balances! And not only here! */

   sign = (high < 0) ? -1 : 1;
   h = (high < 0) ? -high : high;
   l = low;

   /* 64 bits in decimal are 20 digits plus room for the sign and EOS */
   output = malloc(2 * 20 + 1 + 1);
   if (output == NULL) {
      return NULL;
   }
   str = output;

   /*
      Yes, you can use other bases, too, but that gets more complicated,
      you need a small table. Either with all of the characters as they
      are or with a bunch of small constants to add to reach the individual
      character groups in ASCII.
      Hint: use a character table, it's much easier.
    */
   base = 10ul;

   /* Get the bits necessary to gather the digits one by one */
   for (;;) {
      div64x64by32(h, l, base, &h, &l, &remainder);
      /*
         ASCII has "0" at position 0x30 and the C standard guarantees
         all digits to be in consecutive order.
         EBCDIC has "0" at position 0xF0 and would need an uint8_t type.
       */
      c = (char)(remainder + 0x30);
      *str = c;
      str++;
      if ((h == 0ll) && (l == 0ull)) {
         break;
      }
   }
   /* Put sign in last */
   if (sign < 0) {
      *str = '-';
      str++;
   }
   /* Don't forget EOS! */
   *str = '\0';

   /* String is in reverse order. Reverse that. */
   strrev(output);

   return output;
}

int main(int argc, char **argv)
{
   int64_t a, b;
   uint64_t high, low;
   int sign = 1;
   char *s;

   if (argc == 3) {
      /* TODO: catch errors (see manpage, there is a full example at the end) */
      a = strtoll(argv[1], NULL, 10);
      b = strtoll(argv[2], NULL, 10);
   } else {
      fprintf(stderr,"Usage: %s integer integer\n",argv[0]);
      exit(EXIT_FAILURE);
   }

   printf("Input: %"PRId64" * %"PRId64"\n", a, b);

   /* Yes, that can be done a bit simpler, give it a try. */
   if (a < 0) {
      sign = -sign;
      a = -a;
   }

   if (b < 0) {
      sign = -sign;
      b = -b;
   }

   mul64x64((uint64_t)a, (uint64_t)b, &high, &low);
   /* Cannot loose information here, because we multiplied signed integers  */
   a = (int64_t)high * sign;

   printf("%"PRId64"  %"PRIu64"\n",a,low);
   /* Mmmh...that doesn't seem right. Why? The high part is off by 2^64! */

   /* We need to do it manually. */
   s = print_high_low_64(a, low);
   printf("%s\n",s);

   /* Clean up */
   free(s);
   exit(EXIT_SUCCESS);
}
/* clang -Weverything -g3 -O3 stack_bigmul.c -o stack_bigmul */

但是如果你选择一个2n碱基,它就更灵活了。您可以将上述代码中的类型与其他较小的类型交换,并使其在32位和16位MCU上工作。使用8位微控制器会稍微复杂一些,但不会那么复杂。

相关问题