home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
hugs101.zip
/
hugs101sc.zip
/
hugsdist
/
src
/
bignums.c
next >
Wrap
C/C++ Source or Header
|
1995-03-02
|
18KB
|
690 lines
/* --------------------------------------------------------------------------
* bignums.c: Copyright (c) Mark P Jones 1991-1994. All rights reserved.
* See NOTICE for details and conditions of use etc...
* Hugs version 1.0 August 1994, derived from Gofer 2.30a
*
* Functions for manipulating Haskell Integers (bignums).
* ------------------------------------------------------------------------*/
/*#define DEBUG_BIGNUMS*/
Bignum bigInt Args((Int));
Bignum bigDouble Args((double));
Cell bigToInt Args((Bignum));
Cell bigToFloat Args((Bignum));
Bignum bigStr Args((String));
Bignum bigNeg Args((Bignum));
Bool bigEven Args((Bignum));
Int bigCmp Args((Bignum,Bignum));
Bignum bigAdd Args((Bignum,Bignum));
Bignum bigSub Args((Bignum,Bignum));
Bignum bigMul Args((Bignum,Bignum));
Bignum bigQrm Args((Bignum,Bignum));
#ifdef DEBUG_BIGNUMS
static Void local bigDump(List ds, Int n) {
while (nonNull(ds) && 0<n--) {
printf(":%04d",digitOf(hd(ds)));
ds = tl(ds);
}
}
#endif
/* --------------------------------------------------------------------------
* Local function prototypes:
* ------------------------------------------------------------------------*/
static Int local digitsCmp Args((List,List));
static Bignum local digitsAdd Args((Cell,List,List));
static Bignum local digitsSub Args((List,List));
static List local digitsQrm Args((List,List));
/*---------------------------------------------------------------------------
* Simple bignum primitives:
*-------------------------------------------------------------------------*/
Bignum bigInt(n) /* convert Int to bignum */
Int n; {
if (n==0)
return ZERONUM;
else {
unsigned long no;
Cell bn, nx;
if (n<0) {
no = (unsigned long)(-n);
bn = pair(NEGNUM,NIL);
}
else {
no = (unsigned long)(n);
bn = pair(POSNUM,NIL);
}
for (nx=bn; no>0; no/=BIGBASE, nx=tl(nx))
tl(nx) = pair(mkDigit(no%BIGBASE),NIL);
return bn;
}
}
Bignum bigDouble(d) /* convert double to bignum */
double d; {
if (d==0)
return ZERONUM;
else {
Cell bn, nx;
if (d<0) {
d = (-d);
bn = pair(NEGNUM,NIL);
}
else
bn = pair(POSNUM,NIL);
for (d=floor(d), nx=bn; d>0; nx=tl(nx)) {
double n = fmod(d,(double)(BIGBASE));
tl(nx) = pair(mkDigit(((Int)(n))),NIL);
d = (d - n) / BIGBASE;
}
return bn;
}
}
Cell bigToInt(n) /* convert bignum to Int */
Bignum n; {
if (n==ZERONUM)
return mkInt(0);
else {
Int m = 0;
List ds = snd(n);
List rs = NIL;
for (; nonNull(ds); ds=tl(ds))
rs = cons(hd(ds),rs);
for (; nonNull(rs); rs=tl(rs)) {
Int d = digitOf(hd(rs));
if (m > ((MAXPOSINT - d)/BIGBASE))
return NIL;
else
m = m * BIGBASE + d;
}
return mkInt(fst(n)==POSNUM ? m : (-m));
}
}
Cell bigToFloat(n) /* convert bignum to float */
Bignum n; {
if (n==ZERONUM)
return mkFloat((Float)0);
else {
Float m = 0;
List ds = snd(n);
List rs = NIL;
for (; nonNull(ds); ds=tl(ds))
rs = cons(hd(ds),rs);
for (; nonNull(rs); rs=tl(rs))
m = m * BIGBASE + digitOf(hd(rs));
return mkFloat(fst(n)==POSNUM ? m : (-m));
}
}
Bignum bigStr(s) /* convert String to bignum */
String s; {
List ds = NIL;
String t = s;
Int i;
if (*t == '-') /* look for leading minus */
t++; /* and ignore for time being */
if (i=strlen(t)%BIGEXP) {
Int d = 0;
while (0<i--)
d = d*10 + (*t++ - '0');
if (d)
ds = cons(mkDigit(d),NIL);
}
while (*t) { /* now scan rest of string */
Int d = 0;
for (i=BIGEXP; i>0; i--)
d = d*10 + (*t++ - '0');
if (nonNull(ds) || d)
ds = cons(mkDigit(d),ds);
}
return isNull(ds) ? ZERONUM : pair((*s=='-' ? NEGNUM : POSNUM), ds);
}
Cell bigOut(a,s,b) /* bignum output, prepend digits to*/
Bignum a; /* stream s */
Cell s;
Bool b; { /* TRUE => wrap neg int in parens */
if (a==ZERONUM)
return ap(consChar('0'),s);
else {
Bool isNeg = fst(a)==NEGNUM; /* keep record of sign */
List ds = snd(a); /* list of digits */
if (b && isNeg) /* print closing paren */
s = ap(consChar(')'),s);
for (;;) {
Int d = digitOf(hd(ds)); /* get next digit */
ds = tl(ds); /* move to next digit */
if (nonNull(ds)) { /* more digits to come after this */
Int i = BIGEXP;
for (; i>0; i--, d/=10)
s = ap(consChar('0'+(d%10)),s);
}
else { /* leading (non-zero) digits */
for (; d; d/=10)
s = ap(consChar('0'+(d%10)),s);
break;
}
}
if (isNeg) /* print minus sign */
s = ap(consChar('-'),s);
if (b && isNeg) /* print open paren */
s = ap(consChar('('),s);
return s;
}
}
Bignum bigNeg(a) /* unary negation */
Bignum a; {
if (a==ZERONUM)
return ZERONUM;
else
return pair(((fst(a)==POSNUM) ? NEGNUM : POSNUM), snd(a));
}
Bool bigEven(a) /* test for even number */
Bignum a; { /* ASSUMES BIGBASE is EVEN! */
return a==ZERONUM || (digitOf(hd(snd(a))) % 2 == 0) ;
}
/*---------------------------------------------------------------------------
* Bignum comparison routines:
*-------------------------------------------------------------------------*/
Int bigCmp(a,b) /* Compare bignums returning: */
Bignum a, b; { /* -1 if a<b, +1 if a>b, 0 o/w */
if (a==ZERONUM)
return (b==ZERONUM) ? 0 : ((fst(b)==POSNUM) ? (-1) : 1);
else if (fst(a)==NEGNUM)
if (b==ZERONUM || fst(b)==POSNUM)
return (-1);
else
return digitsCmp(snd(b),snd(a));
else
if (b==ZERONUM || fst(b)==NEGNUM)
return 1;
else
return digitsCmp(snd(a),snd(b));
}
static Int local digitsCmp(xs,ys) /* Compare positive digit streams */
List xs, ys; { /* -1 if xs<ys, +1 if xs>ys, 0 if= */
Int s = 0;
for (; nonNull(xs) && nonNull(ys); xs=tl(xs), ys=tl(ys)) {
Int x = hd(xs);
Int y = hd(ys);
if (x<y)
s = (-1);
else if (x>y)
s = 1;
}
return (nonNull(xs) ? 1 : (nonNull(ys) ? (-1) : s));
}
/*---------------------------------------------------------------------------
* Addition and subtraction:
*-------------------------------------------------------------------------*/
static Bignum local digitsAdd(sign,xs,ys)/* Addition of digit streams */
Cell sign;
List xs, ys; {
Cell bn = pair(sign,NIL);
Cell nx = bn;
Int c = 0;
for (;;) {
if (nonNull(xs)) { /* Add any digits to carry */
if (nonNull(ys)) {
c += digitOf(hd(xs)) + digitOf(hd(ys));
xs = tl(xs);
ys = tl(ys);
}
else if (c==0) { /* look for short cut when */
tl(nx) = xs; /* a stream ends and there */
break; /* is no outstanding carry */
}
else {
c += digitOf(hd(xs));
xs = tl(xs);
}
}
else if (c==0) {
tl(nx) = ys;
break;
}
else if (nonNull(ys)) {
c += digitOf(hd(ys));
ys = tl(ys);
}
if (c>=BIGBASE) { /* Calculate output digit */
nx = tl(nx) = cons(mkDigit(c-BIGBASE),NIL);
c = 1;
}
else { /* Carry will always be >0 */
nx = tl(nx) = cons(mkDigit(c),NIL); /* at this point */
c = 0;
}
}
return bn;
}
static Bignum local digitsSub(xs,ys) /* Subtraction of digit streams */
List xs, ys; {
Cell bn;
Cell nx;
Int b = 0;
Int lz = 0;
switch (digitsCmp(xs,ys)) {
case (-1) : nx = xs; /* if xs<ys, return -(ys-xs) */
xs = ys;
ys = nx;
bn = pair(NEGNUM,NIL);
break;
case 0 : return ZERONUM; /* if xs=ys, return 0 */
case 1 : bn = pair(POSNUM,NIL);
break; /* if xs>ys, return +(xs-ys) */
}
nx = bn; /* Now we can assume that xs>ys */
for (;;) { /* Scan each digit */
Int y = b;
if (nonNull(ys)) {
y += digitOf(hd(xs)) - digitOf(hd(ys));
xs = tl(xs);
ys = tl(ys);
}
else if (y==0) {
if (nonNull(xs))
for (; lz>0; lz--)
nx = tl(nx) = cons(mkDigit(0),NIL);
tl(nx) = xs;
break;
}
else {
y += digitOf(hd(xs)); /* xs>ys, so we can't run out of */
xs = tl(xs); /* digits of xs while y!=0 */
}
if (y<0) { /* Calculate output digit */
y += BIGBASE;
b = (-1);
}
else
b = 0;
if (y==0) /* Don't insert leading zeros */
lz++;
else {
for (; lz>0; lz--)
nx = tl(nx) = cons(mkDigit(0),NIL);
nx = tl(nx) = cons(mkDigit(y),NIL);
}
}
return bn;
}
Bignum bigAdd(a,b) /* Bignum addition */
Bignum a, b; {
if (a==ZERONUM)
return b;
else if (b==ZERONUM)
return a;
else if (fst(a)==POSNUM)
if (fst(b)==POSNUM)
return digitsAdd(POSNUM,snd(a),snd(b));
else
return digitsSub(snd(a),snd(b));
else /* fst(a)==NEGNUM */
if (fst(b)==NEGNUM)
return digitsAdd(NEGNUM,snd(a),snd(b));
else
return digitsSub(snd(b),snd(a));
}
Bignum bigSub(a,b) /* Bignum subtraction */
Bignum a, b; {
if (a==ZERONUM)
return bigNeg(b);
else if (b==ZERONUM)
return a;
else if (fst(a)==POSNUM)
if (fst(b)==NEGNUM)
return digitsAdd(POSNUM,snd(a),snd(b));
else
return digitsSub(snd(a),snd(b));
else /* fst(a)==NEGNUM */
if (fst(b)==POSNUM)
return digitsAdd(NEGNUM,snd(a),snd(b));
else
return digitsSub(snd(b),snd(a));
}
/*---------------------------------------------------------------------------
* Multiplication:
*-------------------------------------------------------------------------*/
Bignum bigMul(a,b) /* Calculate product of bignums a and b */
Bignum a, b; {
if (a==ZERONUM || b==ZERONUM) /* if either operand is zero, then */
return ZERONUM; /* so is the result ... */
else { /* otherwise, use rule of signs: */
Bignum bn = ap((hd(a)==hd(b) ? POSNUM : NEGNUM), NIL);
List nx = bn;
for (; nonNull(b=tl(b)); nx=tl(nx)) { /* loop through digits of b*/
List as = nx; /* At each stage of the loop, add */
List xs = tl(a); /* y * xs to the value in result, */
Int y = digitOf(hd(b)); /* using c as carry */
Int c = 0;
for (; nonNull(xs); xs=tl(xs)) { /* loop through digits of a*/
c += digitOf(hd(xs)) * y;
if (nonNull(tl(as))) {
as = tl(as);
c += digitOf(hd(as));
}
else
as = tl(as) = cons(NIL,NIL);
hd(as) = mkDigit(c % BIGBASE);
c /= BIGBASE;
}
if (c>0) /* add carry digit, if required */
tl(as) = cons(mkDigit(c),NIL);
}
return bn;
}
}
/*---------------------------------------------------------------------------
* Division:
*-------------------------------------------------------------------------*/
static List local bigRem = NIL; /* used to return remainder */
static List local digitsQrm(us,vs) /* digits quotient and remainder */
List us, vs; {
if (isNull(tl(vs))) { /* single digit divisor */
Int v = digitOf(hd(vs));
Int r = 0;
List us1 = NIL; /* first, copy and reverse us */
for (; nonNull(us); us=tl(us))
us1 = cons(hd(us),us1);
while (nonNull(us1)) { /* then do division, MSD first */
Cell tmp = tl(us1);
Int u = r * BIGBASE + digitOf(hd(us1));
r = u % v;
u = u / v;
if (nonNull(us) || u) { /* output quotient digit */
hd(us1) = mkDigit(u);
tl(us1) = us;
us = us1;
}
us1 = tmp;
}
bigRem = r ? singleton(mkDigit(r)) : NIL;
return us;
}
else { /* at least two digits in divisor */
/* The division algorithm used here is, inevitably, based on the
* description in Knuth's volume 2 on Seminumerical algorithms,
* and is probably at least as comprehensible as the MIX
* implementation given there. It took me a long time to
* figure that out, so let's just hope that I got it right!
*/
List us1 = NIL;
List vs1 = NIL;
List ds = us;
Int v1 = 0, v2 = 0;
Int uj = 0, uj1 = 0, uj2 = 0;
Int n = 0;
List qs = NIL;
Int sc;
while (nonNull(us) && nonNull(vs)) {
v2 = v1;
v1 = digitOf(hd(vs));
vs1 = cons(hd(vs),vs1);
vs = tl(vs);
uj2 = uj1;
uj1 = digitOf(hd(us));
us1 = cons(hd(us),us1);
us = tl(us);
n++;
}
if (nonNull(vs)) { /* if us is shorter than vs, then */
bigRem = ds; /* remainder is us, quotient zero */
return NIL;
}
vs = rev(vs1);
/* Now we have:
* n = number of digits in vs which is at least two (we
* also know that us has at least n digits),
* v1, v2 = most significant digits of vs
* vs = digits of vs with least significant digit first
*/
#ifdef DEBUG_BIGNUMS
printf("initial vs (n=%d, v1=%d, v2=%d): ",n,v1,v2);
bigDump(vs,n);
putchar('\n');
#endif
while (nonNull(us)) {
uj2 = uj1;
uj1 = digitOf(hd(us));
us1 = cons(hd(us),us1);
us = tl(us);
}
us = cons(mkDigit(uj=0),NIL);
ds = us1;
for (vs1=tl(vs); nonNull(vs1); vs1=tl(vs1)) {
us1 = tl(ds);
tl(ds) = us;
us = ds;
ds = us1;
}
/* And, at this point, we have:
* us = first (n-1) significant digits of original numerator,
* with least significant digit first, and a zero at the
* end (MSD) of the list, so that length us == n.
* ds = remaining digits of the numerator, most significant
* digit first.
* uj, uj1, uj2
* = first three significant digits of us. (At this point,
* uj is actually zero.)
*/
#ifdef DEBUG_BIGNUMS
printf("initial us (uj=%d, uj1=%d, uj2=%d): ",uj,uj1,uj2);
bigDump(us,n);
putchar('\n');
printf("initial ds: ");
bigDump(ds,1000);
putchar('\n');
#endif
sc = BIGBASE / (v1+1);
#ifdef DEBUG_BIGNUMS
printf("scaling factor %d\n",sc);
#endif
if (sc!=1) { /* Scale numerator and denominator */
Int c = 0;
v1 = v2 = 0;
for (vs1=vs; nonNull(vs1); vs1=tl(vs1)) {
v2 = v1;
v1 = sc * digitOf(hd(vs1)) + c;
c = v1 / BIGBASE;
hd(vs1) = mkDigit(v1%=BIGBASE);
} /* no carry here, guaranteed */
c = uj = uj1 = uj2 = 0;
for (us1=ds=rev(ds); nonNull(us1); us1=tl(us1)) {
uj2 = uj1;
uj1 = uj;
uj = sc * digitOf(hd(us1)) + c;
c = uj / BIGBASE;
hd(us1) = mkDigit(uj%=BIGBASE);
}
for (ds=rev(ds), us1=us; nonNull(us1); us1=tl(us1)) {
uj2 = uj1;
uj1 = uj;
uj = sc * digitOf(hd(us1)) + c;
c = uj / BIGBASE;
hd(us1) = mkDigit(uj%=BIGBASE);
} /* no carry here, guaranteed */
}
#ifdef DEBUG_BIGNUMS
printf("scaled vs (n=%d, v1=%d, v2=%d): ",n,v1,v2);
bigDump(vs,n);
putchar('\n');
printf("scaled us (uj=%d, uj1=%d, uj2=%d): ",uj,uj1,uj2);
bigDump(us,n);
putchar('\n');
printf("scaled ds: ");
bigDump(ds,1000);
putchar('\n');
#endif
/* Most of the conditions above are still valid, except that both
* the numerator and denominator have been multiplied by the scaling
* factor sc, and the values of the various digit positions have been
* updated accordingly.
*
* Now we can start the division algorithm proper:
*/
while (nonNull(ds)) {
Int c; /* Guess a value for quotient digit*/
Int qhat = (uj==v1) ? (BIGBASE-1) : (uj*BIGBASE+uj1)/v1;
while (v2*qhat > (uj*BIGBASE+uj1-qhat*v1)*BIGBASE+uj2)
qhat--; /* and then try to improve it */
us1 = tl(ds); /* take digit off off front of ds */
tl(ds) = us; /* and add to front of us */
us = ds;
ds = us1;
#ifdef DEBUG_BIGNUMS
printf("To divide us (uj=%d, uj1=%d, uj2=%d): ",uj,uj1,uj2);
bigDump(us,n+1);
printf(" by vs (v1=%d, v2=%d): ",v1,v2);
bigDump(vs,n);
printf(", guess qhat=%d\n",qhat);
#endif
uj = uj1 = uj2 = c = 0; /* us := us - qhat * vs */
us1 = us;
vs1 = vs;
do {
uj2 = uj1;
uj1 = uj;
uj = digitOf(hd(us1)) - qhat*digitOf(hd(vs1)) - c;
if (uj>=0)
c = 0;
else {
c = (BIGBASE - 1 - uj) / BIGBASE;
uj += c*BIGBASE;
}
hd(us1) = mkDigit(uj);
us1 = tl(us1);
vs1 = tl(vs1);
} while (nonNull(vs1));
if (digitOf(hd(us1))<c) { /* maybe we overestimated qhat? */
#ifdef DEBUG_BIGNUMS
printf("overestimated qhat by one!\n");
#endif
qhat--; /* so reduce guess */
uj = uj1 = uj2 = c = 0;/* and set us := us + vs */
us1 = us; /* (we can't have overestimated by */
vs1 = vs; /* more than one thanks to the */
do { /* initial scaling of us, vs by sc)*/
uj2 = uj1;
uj1 = uj;
uj = digitOf(hd(us1)) + digitOf(hd(vs1)) + c;
if (uj>=BIGBASE)
c = 1, uj -= BIGBASE;
else
c = 0;
hd(us1) = mkDigit(uj);
us1 = tl(us1);
vs1 = tl(vs1);
} while (nonNull(vs1));
}
#ifdef DEBUG_BIGNUMS
printf("There remains (uj=%d, uj1=%d, uj2=%d): ",uj,uj1,uj2);
bigDump(us,n);
putchar('\n');
#endif
if (nonNull(qs) || qhat) /* output quotient digit, without */
qs = cons(mkDigit(qhat),qs); /* leading zeros */
}
#ifdef DEBUG_BIGNUMS
printf("done quotient\n");
#endif
/* Now we have the quotient digits (if any) with least significant
* digit first in qs, and sc times the remainder is the first n
* digits of us. All that remains is to adjust the remainder:
*/
us1 = rev(take(n,us));
us = NIL;
uj = 0; /* reuse variable uj as a carry */
while (nonNull(us1)) {
Int y = uj * BIGBASE + digitOf(hd(us1));
uj = y % sc;
y /= sc;
if (nonNull(us) || y) {
vs1 = tl(us1);
tl(us1) = us;
hd(us1) = mkDigit(y);
us = us1;
us1 = vs1;
}
else
us1 = tl(us1);
}
bigRem = us;
return qs;
}
}
Cell bigQrm(a,b) /* bignum quotient and remainder */
Bignum a, b; {
if (a==ZERONUM)
return ap(ap(mkTuple(2),ZERONUM),ZERONUM);
else if (b!=ZERONUM) {
/* The sign of the remainder is positive if numerator and denominator
* have the same sign, negative if the signs differ. The sign of the
* quotient is always the same as the sign of the numerator.
*/
Cell qs = digitsQrm(snd(a),snd(b));
Cell rs = bigRem;
qs = isNull(qs) ? ZERONUM
: pair((fst(a)==fst(b) ? POSNUM : NEGNUM), qs);
rs = isNull(rs) ? ZERONUM : pair(hd(a),rs);
return ap(ap(mkTuple(2),qs),rs);
}
return NIL; /* indicates division by zero */
}
/*-------------------------------------------------------------------------*/