home *** CD-ROM | disk | FTP | other *** search
- /*
- * This program demonstrates a method of performing basic arithmetic
- * operations (+ - * / %) on arbitrarily long unsigned BINARY numbers.
- *
- * The arithmetic is performed using only SHIFT, ADD and SUBTRACT
- * operations. These algorithms are ideally suitable for fast long
- * math functions written in assembler. They operate quite slowly
- * when written in 'C', due to the considerable overhead involved
- * in simulating the "Carry bit", which is used used to propogate
- * the multi-byte add, subtract and shifts.
- *
- * The numbers are stored and manipulated in memory based "registers".
- * These registers are accessed a byte at a time, and are stored in
- * "little endian" format (least significant byte occuring first in
- * memory). This is done for two reasons:
- * 1 - It makes it easier for most of the functions to manupulate
- * the register.
- * 2 - It makes it easy to define small constants in 'C', due to
- * the automatic zero filling on the right.
- * eg: unsigned char BIG_ONE[BIG_WIDTH] = { 1 };
- *
- * Copyright 1993-1994 Dave Dunfield
- * All rights reserved.
- *
- * Compile command: cc bignum -fop
- */
- #include <stdio.h>
-
- /*
- * To work with larger/smaller numbers, change this constant
- */
- #define BIG_WIDTH 8 /* 8 bytes (64 bits) */
-
- /*
- * This temporary register will contain the REMAINDER after a division,
- * and also a second copy of the product after a multiplication.
- */
- unsigned char big_temp[BIG_WIDTH];
-
- /*
- * Add two BIG values: n1 += n2
- * Returns carry out of big addition.
- */
- unsigned big_add(n1, n2)
- unsigned char *n1, *n2;
- {
- unsigned i, t;
-
- i = BIG_WIDTH;
- t = 0;
- do {
- t = (unsigned)*n1 + (unsigned)*n2++ + t;
- *n1++ = t;
- t = t >> 8; }
- while(--i);
- return t;
- }
-
- /*
- * Subtract two BIG values: n1 -= n2
- * Returns borrow out of big subtraction.
- */
- unsigned big_sub(n1, n2)
- unsigned char *n1, *n2;
- {
- unsigned i, t;
-
- i = BIG_WIDTH;
- t = 0;
- do {
- t = (unsigned)*n1 - (unsigned)*n2++ - t;
- *n1++ = t;
- t = (t >> 8) ? 1 : 0; }
- while(--i);
- return t;
- }
-
- /*
- * Shift a BIG value right: n1 >>= 1
- * 'c' is the carry in (1/0 shifted in on left)
- * Returns carry out of big shift.
- */
- unsigned char big_shift_right(n1, c)
- unsigned char *n1, c;
- {
- unsigned i;
- unsigned char c1;
-
- i = BIG_WIDTH;
- do {
- c1 = n1[--i];
- n1[i] = (c1 >> 1) | (c ? 0x80 : 0);
- c = c1 & 0x01; }
- while(i);
- return c;
- }
-
- /*
- * Shift a BIG value left: n1 <<= 1
- * 'c' is the carry in (1/0 shifted in on right)
- * Returns carry out of big shift.
- */
- unsigned char big_shift_left(n1, c)
- unsigned char *n1, c;
- {
- unsigned i;
- unsigned char c1;
-
- i = 0;
- do {
- c1 = n1[i];
- n1[i] = (c1 << 1) | (c ? 0x01 : 0);
- c = c1 & 0x80; }
- while(++i < BIG_WIDTH);
- return c;
- }
-
- /*
- * Test a BIG value for ZERO
- */
- int big_test(n1)
- unsigned char *n1;
- {
- unsigned i;
-
- i = BIG_WIDTH;
- do {
- if(*n1++)
- return 1; }
- while(--i);
- return 0;
- }
-
- /*
- * Compare two BIG values. Return 1 if n1 < n2
- */
- int big_compare(n1, n2)
- unsigned char *n1, *n2;
- {
- unsigned i;
-
- n1 += (i = BIG_WIDTH);
- n2 += BIG_WIDTH;
- do {
- if(*--n1 != *--n2)
- return *n1 < *n2; }
- while(--i);
- return 0;
- }
-
- /*
- * Multiply two BIG values: n1 *= n2
- * Product is also stored in big_temp
- */
- big_mul(n1, n2)
- unsigned char *n1, *n2;
- {
- unsigned char n3[BIG_WIDTH];
-
- /* Use a local copy of 'n2' to avoid destroying the original */
- memset(big_temp, 0, sizeof(big_temp));
- memcpy(n3, n2, sizeof(n3));
-
- do {
- if(big_shift_right(n1, 0))
- big_add(big_temp, n3);
- if(!big_test(n1))
- break;
- big_shift_left(n3, 0); }
- while(big_test(n3));
-
- memcpy(n1, big_temp, sizeof(big_temp));
- }
-
- /*
- * Perform a BIG divide: n1 /= n2
- * Remainder is stored in big_temp
- */
- big_div(n1, n2)
- unsigned char *n1, *n2;
- {
- unsigned t, cy;
-
- memset(big_temp, 0, sizeof(big_temp));
- t = (BIG_WIDTH*8) + 1;
-
- carry_zero:
- cy = 0;
- for(;;) {
- cy = big_shift_left(n1, cy);
- if(!--t)
- return;
- big_shift_left(big_temp, cy);
- if(big_compare(big_temp, n2))
- goto carry_zero;
- big_sub(big_temp, n2);
- cy = 1; }
- }
-
- /*
- * Convert BIG number to an ASCII string
- */
- big_to_string(string, n1, base)
- unsigned char *string, *n1, base;
- {
- unsigned sp;
- unsigned char n2[BIG_WIDTH], btemp[BIG_WIDTH], c, stack[(BIG_WIDTH*25)/10+1];
-
- /* Use a local copy of 'n1' to avoid destroying original */
- memcpy(n2, n1, sizeof(n2));
-
- memset(btemp, sp = 0, sizeof(btemp));
- *btemp = base;
-
- /* Stack up digits in reverse order */
- do {
- big_div(n2, btemp);
- if((c = *big_temp + '0') > '9')
- c += 7;
- stack[sp++] = c; }
- while(big_test(n2));
- /* Unstack digits into output buffer */
- do
- *string++ = stack[--sp];
- while(sp);
- *string = 0;
- }
-
- /*
- * Parse a string into a BIG number
- * Returns character terminating conversion (0 = end of string)
- */
- char string_to_big(string, n1, base)
- unsigned char *string, *n1, base;
- {
- unsigned char btemp[BIG_WIDTH], c;
-
- memset(btemp, 0, sizeof(btemp));
- memset(n1, 0, BIG_WIDTH);
-
- while(c = *string++) {
- if(isdigit(c)) /* Decimal digit */
- c -= '0';
- else if(c >= 'a') /* Lower-case alphabetic */
- c -= ('a' - 10);
- else if(c >= 'A') /* Upper-case alphabetic */
- c -= ('A' - 10);
- else /* Error - Invalid digit */
- break;
- if(c >= base) /* Error - out of range */
- break;
- *btemp = base;
- big_mul(n1, btemp); /* Make room */
- *btemp = c;
- big_add(n1, btemp); } /* Add new digit */
- return c;
- }
-
-
- /*
- * Demo program... A simple BIG number command line calculator
- */
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int i;
- char output[(BIG_WIDTH*25)/10+1];
- unsigned char buffer1[BIG_WIDTH], buffer2[BIG_WIDTH];
-
- if(argc < 2) {
- fputs("\nUse: bignum <num> [+-*/% <num> ...]\n", stderr);
- exit(-1); }
-
- /* Obtain first value from command line */
- string_to_big(argv[1], buffer1, 10);
-
- /* For each operator/number, get value & perform operation */
- for(i=2; i < argc; i += 2) {
- string_to_big(argv[i+1], buffer2, 10);
- switch(*argv[i]) {
- case '+' : big_add(buffer1, buffer2); break;
- case '-' : big_sub(buffer1, buffer2); break;
- case '*' : big_mul(buffer1, buffer2); break;
- case '/' : big_div(buffer1, buffer2); break;
- case '%' :
- big_div(buffer1, buffer2);
- memcpy(buffer1, big_temp, sizeof(buffer1));
- break;
- default:
- fputs("\nInvalid operator. Use: + - * / %\n", stderr);
- exit(-1); } }
-
- /* Display final result */
- big_to_string(output, buffer1, 10);
- printf("%s\n", output);
- }
-