blob: ce4a24b1655d7729f39024c69411e558cd21e465 [file] [log] [blame]
#include "pdefs.h"
#include "precision.h"
#ifdef DEBUG
#include <stdio.h>
#endif
#ifdef ASM_16BIT
#include "asm16bit.h"
#endif
/*
* Divide u (dividend) by v (divisor); If non-null, qp and rp are set to
* quotient and remainder. The result returned will be *qp, unless qp is
* NULL, then *rp will be returned if non-null, otherwise pUndef is returned.
*
* Produce:
*
* q (quotient) = u div v (v != 0)
* truncation is toward zero
*
* r (remainder) = u mod v
* = u - u div v * v (v != 0)
* = u (v == 0)
* ( e.g. u == q*v + r )
* remainder has same sign and dividend
*
* Note: this has opposite convention than the C standard div fuction,
* but the same convention of the typical C "/" operator
* It is also inconvienient for the mod function.
*/
/*
* This algorithm is taken almost verbatum from Knuth Vol 2.
* Please note the following trivial(?) array index
* transformations (since MSD to LSD order is reversed):
*
* q[0..m] to Q[0..m] thus q[i] == Q[m-i]
* r[1..n] R[0..n-1] r[i] == R[n+1-i]
* u[0..m+n] w[0..m+n] u[i] == w[m+n-i]
* v[1..n] x[0..n-1] v[i] == x[n-i]
*
* let N == n - 1 so that n == N + 1 thus:
*
* q[0..m] to Q[0..m] thus q[i] == Q[m-i]
* r[1..n] R[0..N] r[i] == R[N+2-i]
* u[0..m+n] w[0..m+N+1] u[i] == w[m+N+1-i]
* v[1..n] x[0..N] v[i] == x[N+1-i]
*/
/*
* Note: Be very observent of the usage of uPtr, and vPtr.
* They are used to point to u, v, w, q or r as necessary.
*/
precision pdivmod(u, v, qp, rp)
precision u, v, *qp, *rp;
{
register digitPtr uPtr, vPtr, qPtr, LastPtr;
register accumulator temp; /* 0 <= temp < base^2 */
register digit carry; /* 0 <= carry < 2 */
register digit hi; /* 0 <= hi < base */
register posit n, m;
digit d; /* 0 <= d < base */
digit qd; /* 0 <= qd < base */
#ifdef DEBUG
int i;
#endif
precision q, r, w; /* quotient, remainder, temporary */
n = v->size; /* size of v and r */
(void) pparm(u);
(void) pparm(v);
if (u->size < n) {
q = pUndef;
r = pUndef;
pset(&q, pzero);
pset(&r, u);
goto done;
}
m = u->size - n;
uPtr = u->value + m + n;
vPtr = v->value + n;
q = palloc(m + 1);
if (q == pUndef) return q;
q->sign = (u->sign != v->sign); /* can generate -0 */
r = palloc(n);
if (r == pUndef) {
pdestroy(q);
return r;
}
r->sign = u->sign;
/*
* watch out! does this function return: q=floor(a/b) or trunc(a/b)?
* it's currently the latter, but every mathmaticion I have talked to
* prefers the former so that a % b returns between 0 to b-1. The
* problem is that this is slower and disagrees with C common practice.
*/
qPtr = q->value + m + 1;
if (n == 1) {
d = *--vPtr; /* d is only digit of v */
if (d == 0) { /* divide by zero? */
q = pnew(errorp(PDOMAIN, "pdivmod", "divide by zero"));
} else { /* single digit divide */
#ifndef ASM_16BIT
vPtr = r->value + n;
hi = 0; /* hi is current remainder */
do {
temp = mulBase(hi); /* 0 <= temp <= (base-1)^2 */
temp += *--uPtr; /* 0 <= temp <= base(base-1) */
hi = uModDiv(temp, d, --qPtr); /* 0 <= hi < base */
} while (uPtr > u->value);
*--vPtr = hi;
#else
qPtr -= m + 1;
*(r->value) = memdivw1(qPtr, u->value, m + 1, d);
#endif
}
} else { /* muti digit divide */
/*
* normalize: multiply u and v by d so hi digit of v > b/2
*/
d = BASE / (*--vPtr+1); /* high digit of v */
w = palloc(n); /* size of v */
if (w == pUndef) return w;
#ifndef ASM_16BIT
vPtr = v->value;
uPtr = w->value; /* very confusing. just a temp */
LastPtr = vPtr + n;
hi = 0;
do { /* single digit multiply */
temp = uMul(*vPtr++, d); /* 0<= temp <= base(base-1)/2 */
temp += hi; /* 0 <= temp <= (base^2-1)/2 */
hi = divBase(temp); /* 0 <= hi < base / 2 */
*uPtr++ = modBase(temp); /* 0 <= hi < base / 2 */
} while (vPtr < LastPtr); /* on exit hi == 0 */
#else
hi = memmulw1(w->value, v->value, n, d);
#endif
pset(&v, w);
pdestroy(w);
w = palloc(m + n + 1);
if (w == pUndef) return w;
#ifndef ASM_16BIT
uPtr = u->value;
vPtr = w->value; /* very confusing. just a temp */
LastPtr = uPtr + m + n;
do { /* single digit multiply */
temp = uMul(*uPtr++, d);
temp += hi;
hi = divBase(temp);
*vPtr++ = modBase(temp);
} while (uPtr < LastPtr);
*vPtr = hi; /* note extra digit */
#else
hi = memmulw1(w->value, u->value, m + n, d);
w->value[m + n] = hi;
#endif
pset(&u, w);
pdestroy(w);
#ifdef DEBUG
printf("m = %d n = %d\nd = %d\n", m, n, d);
printf("norm u = "); pshow(u);
printf("norm v = "); pshow(v);
#endif
uPtr = u->value + m + 1; /* current least significant digit */
do {
--uPtr;
#ifdef DEBUG
printf(" u = ");
for (i = n; i >= 0; --i) printf("%.*x ", sizeof(digit) * 2, uPtr[i]);
putchar('\n');
printf(" v = ");
for (i = 1; i < 3; i++) printf("%.*x ", sizeof(digit) * 2,
v->value[n-i]);
putchar('\n');
#endif
#ifndef ASM_16BIT
vPtr = v->value + n;
LastPtr = uPtr + n;
if (*LastPtr == *--vPtr) { /* guess next digit */
qd = BASE - 1;
} else {
temp = mulBase(*LastPtr);
temp += *--LastPtr; /* 0 <= temp< base^2 */
temp = uModDiv(temp, *vPtr, &qd);
--vPtr;
--LastPtr;
while (uMul(*vPtr, qd) > mulBase(temp) + *LastPtr) {
--qd;
temp += vPtr[1];
if (temp >= BASE) break; /* if so, vPtr*qd <= temp*base */
}
LastPtr += 2;
}
/*
* Single digit Multiply then Subtract
*/
vPtr = v->value;
carry = 1; /* noborrow bit */
hi = 0; /* hi digit of multiply */
do {
/* multiply */
temp = uMul(qd, *vPtr++); /* 0 <= temp <= (base-1)^2 */
temp += hi; /* 0 <= temp <= base(base-1) */
hi = divBase(temp);
temp = modBase(temp);
/* subtract */
temp = (BASE-1) - temp; /* 0 <= temp < base */
temp += *uPtr + carry; /* 0 <= temp < 2*base */
carry = divBase(temp);
*uPtr++ = modBase(temp); /* 0 <= carry < 2 */
} while (uPtr < LastPtr);
temp = (BASE-1) - hi;
temp += *uPtr + carry;
carry = divBase(temp);
*uPtr = modBase(temp);
uPtr -= n;
#else
#if 0
carry = !memmulsubw(uPtr, v->value, n, qd); /* 1 if noborrow */
#endif
carry = !memdivw(uPtr, v->value, n, &qd); /* 1 if noborrow */
#endif
#ifdef DEBUG
printf(" qhat = %.*x\n", sizeof(digit) * 2, qd);
printf(" new u = ");
for (i = n; i >= 0; --i) printf("%.*x ", sizeof(digit) * 2, uPtr[i]);
putchar('\n');
#endif
if (carry == 0) { /* Test remainder, add back */
vPtr = v->value;
LastPtr = uPtr + n;
do {
temp = *uPtr + *vPtr++;
temp += carry;
carry = divBase(temp);
*uPtr++ = modBase(temp);
} while (uPtr < LastPtr);
*uPtr += carry - BASE; /* real strange but works */
uPtr -= n;
--qd;
#ifdef DEBUG
printf(" decrementing q...adding back\n");
printf(" fixed u = ");
for (i = n; i >= 0; --i) printf("%.*x ", sizeof(digit) * 2, uPtr[i]);
putchar('\n');
printf(" newq = %.*x\n", sizeof(digit) * 2, qd);
#endif
}
*--qPtr = qd; /* one leading zero possible */
#ifdef DEBUG
putchar('\n');
#endif
} while (uPtr > u->value);
/*
* Un-normalize to get remainder
*/
#ifndef ASM_16BIT
uPtr = u->value + n; /* skip hi digit (it's zero) */
vPtr = r->value + n;
hi = 0; /* hi is current remainder */
do { /* single digit divide */
temp = mulBase(hi); /* 0<=temp < base^2-(base-1) */
temp += *--uPtr; /* 0 <= temp < base^2 */
hi = uModDiv(temp, d, --vPtr);
} while (uPtr > u->value); /* carry will be zero */
#else
carry = memdivw1(r->value, u->value, n, d); /* always 0 */
#endif
pnorm(r); /* remainder may have many leading 0's */
}
if (m > 0 && qPtr[m] == 0) {
--(q->size); /* normalize */
}
if (q->size == 1 && *qPtr == 0) q->sign = false;
done:
pdestroy(u);
pdestroy(v);
if (rp == (precision *) -1) {
if (qp != pNull) pset(qp, q);
pdestroy(q);
return presult(r);
} else if (qp == (precision *) -1) {
if (rp != pNull) pset(rp, r);
pdestroy(r);
return presult(q);
}
if (qp != pNull) pset(qp, q);
if (rp != pNull) pset(rp, r);
pdestroy(q);
pdestroy(r);
return pUndef;
}