diff options
Diffstat (limited to 'lisp/mp/mpi.c')
-rw-r--r-- | lisp/mp/mpi.c | 1656 |
1 files changed, 1656 insertions, 0 deletions
diff --git a/lisp/mp/mpi.c b/lisp/mp/mpi.c new file mode 100644 index 0000000..506dc7e --- /dev/null +++ b/lisp/mp/mpi.c @@ -0,0 +1,1656 @@ +/* + * Copyright (c) 2002 by The XFree86 Project, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of the XFree86 Project shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization from the + * XFree86 Project. + * + * Author: Paulo César Pereira de Andrade + */ + +/* $XFree86: xc/programs/xedit/lisp/mp/mpi.c,v 1.12 2002/11/20 07:44:43 paulo Exp $ */ + +#include "mp.h" + +/* + * Prototypes + */ + /* do the hard work of mpi_add and mpi_sub */ +static void mpi_addsub(mpi *rop, mpi *op1, mpi *op2, int sub); + + /* logical functions implementation */ +static INLINE BNS mpi_logic(BNS op1, BNS op2, BNS op); +static void mpi_log(mpi *rop, mpi *op1, mpi *op2, BNS op); + + /* internal mpi_seti, whithout memory allocation */ +static void _mpi_seti(mpi *rop, long si); + +/* + * Initialization + */ +static BNS onedig[1] = { 1 }; +static mpi mpone = { 1, 1, 0, (BNS*)&onedig }; + +/* + * Implementation + */ +void +mpi_init(mpi *op) +{ + op->sign = 0; + op->size = op->alloc = 1; + op->digs = mp_malloc(sizeof(BNS)); + op->digs[0] = 0; +} + +void +mpi_clear(mpi *op) +{ + op->sign = 0; + op->size = op->alloc = 0; + mp_free(op->digs); +} + +void +mpi_set(mpi *rop, mpi *op) +{ + if (rop != op) { + if (rop->alloc < op->size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * op->size); + rop->alloc = op->size; + } + rop->size = op->size; + memcpy(rop->digs, op->digs, sizeof(BNS) * op->size); + rop->sign = op->sign; + } +} + +void +mpi_seti(mpi *rop, long si) +{ + unsigned long ui; + int sign = si < 0; + int size; + + if (si == MINSLONG) { + ui = MINSLONG; + size = 2; + } + else { + if (sign) + ui = -si; + else + ui = si; + if (ui < CARRY) + size = 1; + else + size = 2; + } + + if (rop->alloc < size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * size); + rop->alloc = size; + } + rop->size = size; + + /* store data in small mp integer */ + rop->digs[0] = (BNS)ui; + if (size > 1) + rop->digs[1] = (BNS)(ui >> BNSBITS); + rop->size = size; + + /* adjust result sign */ + rop->sign = sign; +} + +static void +_mpi_seti(mpi *rop, long si) +{ + unsigned long ui; + int sign = si < 0; + int size; + + if (si == MINSLONG) { + ui = MINSLONG; + size = 2; + } + else { + if (sign) + ui = -si; + else + ui = si; + if (ui < CARRY) + size = 1; + else + size = 2; + } + + rop->digs[0] = (BNS)ui; + if (size > 1) + rop->digs[1] = (BNS)(ui >> BNSBITS); + rop->size = size; + + rop->sign = sign; +} + +void +mpi_setd(mpi *rop, double d) +{ + long i; + double mantissa; + int shift, exponent; + BNI size; + + if (isnan(d)) + d = 0.0; + else if (!finite(d)) + d = copysign(1.0, d) * DBL_MAX; + + /* check if number is larger than 1 */ + if (fabs(d) < 1.0) { + rop->digs[0] = 0; + rop->size = 1; + rop->sign = d < 0.0; + + return; + } + + mantissa = frexp(d, &exponent); + if (mantissa < 0) + mantissa = -mantissa; + + size = (exponent + (BNSBITS - 1)) / BNSBITS; + shift = BNSBITS - (exponent & (BNSBITS - 1)); + + /* adjust amount of memory */ + if (rop->alloc < size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * size); + rop->alloc = size; + } + rop->size = size; + + /* adjust the exponent */ + if (shift < BNSBITS) + mantissa = ldexp(mantissa, -shift); + + /* convert double */ + for (i = size - 1; i >= 0 && mantissa != 0.0; i--) { + mantissa = ldexp(mantissa, BNSBITS); + rop->digs[i] = (BNS)mantissa; + mantissa -= rop->digs[i]; + } + for (; i >= 0; i--) + rop->digs[i] = 0; + + /* normalize */ + if (size > 1 && rop->digs[size - 1] == 0) + --rop->size; + + rop->sign = d < 0.0; +} + +/* how many BNS in the given base, log(base) / log(CARRY) */ +#ifdef LONG64 +static double str_bases[37] = { + 0.0000000000000000, 0.0000000000000000, 0.0312500000000000, + 0.0495300781475362, 0.0625000000000000, 0.0725602529652301, + 0.0807800781475362, 0.0877298413143002, 0.0937500000000000, + 0.0990601562950723, 0.1038102529652301, 0.1081072380824156, + 0.1120300781475362, 0.1156387411919092, 0.1189798413143002, + 0.1220903311127662, 0.1250000000000000, 0.1277332137890731, + 0.1303101562950723, 0.1327477347951120, 0.1350602529652300, + 0.1372599194618363, 0.1393572380824156, 0.1413613111267817, + 0.1432800781475362, 0.1451205059304602, 0.1468887411919092, + 0.1485902344426084, 0.1502298413143002, 0.1518119060977367, + 0.1533403311127662, 0.1548186346995899, 0.1562500000000000, + 0.1576373162299517, 0.1589832137890731, 0.1602900942795302, + 0.1615601562950723, +}; +#else +static double str_bases[37] = { + 0.0000000000000000, 0.0000000000000000, 0.0625000000000000, + 0.0990601562950723, 0.1250000000000000, 0.1451205059304602, + 0.1615601562950723, 0.1754596826286003, 0.1875000000000000, + 0.1981203125901446, 0.2076205059304602, 0.2162144761648311, + 0.2240601562950723, 0.2312774823838183, 0.2379596826286003, + 0.2441806622255325, 0.2500000000000000, 0.2554664275781462, + 0.2606203125901445, 0.2654954695902241, 0.2701205059304602, + 0.2745198389236725, 0.2787144761648311, 0.2827226222535633, + 0.2865601562950723, 0.2902410118609203, 0.2937774823838183, + 0.2971804688852168, 0.3004596826286003, 0.3036238121954733, + 0.3066806622255324, 0.3096372693991797, 0.3125000000000000, + 0.3152746324599034, 0.3179664275781462, 0.3205801885590604, + 0.3231203125901446, +}; +#endif + +void +mpi_setstr(mpi *rop, char *str, int base) +{ + long i; /* counter */ + int sign; /* result sign */ + BNI carry; /* carry value */ + BNI value; /* temporary value */ + BNI size; /* size of result */ + char *ptr; /* end of valid input */ + + /* initialization */ + sign = 0; + carry = 0; + + /* skip leading spaces */ + while (isspace(*str)) + ++str; + + /* check if sign supplied */ + if (*str == '-') { + sign = 1; + ++str; + } + else if (*str == '+') + ++str; + + /* skip leading zeros */ + while (*str == '0') + ++str; + + ptr = str; + while (*ptr) { + if (*ptr >= '0' && *ptr <= '9') { + if (*ptr - '0' >= base) + break; + } + else if (*ptr >= 'A' && *ptr <= 'Z') { + if (*ptr - 'A' + 10 >= base) + break; + } + else if (*ptr >= 'a' && *ptr <= 'z') { + if (*ptr - 'a' + 10 >= base) + break; + } + else + break; + ++ptr; + } + + /* resulting size */ + size = (ptr - str) * str_bases[base] + 1; + + /* make sure rop has enough storage */ + if (rop->alloc < size) { + rop->digs = mp_realloc(rop->digs, size * sizeof(BNS)); + rop->alloc = size; + } + rop->size = size; + + /* initialize rop to zero */ + memset(rop->digs, '\0', size * sizeof(BNS)); + + /* set result sign */ + rop->sign = sign; + + /* convert string */ + for (; str < ptr; str++) { + value = *str; + if (islower(value)) + value = toupper(value); + value = value > '9' ? value - 'A' + 10 : value - '0'; + value += rop->digs[0] * base; + carry = value >> BNSBITS; + rop->digs[0] = (BNS)value; + for (i = 1; i < size; i++) { + value = (BNI)rop->digs[i] * base + carry; + carry = value >> BNSBITS; + rop->digs[i] = (BNS)value; + } + } + + /* normalize */ + if (rop->size > 1 && rop->digs[rop->size - 1] == 0) + --rop->size; +} + +void +mpi_add(mpi *rop, mpi *op1, mpi *op2) +{ + mpi_addsub(rop, op1, op2, 0); +} + +void +mpi_addi(mpi *rop, mpi *op1, long op2) +{ + BNS digs[2]; + mpi op; + + op.digs = (BNS*)digs; + _mpi_seti(&op, op2); + + mpi_addsub(rop, op1, &op, 0); +} + +void +mpi_sub(mpi *rop, mpi *op1, mpi *op2) +{ + mpi_addsub(rop, op1, op2, 1); +} + +void +mpi_subi(mpi *rop, mpi *op1, long op2) +{ + BNS digs[2]; + mpi op; + + op.digs = (BNS*)digs; + _mpi_seti(&op, op2); + + mpi_addsub(rop, op1, &op, 1); +} + +static void +mpi_addsub(mpi *rop, mpi *op1, mpi *op2, int sub) +{ + long xlen; /* maximum result size */ + + if (sub ^ (op1->sign == op2->sign)) { + /* plus one for possible carry */ + xlen = MAX(op1->size, op2->size) + 1; + if (rop->alloc < xlen) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * xlen); + rop->alloc = xlen; + } + rop->size = mp_add(rop->digs, op1->digs, op2->digs, + op1->size, op2->size); + rop->sign = op1->sign; + } + else { + long cmp; /* check for larger operator */ + + cmp = mpi_cmpabs(op1, op2); + if (cmp == 0) { + rop->digs[0] = 0; + rop->size = 1; + rop->sign = 0; + } + else { + xlen = MAX(op1->size, op2->size); + if (rop->alloc < xlen) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * xlen); + rop->alloc = xlen; + } + if (cmp > 0) { + rop->size = mp_sub(rop->digs, op1->digs, op2->digs, + op1->size, op2->size); + rop->sign = op1->sign; + } + else { + rop->size = mp_sub(rop->digs, op2->digs, op1->digs, + op2->size, op1->size); + rop->sign = sub ^ op2->sign; + } + } + } +} + +void +mpi_mul(mpi *rop, mpi *op1, mpi *op2) +{ + int sign; /* sign flag */ + BNS *digs; /* result data */ + long xsize; /* result size */ + + /* get result sign */ + sign = op1->sign ^ op2->sign; + + /* check for special cases */ + if (op1->size == 1) { + if (*op1->digs == 0) { + /* multiply by 0 */ + mpi_seti(rop, 0); + return; + } + else if (*op1->digs == 1) { + /* multiply by +-1 */ + if (rop->alloc < op2->size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * op2->size); + rop->alloc = op2->size; + } + rop->size = op2->size; + memmove(rop->digs, op2->digs, sizeof(BNS) * op2->size); + rop->sign = op2->size > 1 || *op2->digs ? sign : 0; + + return; + } + } + else if (op2->size == 1) { + if (*op2->digs == 0) { + /* multiply by 0 */ + mpi_seti(rop, 0); + return; + } + else if (*op2->digs == 1) { + /* multiply by +-1 */ + if (rop->alloc < op1->size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * op1->size); + rop->alloc = op1->size; + } + rop->size = op1->size; + memmove(rop->digs, op1->digs, sizeof(BNS) * op1->size); + rop->sign = op1->size > 1 || *op1->digs ? sign : 0; + + return; + } + } + + /* allocate result data and set it to zero */ + xsize = op1->size + op2->size; + if (rop->digs == op1->digs || rop->digs == op2->digs) + /* rop is also an operand */ + digs = mp_calloc(1, sizeof(BNS) * xsize); + else { + if (rop->alloc < xsize) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * xsize); + rop->alloc = xsize; + } + digs = rop->digs; + memset(digs, '\0', sizeof(BNS) * xsize); + } + + /* multiply operands */ + xsize = mp_mul(digs, op1->digs, op2->digs, op1->size, op2->size); + + /* store result in rop */ + if (digs != rop->digs) { + /* if rop was an operand, free old data */ + mp_free(rop->digs); + rop->digs = digs; + } + rop->size = xsize; + + /* set result sign */ + rop->sign = sign; +} + +void +mpi_muli(mpi *rop, mpi *op1, long op2) +{ + BNS digs[2]; + mpi op; + + op.digs = (BNS*)digs; + _mpi_seti(&op, op2); + + mpi_mul(rop, op1, &op); +} + +void +mpi_div(mpi *rop, mpi *num, mpi *den) +{ + mpi_divqr(rop, NULL, num, den); +} + +void +mpi_rem(mpi *rop, mpi *num, mpi *den) +{ + mpi_divqr(NULL, rop, num, den); +} + +/* + * Could/should be changed to not allocate qdigs if qrop is NULL + * Performance wouldn't suffer too much with a test on every loop iteration. + */ +void +mpi_divqr(mpi *qrop, mpi *rrop, mpi *num, mpi *den) +{ + long i, j; /* counters */ + int qsign; /* sign of quotient */ + int rsign; /* sign of remainder */ + BNI qsize; /* size of quotient */ + BNI rsize; /* size of remainder */ + BNS qest; /* estimative of quotient value */ + BNS *qdigs, *rdigs; /* work copy or result */ + BNS *ndigs, *ddigs; /* work copy or divisor and dividend */ + BNI value; /* temporary result */ + long svalue; /* signed temporary result (2's complement) */ + BNS carry, scarry, denorm; /* carry and normalization */ + BNI dpos, npos; /* offsets in data */ + + /* get signs */ + rsign = num->sign; + qsign = rsign ^ den->sign; + + /* check for special case */ + if (num->size < den->size) { + /* quotient is zero and remainder is numerator */ + if (rrop && rrop->digs != num->digs) { + if (rrop->alloc < num->size) { + rrop->digs = mp_realloc(rrop->digs, sizeof(BNS) * num->size); + rrop->alloc = num->size; + } + rrop->size = num->size; + memcpy(rrop->digs, num->digs, sizeof(BNS) * num->size); + rrop->sign = rsign; + } + if (qrop) + mpi_seti(qrop, 0); + + return; + } + + /* estimate result sizes */ + rsize = den->size; + qsize = num->size - den->size + 1; + + /* offsets */ + npos = num->size - 1; + dpos = den->size - 1; + + /* allocate space for quotient and remainder */ + if (qrop == NULL || qrop->digs == num->digs || qrop->digs == den->digs) + qdigs = mp_calloc(1, sizeof(BNS) * qsize); + else { + if (qrop->alloc < qsize) { + qrop->digs = mp_realloc(qrop->digs, sizeof(BNS) * qsize); + qrop->alloc = qsize; + } + memset(qrop->digs, '\0', sizeof(BNS) * qsize); + qdigs = qrop->digs; + } + if (rrop) { + if (rrop->digs == num->digs || rrop->digs == den->digs) + rdigs = mp_calloc(1, sizeof(BNS) * rsize); + else { + if (rrop->alloc < rsize) { + rrop->digs = mp_realloc(rrop->digs, sizeof(BNS) * rsize); + rrop->alloc = rsize; + } + memset(rrop->digs, '\0', sizeof(BNS) * rsize); + rdigs = rrop->digs; + } + } + else + rdigs = NULL; /* fix gcc warning */ + + /* special case, only one word in divisor */ + if (dpos == 0) { + for (carry = 0, i = npos; i >= 0; i--) { + value = ((BNI)carry << BNSBITS) + num->digs[i]; + qdigs[i] = (BNS)(value / den->digs[0]); + carry = (BNS)(value % den->digs[0]); + } + if (rrop) + rdigs[0] = carry; + + goto mpi_divqr_done; + } + + /* make work copy of numerator */ + ndigs = mp_malloc(sizeof(BNS) * (num->size + 1)); + /* allocate one extra word an update offset */ + memcpy(ndigs, num->digs, sizeof(BNS) * num->size); + ndigs[num->size] = 0; + ++npos; + + /* normalize */ + denorm = (BNS)((BNI)CARRY / ((BNI)(den->digs[dpos]) + 1)); + + if (denorm > 1) { + /* i <= num->size because ndigs has an extra word */ + for (carry = 0, i = 0; i <= num->size; i++) { + value = ndigs[i] * (BNI)denorm + carry; + ndigs[i] = (BNS)value; + carry = (BNS)(value >> BNSBITS); + } + /* make work copy of denominator */ + ddigs = mp_malloc(sizeof(BNS) * den->size); + memcpy(ddigs, den->digs, sizeof(BNS) * den->size); + for (carry = 0, i = 0; i < den->size; i++) { + value = ddigs[i] * (BNI)denorm + carry; + ddigs[i] = (BNS)value; + carry = (BNS)(value >> BNSBITS); + } + } + else + /* only allocate copy of denominator if going to change it */ + ddigs = den->digs; + + /* divide mp integers */ + for (j = qsize - 1; j >= 0; j--, npos--) { + /* estimate quotient */ + if (ndigs[npos] == ddigs[dpos]) + qest = (BNS)SMASK; + else + qest = (BNS)((((BNI)(ndigs[npos]) << 16) + ndigs[npos - 1]) / + ddigs[dpos]); + + while ((value = ((BNI)(ndigs[npos]) << 16) + ndigs[npos - 1] - + qest * (BNI)(ddigs[dpos])) < CARRY && + ddigs[dpos - 1] * (BNI)qest > + (value << BNSBITS) + ndigs[npos - 2]) + --qest; + + /* multiply and subtract */ + carry = scarry = 0; + for (i = 0; i < den->size; i++) { + value = qest * (BNI)ddigs[i] + carry; + carry = (BNS)(value >> BNSBITS); + svalue = (long)ndigs[npos - dpos + i - 1] - (long)(value & SMASK) - + (long)scarry; + ndigs[npos - dpos + i - 1] = (BNS)svalue; + scarry = svalue < 0; + } + + svalue = (long)ndigs[npos] - (long)(carry & SMASK) - (long)scarry; + ndigs[npos] = (BNS)svalue; + + if (svalue & LMASK) { + /* quotient too big */ + --qest; + carry = 0; + for (i = 0; i < den->size; i++) { + value = ndigs[npos - dpos + i - 1] + (BNI)carry + (BNI)ddigs[i]; + ndigs[npos - dpos + i - 1] = (BNS)value; + carry = (BNS)(value >> BNSBITS); + } + ndigs[npos] += carry; + } + + qdigs[j] = qest; + } + + /* calculate remainder */ + if (rrop) { + for (carry = 0, j = dpos; j >= 0; j--) { + value = ((BNI)carry << BNSBITS) + ndigs[j]; + rdigs[j] = (BNS)(value / denorm); + carry = (BNS)(value % denorm); + } + } + + mp_free(ndigs); + if (ddigs != den->digs) + mp_free(ddigs); + +mpi_divqr_done: + if (rrop) { + if (rrop->digs != rdigs) + mp_free(rrop->digs); + /* normalize remainder */ + for (i = rsize - 1; i >= 0; i--) + if (rdigs[i] != 0) + break; + if (i != rsize - 1) { + if (i < 0) { + rsign = 0; + rsize = 1; + } + else + rsize = i + 1; + } + rrop->digs = rdigs; + rrop->sign = rsign; + rrop->size = rsize; + } + + /* normalize quotient */ + if (qrop) { + if (qrop->digs != qdigs) + mp_free(qrop->digs); + for (i = qsize - 1; i >= 0; i--) + if (qdigs[i] != 0) + break; + if (i != qsize - 1) { + if (i < 0) { + qsign = 0; + qsize = 1; + } + else + qsize = i + 1; + } + qrop->digs = qdigs; + qrop->sign = qsign; + qrop->size = qsize; + } + else + mp_free(qdigs); +} + +long +mpi_divqri(mpi *qrop, mpi *num, long den) +{ + BNS ddigs[2]; + mpi dop, rrop; + long remainder; + + dop.digs = (BNS*)ddigs; + _mpi_seti(&dop, den); + + memset(&rrop, '\0', sizeof(mpi)); + mpi_init(&rrop); + mpi_divqr(qrop, &rrop, num, &dop); + remainder = rrop.digs[0]; + if (rrop.size > 1) + remainder |= (BNI)(rrop.digs[1]) << BNSBITS; + if (rrop.sign) + remainder = -remainder; + mpi_clear(&rrop); + + return (remainder); +} + +void +mpi_divi(mpi *rop, mpi *num, long den) +{ + BNS ddigs[2]; + mpi dop; + + dop.digs = (BNS*)ddigs; + _mpi_seti(&dop, den); + + mpi_divqr(rop, NULL, num, &dop); +} + +long +mpi_remi(mpi *num, long den) +{ + return (mpi_divqri(NULL, num, den)); +} + +void +mpi_mod(mpi *rop, mpi *num, mpi *den) +{ + mpi_rem(rop, num, den); + if (num->sign ^ den->sign) + mpi_add(rop, rop, den); +} + +long +mpi_modi(mpi *num, long den) +{ + long remainder; + + remainder = mpi_remi(num, den); + if (num->sign ^ (den < 0)) + remainder += den; + + return (remainder); +} + +void +mpi_gcd(mpi *rop, mpi *num, mpi *den) +{ + long cmp; + mpi rem; + + /* check if result already given */ + cmp = mpi_cmpabs(num, den); + + /* check if num is equal to den or if num is zero */ + if (cmp == 0 || (num->size == 1 && num->digs[0] == 0)) { + mpi_set(rop, den); + rop->sign = 0; + return; + } + /* check if den is not zero */ + if (den->size == 1 && den->digs[0] == 0) { + mpi_set(rop, num); + rop->sign = 0; + return; + } + + /* don't call mpi_init, relies on realloc(0, size) == malloc(size) */ + memset(&rem, '\0', sizeof(mpi)); + + /* if num larger than den */ + if (cmp > 0) { + mpi_rem(&rem, num, den); + if (rem.size == 1 && rem.digs[0] == 0) { + /* exact division */ + mpi_set(rop, den); + rop->sign = 0; + mpi_clear(&rem); + return; + } + mpi_set(rop, den); + } + else { + mpi_rem(&rem, den, num); + if (rem.size == 1 && rem.digs[0] == 0) { + /* exact division */ + mpi_set(rop, num); + rop->sign = 0; + mpi_clear(&rem); + return; + } + mpi_set(rop, num); + } + + /* loop using positive values */ + rop->sign = rem.sign = 0; + + /* cannot optimize this inverting rem/rop assignment earlier + * because rop mais be an operand */ + mpi_swap(rop, &rem); + + /* Euclides algorithm */ + for (;;) { + mpi_rem(&rem, &rem, rop); + if (rem.size == 1 && rem.digs[0] == 0) + break; + mpi_swap(rop, &rem); + } + mpi_clear(&rem); +} + +void +mpi_lcm(mpi *rop, mpi *num, mpi *den) +{ + mpi gcd; + + /* check for zero operand */ + if ((num->size == 1 && num->digs[0] == 0) || + (den->size == 1 && den->digs[0] == 0)) { + rop->digs[0] = 0; + rop->sign = 0; + return; + } + + /* don't call mpi_init, relies on realloc(0, size) == malloc(size) */ + memset(&gcd, '\0', sizeof(mpi)); + + mpi_gcd(&gcd, num, den); + mpi_div(&gcd, den, &gcd); + mpi_mul(rop, &gcd, num); + rop->sign = 0; + + mpi_clear(&gcd); +} + +void +mpi_pow(mpi *rop, mpi *op, unsigned long exp) +{ + mpi zop, top; + + if (exp == 2) { + mpi_mul(rop, op, op); + return; + } + /* check for op**0 */ + else if (exp == 0) { + rop->digs[0] = 1; + rop->size = 1; + rop->sign = 0; + return; + } + else if (exp == 1) { + mpi_set(rop, op); + return; + } + else if (op->size == 1) { + if (op->digs[0] == 0) { + mpi_seti(rop, 0); + return; + } + else if (op->digs[0] == 1) { + mpi_seti(rop, op->sign && (exp & 1) ? -1 : 1); + return; + } + } + + memset(&zop, '\0', sizeof(mpi)); + memset(&top, '\0', sizeof(mpi)); + mpi_set(&zop, op); + mpi_set(&top, op); + for (--exp; exp; exp >>= 1) { + if (exp & 1) + mpi_mul(&zop, &top, &zop); + mpi_mul(&top, &top, &top); + } + + mpi_clear(&top); + rop->sign = zop.sign; + mp_free(rop->digs); + rop->digs = zop.digs; + rop->size = zop.size; +} + +/* Find integer root of given number using the iteration + * x{n+1} = ((K-1) * x{n} + N / x{n}^(K-1)) / K + */ +int +mpi_root(mpi *rop, mpi *op, unsigned long nth) +{ + long bits, cmp; + int exact; + int sign; + mpi *r, t, temp, quot, old, rem; + + sign = op->sign; + + /* divide by zero op**1/0 */ + if (nth == 0) { + int one = 1, zero = 0; + one = one / zero; + } + /* result is complex */ + if (sign && !(nth & 1)) { + int one = 1, zero = 0; + one = one / zero; + } + + /* special case op**1/1 = op */ + if (nth == 1) { + mpi_set(rop, op); + return (1); + } + + bits = mpi_getsize(op, 2) - 2; + + if (bits < 0 || bits / nth == 0) { + /* integral root is surely less than 2 */ + exact = op->size == 1 && (op->digs[0] == 1 || op->digs[0] == 0); + mpi_seti(rop, sign ? -1 : op->digs[0] == 0 ? 0 : 1); + + return (exact == 1); + } + + /* initialize */ + if (rop != op) + r = rop; + else { + r = &t; + memset(r, '\0', sizeof(mpi)); + } + memset(&temp, '\0', sizeof(mpi)); + memset(", '\0', sizeof(mpi)); + memset(&old, '\0', sizeof(mpi)); + memset(&rem, '\0', sizeof(mpi)); + + if (sign) + r->sign = 0; + + /* root aproximation */ + mpi_ash(r, op, -(bits - (bits / nth))); + + for (;;) { + mpi_pow(&temp, r, nth - 1); + mpi_divqr(", &rem, op, &temp); + cmp = mpi_cmpabs(r, "); + if (cmp == 0) { + exact = mpi_cmpi(&rem, 0) == 0; + break; + } + else if (cmp < 0) { + if (mpi_cmpabs(r, &old) == 0) { + exact = 0; + break; + } + mpi_set(&old, r); + } + mpi_muli(&temp, r, nth - 1); + mpi_add(", ", &temp); + mpi_divi(r, ", nth); + } + + mpi_clear(&temp); + mpi_clear("); + mpi_clear(&old); + mpi_clear(&rem); + if (r != rop) { + mpi_set(rop, r); + mpi_clear(r); + } + rop->sign = sign; + + return (exact); +} + +/* + * Find square root using the iteration: + * x{n+1} = (x{n}+N/x{n})/2 + */ +int +mpi_sqrt(mpi *rop, mpi *op) +{ + long bits, cmp; + int exact; + mpi *r, t, quot, rem, old; + + /* result is complex */ + if (op->sign) { + int one = 1, zero = 0; + one = one / zero; + } + + bits = mpi_getsize(op, 2) - 2; + + if (bits < 2) { + /* integral root is surely less than 2 */ + exact = op->size == 1 && (op->digs[0] == 1 || op->digs[0] == 0); + mpi_seti(rop, op->digs[0] == 0 ? 0 : 1); + + return (exact == 1); + } + + /* initialize */ + if (rop != op) + r = rop; + else { + r = &t; + memset(r, '\0', sizeof(mpi)); + } + memset(", '\0', sizeof(mpi)); + memset(&rem, '\0', sizeof(mpi)); + memset(&old, '\0', sizeof(mpi)); + + /* root aproximation */ + mpi_ash(r, op, -(bits - (bits / 2))); + + for (;;) { + if (mpi_cmpabs(r, &old) == 0) { + exact = 0; + break; + } + mpi_divqr(", &rem, op, r); + cmp = mpi_cmpabs(", r); + if (cmp == 0) { + exact = mpi_cmpi(&rem, 0) == 0; + break; + } + else if (cmp > 0 && rem.size == 1 && rem.digs[0] == 0) + mpi_subi(", ", 1); + mpi_set(&old, r); + mpi_add(r, r, "); + mpi_ash(r, r, -1); + } + mpi_clear("); + mpi_clear(&rem); + mpi_clear(&old); + if (r != rop) { + mpi_set(rop, r); + mpi_clear(r); + } + + return (exact); +} + +void +mpi_ash(mpi *rop, mpi *op, long shift) +{ + long i; /* counter */ + long xsize; /* maximum result size */ + BNS *digs; + + /* check for 0 shift, multiply/divide by 1 */ + if (shift == 0) { + if (rop != op) { + if (rop->alloc < op->size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * op->size); + rop->alloc = op->size; + } + rop->size = op->size; + memcpy(rop->digs, op->digs, sizeof(BNS) * op->size); + } + + return; + } + else if (op->size == 1 && op->digs[0] == 0) { + rop->sign = 0; + rop->size = 1; + rop->digs[0] = 0; + + return; + } + + /* check shift and initialize */ + if (shift > 0) + xsize = op->size + (shift / BNSBITS) + 1; + else { + xsize = op->size - ((-shift) / BNSBITS); + if (xsize <= 0) { + rop->size = 1; + rop->sign = op->sign; + rop->digs[0] = op->sign ? 1 : 0; + + return; + } + } + + /* allocate/adjust memory for result */ + if (rop == op) + digs = mp_malloc(sizeof(BNS) * xsize); + else { + if (rop->alloc < xsize) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * xsize); + rop->alloc = xsize; + } + digs = rop->digs; + } + + /* left shift, multiply by power of two */ + if (shift > 0) + rop->size = mp_lshift(digs, op->digs, op->size, shift); + /* right shift, divide by power of two */ + else { + long carry = 0; + + if (op->sign) { + BNI words, bits; + + words = -shift / BNSBITS; + bits = -shift % BNSBITS; + for (i = 0; i < words; i++) + carry |= op->digs[xsize + i]; + if (!carry) { + for (i = 0; i < bits; i++) + if (op->digs[op->size - xsize] & (1 << i)) { + carry = 1; + break; + } + } + } + rop->size = mp_rshift(digs, op->digs, op->size, -shift); + + if (carry) + /* emulates two's complement subtracting 1 from the result */ + rop->size = mp_add(digs, digs, mpone.digs, rop->size, 1); + } + + if (rop->digs != digs) { + mp_free(rop->digs); + rop->alloc = rop->size; + rop->digs = digs; + } + rop->sign = op->sign; +} + +static INLINE BNS +mpi_logic(BNS op1, BNS op2, BNS op) +{ + switch (op) { + case '&': + return (op1 & op2); + case '|': + return (op1 | op2); + case '^': + return (op1 ^ op2); + } + + return (SMASK); +} + +static void +mpi_log(mpi *rop, mpi *op1, mpi *op2, BNS op) +{ + long i; /* counter */ + long c, c1, c2; /* carry */ + BNS *digs, *digs1, *digs2; /* pointers to mp data */ + BNI size, size1, size2; + BNS sign, sign1, sign2; + BNS n, n1, n2; /* logical operands */ + BNI sum; + + /* initialize */ + size1 = op1->size; + size2 = op2->size; + + sign1 = op1->sign ? SMASK : 0; + sign2 = op2->sign ? SMASK : 0; + + sign = mpi_logic(sign1, sign2, op); + + size = MAX(size1, size2); + if (sign) + ++size; + if (rop->alloc < size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * size); + rop->alloc = size; + } + + digs = rop->digs; + digs1 = op1->digs; + digs2 = op2->digs; + + c = c1 = c2 = 1; + + /* apply logical operation */ + for (i = 0; i < size; i++) { + if (i >= size1) + n1 = sign1; + else if (sign1) { + sum = (BNI)(BNS)(~digs1[i]) + c1; + c1 = (long)(sum >> BNSBITS); + n1 = (BNS)sum; + } + else + n1 = digs1[i]; + + if (i >= size2) + n2 = sign2; + else if (sign2) { + sum = (BNI)(BNS)(~digs2[i]) + c2; + c2 = (long)(sum >> BNSBITS); + n2 = (BNS)sum; + } + else + n2 = digs2[i]; + + n = mpi_logic(n1, n2, op); + if (sign) { + sum = (BNI)(BNS)(~n) + c; + c = (long)(sum >> BNSBITS); + digs[i] = (BNS)sum; + } + else + digs[i] = n; + } + + /* normalize */ + for (i = size - 1; i >= 0; i--) + if (digs[i] != 0) + break; + if (i != size - 1) { + if (i < 0) { + sign = 0; + size = 1; + } + else + size = i + 1; + } + + rop->sign = sign != 0; + rop->size = size; +} + +void +mpi_and(mpi *rop, mpi *op1, mpi *op2) +{ + mpi_log(rop, op1, op2, '&'); +} + +void +mpi_ior(mpi *rop, mpi *op1, mpi *op2) +{ + mpi_log(rop, op1, op2, '|'); +} + +void +mpi_xor(mpi *rop, mpi *op1, mpi *op2) +{ + mpi_log(rop, op1, op2, '^'); +} + +void +mpi_com(mpi *rop, mpi *op) +{ + static BNS digs[1] = { 1 }; + static mpi one = { 0, 1, 1, (BNS*)&digs }; + + mpi_log(rop, rop, &one, '^'); +} + +void +mpi_neg(mpi *rop, mpi *op) +{ + int sign = op->sign ^ 1; + + if (rop->digs != op->digs) { + if (rop->alloc < op->size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * rop->size); + rop->alloc = op->size; + } + rop->size = op->size; + memcpy(rop->digs, op->digs, sizeof(BNS) * rop->size); + } + + rop->sign = sign; +} + +void +mpi_abs(mpi *rop, mpi *op) +{ + if (rop->digs != op->digs) { + if (rop->alloc < op->size) { + rop->digs = mp_realloc(rop->digs, sizeof(BNS) * rop->size); + rop->alloc = op->size; + } + rop->size = op->size; + memcpy(rop->digs, op->digs, sizeof(BNS) * rop->size); + } + + rop->sign = 0; +} + +int +mpi_cmp(mpi *op1, mpi *op2) +{ + if (op1->sign ^ op2->sign) + return (op1->sign ? -1 : 1); + + if (op1->size == op2->size) { + long i, cmp = 0; + + for (i = op1->size - 1; i >= 0; i--) + if ((cmp = (long)op1->digs[i] - (long)op2->digs[i]) != 0) + break; + + return (cmp == 0 ? 0 : (cmp < 0) ^ op1->sign ? -1 : 1); + } + + return ((op1->size < op2->size) ^ op1->sign ? -1 : 1); +} + +int +mpi_cmpi(mpi *op1, long op2) +{ + long cmp; + + if (op1->size > 2) + return (op1->sign ? -1 : 1); + + cmp = op1->digs[0]; + if (op1->size == 2) { + cmp |= (long)op1->digs[1] << BNSBITS; + if (cmp == MINSLONG) + return (op2 == cmp && op1->sign ? 0 : op1->sign ? -1 : 1); + } + if (op1->sign) + cmp = -cmp; + + return (cmp - op2); +} + +int +mpi_cmpabs(mpi *op1, mpi *op2) +{ + if (op1->size == op2->size) { + long i, cmp = 0; + + for (i = op1->size - 1; i >= 0; i--) + if ((cmp = (long)op1->digs[i] - (long)op2->digs[i]) != 0) + break; + + return (cmp); + } + + return ((op1->size < op2->size) ? -1 : 1); +} + +int +mpi_cmpabsi(mpi *op1, long op2) +{ + unsigned long cmp; + + if (op1->size > 2) + return (1); + + cmp = op1->digs[0]; + if (op1->size == 2) + cmp |= (unsigned long)op1->digs[1] << BNSBITS; + + return (cmp > op2 ? 1 : cmp == op2 ? 0 : -1); +} + +int +mpi_sgn(mpi *op) +{ + return (op->sign ? -1 : op->size > 1 || op->digs[0] ? 1 : 0); +} + +void +mpi_swap(mpi *op1, mpi *op2) +{ + if (op1 != op2) { + mpi swap; + + memcpy(&swap, op1, sizeof(mpi)); + memcpy(op1, op2, sizeof(mpi)); + memcpy(op2, &swap, sizeof(mpi)); + } +} + +int +mpi_fiti(mpi *op) +{ + if (op->size == 1) + return (1); + else if (op->size == 2) { + unsigned long value = ((BNI)(op->digs[1]) << BNSBITS) | op->digs[0]; + + if (value & MINSLONG) + return (op->sign && value == MINSLONG) ? 1 : 0; + + return (1); + } + + return (0); +} + +long +mpi_geti(mpi *op) +{ + long value; + + value = op->digs[0]; + if (op->size > 1) + value |= (BNI)(op->digs[1]) << BNSBITS; + + return (op->sign && value != MINSLONG ? -value : value); +} + +double +mpi_getd(mpi *op) +{ + long i, len; + double d = 0.0; + int exponent; + +#define FLOATDIGS sizeof(double) / sizeof(BNS) + + switch (op->size) { + case 2: + d = (BNI)(op->digs[1]) << BNSBITS; + case 1: + d += op->digs[0]; + return (op->sign ? -d : d); + default: + break; + } + + for (i = 0, len = op->size; len > 0 && i < FLOATDIGS; i++) + d = ldexp(d, BNSBITS) + op->digs[--len]; + d = frexp(d, &exponent); + if (len > 0) + exponent += len * BNSBITS; + + if (d == 0.0) + return (d); + + d = ldexp(d, exponent); + + return (op->sign ? -d : d); +} + +/* how many digits in a given base, floor(log(CARRY) / log(base)) */ +#ifdef LONG64 +static char dig_bases[37] = { + 0, 0, 32, 20, 16, 13, 12, 11, 10, 10, 9, 9, 8, 8, 8, 8, + 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, +}; +#else +static char dig_bases[37] = { + 0, 0, 16, 10, 8, 6, 6, 5, 5, 5, 4, 4, 4, 4, 4, 4, + 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, +}; +#endif + +/* how many digits per bit in a given base, log(2) / log(base) */ +static double bit_bases[37] = { + 0.0000000000000000, 0.0000000000000000, 1.0000000000000000, + 0.6309297535714575, 0.5000000000000000, 0.4306765580733931, + 0.3868528072345416, 0.3562071871080222, 0.3333333333333334, + 0.3154648767857287, 0.3010299956639811, 0.2890648263178878, + 0.2789429456511298, 0.2702381544273197, 0.2626495350371936, + 0.2559580248098155, 0.2500000000000000, 0.2446505421182260, + 0.2398124665681315, 0.2354089133666382, 0.2313782131597592, + 0.2276702486969530, 0.2242438242175754, 0.2210647294575037, + 0.2181042919855316, 0.2153382790366965, 0.2127460535533632, + 0.2103099178571525, 0.2080145976765095, 0.2058468324604344, + 0.2037950470905062, 0.2018490865820999, 0.2000000000000000, + 0.1982398631705605, 0.1965616322328226, 0.1949590218937863, + 0.1934264036172708, +}; + +/* normalization base for string conversion, pow(base, dig_bases[base]) & ~CARRY */ +#ifdef LONG64 +static BNS big_bases[37] = { + 0x00000001, 0x00000001, 0x00000000, 0xCFD41B91, 0x00000000, 0x48C27395, + 0x81BF1000, 0x75DB9C97, 0x40000000, 0xCFD41B91, 0x3B9ACA00, 0x8C8B6D2B, + 0x19A10000, 0x309F1021, 0x57F6C100, 0x98C29B81, 0x00000000, 0x18754571, + 0x247DBC80, 0x3547667B, 0x4C4B4000, 0x6B5A6E1D, 0x94ACE180, 0xCAF18367, + 0x0B640000, 0x0E8D4A51, 0x1269AE40, 0x17179149, 0x1CB91000, 0x23744899, + 0x2B73A840, 0x34E63B41, 0x40000000, 0x4CFA3CC1, 0x5C13D840, 0x6D91B519, + 0x81BF1000, +}; +#else +static BNS big_bases[37] = { + 0x0001, 0x0001, 0x0000, 0xE6A9, 0x0000, 0x3D09, 0xB640, 0x41A7, 0x8000, + 0xE6A9, 0x2710, 0x3931, 0x5100, 0x6F91, 0x9610, 0xC5C1, 0x0000, 0x1331, + 0x16C8, 0x1ACB, 0x1F40, 0x242D, 0x2998, 0x2F87, 0x3600, 0x3D09, 0x44A8, + 0x4CE3, 0x55C0, 0x5F45, 0x6978, 0x745F, 0x8000, 0x8C61, 0x9988, 0xA77B, + 0xb640, +}; +#endif + +unsigned long +mpi_getsize(mpi *op, int base) +{ + unsigned long value, bits; + + value = op->digs[op->size - 1]; + + /* count leading bits */ + if (value) { + unsigned long count, carry; + + for (count = 0, carry = CARRY >> 1; carry; count++, carry >>= 1) + if (value & carry) + break; + + bits = BNSBITS - count; + } + else + bits = 0; + + return ((bits + (op->size - 1) * BNSBITS) * bit_bases[base] + 1); +} + +char * +mpi_getstr(char *str, mpi *op, int base) +{ + long i; /* counter */ + BNS *digs, *xdigs; /* copy of op data */ + BNI size; /* size of op */ + BNI digits; /* digits per word in given base */ + BNI bigbase; /* big base of given base */ + BNI strsize; /* size of resulting string */ + char *cp; /* pointer in str for conversion */ + + /* initialize */ + size = op->size; + strsize = mpi_getsize(op, base) + op->sign + 1; + + if (str == NULL) + str = mp_malloc(strsize); + + /* check for zero */ + if (size == 1 && op->digs[0] == 0) { + str[0] = '0'; + str[1] = '\0'; + + return (str); + } + + digits = dig_bases[base]; + bigbase = big_bases[base]; + + cp = str + strsize; + *--cp = '\0'; + + /* make copy of op data and adjust digs */ + xdigs = mp_malloc(size * sizeof(BNS)); + memcpy(xdigs, op->digs, size * sizeof(unsigned short)); + digs = xdigs + size - 1; + + /* convert to string */ + for (;;) { + long count = -1; + BNI value; + BNS quotient, remainder = 0; + + /* if power of two base */ + if ((base & (base - 1)) == 0) { + for (i = 0; i < size; i++) { + quotient = remainder; + remainder = digs[-i]; + digs[-i] = quotient; + if (count < 0 && quotient) + count = i; + } + } + else { + for (i = 0; i < size; i++) { + value = digs[-i] + ((BNI)remainder << BNSBITS); + quotient = (BNS)(value / bigbase); + remainder = (BNS)(value % bigbase); + digs[-i] = quotient; + if (count < 0 && quotient) + count = i; + } + } + quotient = remainder; + for (i = 0; i < digits; i++) { + if (quotient == 0 && count < 0) + break; + remainder = quotient % base; + quotient /= base; + *--cp = remainder < 10 ? remainder + '0' : remainder - 10 + 'A'; + } + if (count < 0) + break; + digs -= count; + size -= count; + } + + /* adjust sign */ + if (op->sign) + *--cp = '-'; + + /* remove any extra characters */ + if (cp > str) + strcpy(str, cp); + + mp_free(xdigs); + + return (str); +} |