As the base 10n version hase already been given, the base 2n version is slightly more involved:
#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 */
But if you choose a 2n base it is a bit more flexible. You can exchange the types in the above code with other, smaller ones and make it work on 32-bit and 16-bit MCUs. It is bit more complicated with 8-bit micro controllers, but not that much.