/* -*-C-*- $Id: bignum.c,v 9.49 2000/12/05 21:23:43 cph Exp $ Copyright (c) 1989-2000 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Implementation of Bignums (unlimited precision integers) */ #ifdef MIT_SCHEME #include "scheme.h" #undef CHAR_BIT /* redefined in "limits.h" */ #else #include "bignum.h" #endif #include "bignmint.h" #include "limits.h" #include #ifndef MIT_SCHEME static bignum_type DEFUN (bignum_malloc, (length), bignum_length_type length) { extern char * malloc (); char * result = (malloc ((length + 1) * (sizeof (bignum_digit_type)))); BIGNUM_ASSERT (result != ((char *) 0)); return ((bignum_type) result); } static bignum_type DEFUN (bignum_realloc, (bignum, length), bignum_type bignum AND bignum_length_type length) { extern char * realloc (); char * result = (realloc (((char *) bignum), ((length + 1) * (sizeof (bignum_digit_type))))); BIGNUM_ASSERT (result != ((char *) 0)); return ((bignum_type) result); } #endif /* not MIT_SCHEME */ /* Forward references */ static int EXFUN (bignum_equal_p_unsigned, (bignum_type, bignum_type)); static enum bignum_comparison EXFUN (bignum_compare_unsigned, (bignum_type, bignum_type)); static bignum_type EXFUN (bignum_add_unsigned, (bignum_type, bignum_type, int)); static bignum_type EXFUN (bignum_subtract_unsigned, (bignum_type, bignum_type)); static bignum_type EXFUN (bignum_multiply_unsigned, (bignum_type, bignum_type, int)); static bignum_type EXFUN (bignum_multiply_unsigned_small_factor, (bignum_type, bignum_digit_type, int)); static void EXFUN (bignum_destructive_scale_up, (bignum_type, bignum_digit_type)); static void EXFUN (bignum_destructive_add, (bignum_type, bignum_digit_type)); static void EXFUN (bignum_divide_unsigned_large_denominator, (bignum_type, bignum_type, bignum_type *, bignum_type *, int, int)); static void EXFUN (bignum_destructive_normalization, (bignum_type, bignum_type, int)); static void EXFUN (bignum_destructive_unnormalization, (bignum_type, int)); static void EXFUN (bignum_divide_unsigned_normalized, (bignum_type, bignum_type, bignum_type)); static bignum_digit_type EXFUN (bignum_divide_subtract, (bignum_digit_type *, bignum_digit_type *, bignum_digit_type, bignum_digit_type *)); static void EXFUN (bignum_divide_unsigned_medium_denominator, (bignum_type, bignum_digit_type, bignum_type *, bignum_type *, int, int)); static bignum_digit_type EXFUN (bignum_digit_divide, (bignum_digit_type, bignum_digit_type, bignum_digit_type, bignum_digit_type *)); static bignum_digit_type EXFUN (bignum_digit_divide_subtract, (bignum_digit_type, bignum_digit_type, bignum_digit_type, bignum_digit_type *)); static void EXFUN (bignum_divide_unsigned_small_denominator, (bignum_type, bignum_digit_type, bignum_type *, bignum_type *, int, int)); static bignum_digit_type EXFUN (bignum_destructive_scale_down, (bignum_type, bignum_digit_type)); static bignum_type EXFUN (bignum_remainder_unsigned_small_denominator, (bignum_type, bignum_digit_type, int)); static bignum_type EXFUN (bignum_digit_to_bignum, (bignum_digit_type, int)); static bignum_type EXFUN (bignum_allocate, (bignum_length_type, int)); static bignum_type EXFUN (bignum_allocate_zeroed, (bignum_length_type, int)); static bignum_type EXFUN (bignum_shorten_length, (bignum_type, bignum_length_type)); static bignum_type EXFUN (bignum_trim, (bignum_type)); static bignum_type EXFUN (bignum_copy, (bignum_type)); static bignum_type EXFUN (bignum_new_sign, (bignum_type, int)); static bignum_type EXFUN (bignum_maybe_new_sign, (bignum_type, int)); static void EXFUN (bignum_destructive_copy, (bignum_type, bignum_type)); #define ULONG_LENGTH_IN_BITS(digit, len) \ do { \ fast unsigned long w = digit; \ len = 0; \ while (w > 0xff) { len += 8; w >>= 8; } \ while (w > 0) { len += 1; w >>= 1; } \ } while (0) /* Exports */ bignum_type DEFUN_VOID (bignum_make_zero) { fast bignum_type result = (BIGNUM_ALLOCATE (0)); BIGNUM_SET_HEADER (result, 0, 0); return (result); } bignum_type DEFUN (bignum_make_one, (negative_p), int negative_p) { fast bignum_type result = (BIGNUM_ALLOCATE (1)); BIGNUM_SET_HEADER (result, 1, negative_p); (BIGNUM_REF (result, 0)) = 1; return (result); } int DEFUN (bignum_equal_p, (x, y), fast bignum_type x AND fast bignum_type y) { return ((BIGNUM_ZERO_P (x)) ? (BIGNUM_ZERO_P (y)) : ((! (BIGNUM_ZERO_P (y))) && ((BIGNUM_NEGATIVE_P (x)) ? (BIGNUM_NEGATIVE_P (y)) : (! (BIGNUM_NEGATIVE_P (y)))) && (bignum_equal_p_unsigned (x, y)))); } enum bignum_comparison DEFUN (bignum_test, (bignum), fast bignum_type bignum) { return ((BIGNUM_ZERO_P (bignum)) ? bignum_comparison_equal : (BIGNUM_NEGATIVE_P (bignum)) ? bignum_comparison_less : bignum_comparison_greater); } enum bignum_comparison DEFUN (bignum_compare, (x, y), fast bignum_type x AND fast bignum_type y) { return ((BIGNUM_ZERO_P (x)) ? ((BIGNUM_ZERO_P (y)) ? bignum_comparison_equal : (BIGNUM_NEGATIVE_P (y)) ? bignum_comparison_greater : bignum_comparison_less) : (BIGNUM_ZERO_P (y)) ? ((BIGNUM_NEGATIVE_P (x)) ? bignum_comparison_less : bignum_comparison_greater) : (BIGNUM_NEGATIVE_P (x)) ? ((BIGNUM_NEGATIVE_P (y)) ? (bignum_compare_unsigned (y, x)) : (bignum_comparison_less)) : ((BIGNUM_NEGATIVE_P (y)) ? (bignum_comparison_greater) : (bignum_compare_unsigned (x, y)))); } bignum_type DEFUN (bignum_add, (x, y), fast bignum_type x AND fast bignum_type y) { return ((BIGNUM_ZERO_P (x)) ? (BIGNUM_MAYBE_COPY (y)) : (BIGNUM_ZERO_P (y)) ? (BIGNUM_MAYBE_COPY (x)) : ((BIGNUM_NEGATIVE_P (x)) ? ((BIGNUM_NEGATIVE_P (y)) ? (bignum_add_unsigned (x, y, 1)) : (bignum_subtract_unsigned (y, x))) : ((BIGNUM_NEGATIVE_P (y)) ? (bignum_subtract_unsigned (x, y)) : (bignum_add_unsigned (x, y, 0))))); } bignum_type DEFUN (bignum_subtract, (x, y), fast bignum_type x AND fast bignum_type y) { return ((BIGNUM_ZERO_P (x)) ? ((BIGNUM_ZERO_P (y)) ? (BIGNUM_MAYBE_COPY (y)) : (bignum_new_sign (y, (! (BIGNUM_NEGATIVE_P (y)))))) : ((BIGNUM_ZERO_P (y)) ? (BIGNUM_MAYBE_COPY (x)) : ((BIGNUM_NEGATIVE_P (x)) ? ((BIGNUM_NEGATIVE_P (y)) ? (bignum_subtract_unsigned (y, x)) : (bignum_add_unsigned (x, y, 1))) : ((BIGNUM_NEGATIVE_P (y)) ? (bignum_add_unsigned (x, y, 0)) : (bignum_subtract_unsigned (x, y)))))); } bignum_type DEFUN (bignum_negate, (x), fast bignum_type x) { return ((BIGNUM_ZERO_P (x)) ? (BIGNUM_MAYBE_COPY (x)) : (bignum_new_sign (x, (! (BIGNUM_NEGATIVE_P (x)))))); } bignum_type DEFUN (bignum_multiply, (x, y), fast bignum_type x AND fast bignum_type y) { fast bignum_length_type x_length = (BIGNUM_LENGTH (x)); fast bignum_length_type y_length = (BIGNUM_LENGTH (y)); fast int negative_p = ((BIGNUM_NEGATIVE_P (x)) ? (! (BIGNUM_NEGATIVE_P (y))) : (BIGNUM_NEGATIVE_P (y))); if (BIGNUM_ZERO_P (x)) return (BIGNUM_MAYBE_COPY (x)); if (BIGNUM_ZERO_P (y)) return (BIGNUM_MAYBE_COPY (y)); if (x_length == 1) { bignum_digit_type digit = (BIGNUM_REF (x, 0)); if (digit == 1) return (bignum_maybe_new_sign (y, negative_p)); if (digit < BIGNUM_RADIX_ROOT) return (bignum_multiply_unsigned_small_factor (y, digit, negative_p)); } if (y_length == 1) { bignum_digit_type digit = (BIGNUM_REF (y, 0)); if (digit == 1) return (bignum_maybe_new_sign (x, negative_p)); if (digit < BIGNUM_RADIX_ROOT) return (bignum_multiply_unsigned_small_factor (x, digit, negative_p)); } return (bignum_multiply_unsigned (x, y, negative_p)); } int DEFUN (bignum_divide, (numerator, denominator, quotient, remainder), bignum_type numerator AND bignum_type denominator AND bignum_type * quotient AND bignum_type * remainder) { if (BIGNUM_ZERO_P (denominator)) return (1); if (BIGNUM_ZERO_P (numerator)) { (*quotient) = (BIGNUM_MAYBE_COPY (numerator)); (*remainder) = (BIGNUM_MAYBE_COPY (numerator)); } else { int r_negative_p = (BIGNUM_NEGATIVE_P (numerator)); int q_negative_p = ((BIGNUM_NEGATIVE_P (denominator)) ? (! r_negative_p) : r_negative_p); switch (bignum_compare_unsigned (numerator, denominator)) { case bignum_comparison_equal: { (*quotient) = (BIGNUM_ONE (q_negative_p)); (*remainder) = (BIGNUM_ZERO ()); break; } case bignum_comparison_less: { (*quotient) = (BIGNUM_ZERO ()); (*remainder) = (BIGNUM_MAYBE_COPY (numerator)); break; } case bignum_comparison_greater: { if ((BIGNUM_LENGTH (denominator)) == 1) { bignum_digit_type digit = (BIGNUM_REF (denominator, 0)); if (digit == 1) { (*quotient) = (bignum_maybe_new_sign (numerator, q_negative_p)); (*remainder) = (BIGNUM_ZERO ()); break; } else if (digit < BIGNUM_RADIX_ROOT) { bignum_divide_unsigned_small_denominator (numerator, digit, quotient, remainder, q_negative_p, r_negative_p); break; } else { bignum_divide_unsigned_medium_denominator (numerator, digit, quotient, remainder, q_negative_p, r_negative_p); break; } } bignum_divide_unsigned_large_denominator (numerator, denominator, quotient, remainder, q_negative_p, r_negative_p); break; } } } return (0); } bignum_type DEFUN (bignum_quotient, (numerator, denominator), bignum_type numerator AND bignum_type denominator) { if (BIGNUM_ZERO_P (denominator)) return (BIGNUM_OUT_OF_BAND); if (BIGNUM_ZERO_P (numerator)) return (BIGNUM_MAYBE_COPY (numerator)); { int q_negative_p = ((BIGNUM_NEGATIVE_P (denominator)) ? (! (BIGNUM_NEGATIVE_P (numerator))) : (BIGNUM_NEGATIVE_P (numerator))); switch (bignum_compare_unsigned (numerator, denominator)) { case bignum_comparison_equal: return (BIGNUM_ONE (q_negative_p)); case bignum_comparison_less: return (BIGNUM_ZERO ()); case bignum_comparison_greater: { bignum_type quotient; if ((BIGNUM_LENGTH (denominator)) == 1) { bignum_digit_type digit = (BIGNUM_REF (denominator, 0)); if (digit == 1) return (bignum_maybe_new_sign (numerator, q_negative_p)); if (digit < BIGNUM_RADIX_ROOT) bignum_divide_unsigned_small_denominator (numerator, digit, ("ient), ((bignum_type *) 0), q_negative_p, 0); else bignum_divide_unsigned_medium_denominator (numerator, digit, ("ient), ((bignum_type *) 0), q_negative_p, 0); } else bignum_divide_unsigned_large_denominator (numerator, denominator, ("ient), ((bignum_type *) 0), q_negative_p, 0); return (quotient); } default: /*NOTREACHED*/ return (0); } } } bignum_type DEFUN (bignum_remainder, (numerator, denominator), bignum_type numerator AND bignum_type denominator) { if (BIGNUM_ZERO_P (denominator)) return (BIGNUM_OUT_OF_BAND); if (BIGNUM_ZERO_P (numerator)) return (BIGNUM_MAYBE_COPY (numerator)); switch (bignum_compare_unsigned (numerator, denominator)) { case bignum_comparison_equal: return (BIGNUM_ZERO ()); case bignum_comparison_less: return (BIGNUM_MAYBE_COPY (numerator)); case bignum_comparison_greater: { bignum_type remainder; if ((BIGNUM_LENGTH (denominator)) == 1) { bignum_digit_type digit = (BIGNUM_REF (denominator, 0)); if ((digit & (digit-1)) == 0) /* i.e. digit = 2^k, including 1 */ { bignum_digit_type unsigned_remainder; if (BIGNUM_LENGTH (numerator) == 0) return (BIGNUM_ZERO ()); unsigned_remainder = (digit-1) & (BIGNUM_REF (numerator, 0)); if (unsigned_remainder == 0) return (BIGNUM_ZERO ()); return (bignum_digit_to_bignum (unsigned_remainder, BIGNUM_NEGATIVE_P (numerator))); } if (digit < BIGNUM_RADIX_ROOT) return (bignum_remainder_unsigned_small_denominator (numerator, digit, (BIGNUM_NEGATIVE_P (numerator)))); bignum_divide_unsigned_medium_denominator (numerator, digit, ((bignum_type *) 0), (&remainder), 0, (BIGNUM_NEGATIVE_P (numerator))); } else bignum_divide_unsigned_large_denominator (numerator, denominator, ((bignum_type *) 0), (&remainder), 0, (BIGNUM_NEGATIVE_P (numerator))); return (remainder); } default: /*NOTREACHED*/ return (0); } } /* These procedures depend on the non-portable type `unsigned long'. If your compiler doesn't support this type, either define the switch `BIGNUM_NO_ULONG' to disable them (in "bignum.h"), or write new versions that don't use this type. */ #ifndef BIGNUM_NO_ULONG bignum_type DEFUN (long_to_bignum, (n), long n) { int negative_p; bignum_digit_type result_digits [BIGNUM_DIGITS_FOR_LONG]; fast bignum_digit_type * end_digits = result_digits; /* Special cases win when these small constants are cached. */ if (n == 0) return (BIGNUM_ZERO ()); if (n == 1) return (BIGNUM_ONE (0)); if (n == -1) return (BIGNUM_ONE (1)); { fast unsigned long accumulator = ((negative_p = (n < 0)) ? (-n) : n); do { (*end_digits++) = (accumulator & BIGNUM_DIGIT_MASK); accumulator >>= BIGNUM_DIGIT_LENGTH; } while (accumulator != 0); } { bignum_type result = (bignum_allocate ((end_digits - result_digits), negative_p)); fast bignum_digit_type * scan_digits = result_digits; fast bignum_digit_type * scan_result = (BIGNUM_START_PTR (result)); while (scan_digits < end_digits) (*scan_result++) = (*scan_digits++); return (result); } } long DEFUN (bignum_to_long, (bignum), bignum_type bignum) { if (BIGNUM_ZERO_P (bignum)) return (0); { fast unsigned long accumulator = 0; fast bignum_digit_type * start = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type * scan = (start + (BIGNUM_LENGTH (bignum))); while (start < scan) accumulator = ((accumulator << BIGNUM_DIGIT_LENGTH) + (*--scan)); return ((BIGNUM_NEGATIVE_P (bignum)) ? (- ((long) accumulator)) : ((long) accumulator)); } } bignum_type DEFUN (ulong_to_bignum, (n), unsigned long n) { bignum_digit_type result_digits [BIGNUM_DIGITS_FOR_LONG]; fast bignum_digit_type * end_digits = result_digits; /* Special cases win when these small constants are cached. */ if (n == 0) return (BIGNUM_ZERO ()); if (n == 1) return (BIGNUM_ONE (0)); { fast unsigned long accumulator = n; do { (*end_digits++) = (accumulator & BIGNUM_DIGIT_MASK); accumulator >>= BIGNUM_DIGIT_LENGTH; } while (accumulator != 0); } { bignum_type result = (bignum_allocate ((end_digits - result_digits), 0)); fast bignum_digit_type * scan_digits = result_digits; fast bignum_digit_type * scan_result = (BIGNUM_START_PTR (result)); while (scan_digits < end_digits) (*scan_result++) = (*scan_digits++); return (result); } } unsigned long DEFUN (bignum_to_ulong, (bignum), bignum_type bignum) { if (BIGNUM_ZERO_P (bignum)) return (0); { fast unsigned long accumulator = 0; fast bignum_digit_type * start = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type * scan = (start + (BIGNUM_LENGTH (bignum))); while (start < scan) accumulator = ((accumulator << BIGNUM_DIGIT_LENGTH) + (*--scan)); return (accumulator); } } #endif /* not BIGNUM_NO_ULONG */ #define DTB_WRITE_DIGIT(factor) \ { \ significand *= (factor); \ digit = ((bignum_digit_type) significand); \ (*--scan) = digit; \ significand -= ((double) digit); \ } bignum_type DEFUN (double_to_bignum, (x), double x) { extern double frexp (); int exponent; fast double significand = (frexp (x, (&exponent))); if (exponent <= 0) return (BIGNUM_ZERO ()); if (exponent == 1) return (BIGNUM_ONE (x < 0)); if (significand < 0) significand = (-significand); { bignum_length_type length = (BIGNUM_BITS_TO_DIGITS (exponent)); bignum_type result = (bignum_allocate (length, (x < 0))); bignum_digit_type * start = (BIGNUM_START_PTR (result)); fast bignum_digit_type * scan = (start + length); fast bignum_digit_type digit; int odd_bits = (exponent % BIGNUM_DIGIT_LENGTH); if (odd_bits > 0) DTB_WRITE_DIGIT (1L << odd_bits); while (start < scan) { if (significand == 0) { while (start < scan) (*--scan) = 0; break; } DTB_WRITE_DIGIT (BIGNUM_RADIX); } return (result); } } #undef DTB_WRITE_DIGIT /* This version sometimes gets the answer wrong due to rounding errors. It would be slightly better if the digits were accumulated lsb to msb. */ /* double DEFUN (bignum_to_double, (bignum), bignum_type bignum) { if (BIGNUM_ZERO_P (bignum)) return (0); { fast double accumulator = 0; fast bignum_digit_type * start = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type * scan = (start + (BIGNUM_LENGTH (bignum))); while (start < scan) accumulator = ((accumulator * BIGNUM_RADIX) + (*--scan)); return ((BIGNUM_NEGATIVE_P (bignum)) ? (-accumulator) : accumulator); } } */ double DEFUN (bignum_to_double, (bignum), bignum_type bignum) { if (BIGNUM_ZERO_P (bignum)) return (0.0); { bignum_length_type length = (BIGNUM_LENGTH (bignum)); bignum_length_type index = length - 1; bignum_length_type scale_words = length - 1; bignum_digit_type msd = (BIGNUM_REF (bignum, (index))); #if (FLT_RADIX == 2) int bits_to_get = DBL_MANT_DIG; /* includes implicit 1 */ #else #include "error: must have FLT_RADIX==2" #endif double value = 0; bignum_digit_type mask = 0; bignum_digit_type guard_bit_mask = BIGNUM_RADIX>>1; bignum_digit_type rounding_correction = 0; int current_digit_bit_count = 0; ULONG_LENGTH_IN_BITS (msd, current_digit_bit_count); mask = (1 << (current_digit_bit_count)) - 1; while (1) { if (current_digit_bit_count > bits_to_get) { guard_bit_mask = (1 << (current_digit_bit_count - bits_to_get - 1)); mask &= ~((guard_bit_mask << 1) - 1); current_digit_bit_count = bits_to_get; bits_to_get = 0; } else { bits_to_get -= current_digit_bit_count; } value = (value * BIGNUM_RADIX) + ((BIGNUM_REF (bignum, index)) & mask); if (bits_to_get == 0) { scale_words = index; if (current_digit_bit_count == BIGNUM_DIGIT_LENGTH) { if (index == 0) /* there is no guard bit */ goto finished; guard_bit_mask = (1 << (BIGNUM_DIGIT_LENGTH - 1)); rounding_correction = 1; index -= 1; } else { rounding_correction = (guard_bit_mask << 1); } break; } if (index == 0) /* fewer than DBL_MANT_DIG bits */ goto finished; index -= 1; current_digit_bit_count = BIGNUM_DIGIT_LENGTH; mask = BIGNUM_DIGIT_MASK; } /* round-to-even depending on lsb, guard and following bits: lgfffff */ if ((BIGNUM_REF(bignum,index) & guard_bit_mask) == 0) /* case x0xxxx */ goto round_down; if ((BIGNUM_REF(bignum,index) & (guard_bit_mask-1)) != 0) /* case x1xx1x */ goto round_up; /* cases 110000 and 1101xx: test "odd?", i.e. round-to-even rounds up */ if ((guard_bit_mask << 1) == BIGNUM_RADIX) { if (((BIGNUM_REF (bignum, index+1)) & 1) != 0) /* "odd?" */ goto round_up; } else { if (((BIGNUM_REF (bignum, index)) & (guard_bit_mask << 1)) != 0) goto round_up; } if (index==0) /* case 010000, no more words of following bits */ goto finished; { /* distinguish between cases 0100...00 and 0100..1xx, multiple words */ bignum_length_type index2 = index - 1; while (index2 >= 0) { if ((BIGNUM_REF (bignum, index2)) != 0) goto round_up; index2--; } goto round_down; } round_up: value += rounding_correction; round_down: /* note, ldexp `sticks' at the maximal non-infinity value, which is a reasonable compromise for numbers with DBL_MAX_EXP bits that round up */ if (scale_words > 0) value = ldexp (value, scale_words * BIGNUM_DIGIT_LENGTH); finished: return ((BIGNUM_NEGATIVE_P (bignum)) ? (-value) : value); } } /* ;; Code to test bignum_to_double (declare (usual-integrations)) (define integer->flonum (make-primitive-procedure 'integer->flonum 2)) (define (check n) (let ((f1 (integer->flonum n #b10)) (f2 (exact->inexact n))) (if (and f1 (= f1 f2)) (number->string f1 2) (begin (pp n) (pp (number->string f1 2)) (pp (number->string f2 2)))))) (define (test) (define n 0) (do ((i 0 (+ i 1))) ; guard bit zone patterns ((= i 256)) (do ((j 0 (+ j 1))) ; general word alignment ((= j 30)) (do ((e 0 (+ e 1))) ; random `insignificant' bit ((= e 2)) (do ((up 0 (+ up 1))) ; test potential for carry ((= up 2)) (do ((end-pad 0 (+ end-pad 100))) ((> end-pad 100)) (set! n (+ n 1)) (if (= (remainder n 1000) 0) (pp `(test ,n))) (let ((s (string-append "1" (make-string 48 (vector-ref '#(#\0 #\1) up)) (string-pad-left (number->string i 2) 8 #\0) (make-string (* j 23) #\0) ; gcd(23,30)=1 (number->string e) (make-string end-pad #\0)))) (check (string->number s 2))))))))) */ int DEFUN (bignum_fits_in_word_p, (bignum, word_length, twos_complement_p), bignum_type bignum AND long word_length AND int twos_complement_p) { unsigned int n_bits = (twos_complement_p ? (word_length - 1) : word_length); BIGNUM_ASSERT (n_bits > 0); { fast bignum_length_type length = (BIGNUM_LENGTH (bignum)); fast unsigned int max_digits = (BIGNUM_BITS_TO_DIGITS (n_bits)); if (((unsigned int) length) < max_digits) return (1); if (((unsigned int) length) > max_digits) return (0); { bignum_digit_type msd = (BIGNUM_REF (bignum, (length - 1))); bignum_digit_type max = (1L << (n_bits - ((length - 1) * BIGNUM_DIGIT_LENGTH))); return (((msd < max) || (twos_complement_p && (msd == max) && (BIGNUM_NEGATIVE_P (bignum))))); } } } bignum_type DEFUN (bignum_length_in_bits, (bignum), bignum_type bignum) { if (BIGNUM_ZERO_P (bignum)) return (BIGNUM_ZERO ()); { bignum_length_type index = ((BIGNUM_LENGTH (bignum)) - 1); fast bignum_digit_type digit = (BIGNUM_REF (bignum, index)); fast bignum_digit_type delta = 0; fast bignum_type result = (bignum_allocate (2, 0)); (BIGNUM_REF (result, 0)) = index; (BIGNUM_REF (result, 1)) = 0; bignum_destructive_scale_up (result, BIGNUM_DIGIT_LENGTH); ULONG_LENGTH_IN_BITS (digit, delta); bignum_destructive_add (result, ((bignum_digit_type) delta)); return (bignum_trim (result)); } } bignum_type DEFUN_VOID (bignum_length_upper_limit) { fast bignum_type result = (bignum_allocate (2, 0)); (BIGNUM_REF (result, 0)) = 0; (BIGNUM_REF (result, 1)) = BIGNUM_DIGIT_LENGTH; return (result); } bignum_type DEFUN (bignum_shift_left, (n, m), bignum_type n AND unsigned long m) { unsigned long ln = (BIGNUM_LENGTH (n)); unsigned long delta = 0; if (m == 0) return (n); ULONG_LENGTH_IN_BITS ((BIGNUM_REF (n, (ln - 1))), delta); { unsigned long zeroes = (m / BIGNUM_DIGIT_LENGTH); unsigned long shift = (m % BIGNUM_DIGIT_LENGTH); unsigned long ln2 = (((ln - 1) + ((delta + m) / BIGNUM_DIGIT_LENGTH)) + (((delta + m) % BIGNUM_DIGIT_LENGTH) != 0)); bignum_type result = (bignum_allocate (ln2, (BIGNUM_NEGATIVE_P (n)))); bignum_digit_type * scan_n = (BIGNUM_START_PTR (n)); bignum_digit_type * end_n = (scan_n + ln); bignum_digit_type * scan_result = (BIGNUM_START_PTR (result)); while ((zeroes--) > 0) (*scan_result++) = 0; if (shift == 0) while (scan_n < end_n) (*scan_result++) = (*scan_n++); else { unsigned long temp = 0; while (scan_n < end_n) { bignum_digit_type digit = (*scan_n++); (*scan_result++) = (((digit << shift) & BIGNUM_DIGIT_MASK) | temp); temp = (digit >> (BIGNUM_DIGIT_LENGTH - shift)); } if (temp != 0) (*scan_result) = temp; } return (result); } } bignum_type DEFUN (unsigned_long_to_shifted_bignum, (n, m, sign), unsigned long n AND unsigned long m AND int sign) { unsigned long delta = 0; if (n == 0) return (BIGNUM_ZERO ()); ULONG_LENGTH_IN_BITS (n, delta); { unsigned long zeroes = (m / BIGNUM_DIGIT_LENGTH); unsigned long shift = (m % BIGNUM_DIGIT_LENGTH); unsigned long ln = (((delta + m) / BIGNUM_DIGIT_LENGTH) + (((delta + m) % BIGNUM_DIGIT_LENGTH) != 0)); bignum_type result = (bignum_allocate (ln, sign)); bignum_digit_type * scan_result = (BIGNUM_START_PTR (result)); while ((zeroes--) > 0) (*scan_result++) = 0; (*scan_result++) = ((n << shift) & BIGNUM_DIGIT_MASK); n >>= (BIGNUM_DIGIT_LENGTH - shift); while (n > 0) { (*scan_result++) = (n & BIGNUM_DIGIT_MASK); n >>= BIGNUM_DIGIT_LENGTH; } return (result); } } bignum_type DEFUN (digit_stream_to_bignum, (n_digits, producer, context, radix, negative_p), fast unsigned int n_digits AND unsigned int EXFUN ((*producer), (bignum_procedure_context)) AND bignum_procedure_context context AND fast unsigned int radix AND int negative_p) { BIGNUM_ASSERT ((radix > 1) && (radix <= BIGNUM_RADIX_ROOT)); if (n_digits == 0) return (BIGNUM_ZERO ()); if (n_digits == 1) { long digit = ((long) ((*producer) (context))); return (long_to_bignum (negative_p ? (- digit) : digit)); } { bignum_length_type length; { fast unsigned int log_radix = 0; ULONG_LENGTH_IN_BITS (radix, log_radix); /* This length will be at least as large as needed. */ length = (BIGNUM_BITS_TO_DIGITS (n_digits * log_radix)); } { fast bignum_type result = (bignum_allocate_zeroed (length, negative_p)); while ((n_digits--) > 0) { bignum_destructive_scale_up (result, ((bignum_digit_type) radix)); bignum_destructive_add (result, ((bignum_digit_type) ((*producer) (context)))); } return (bignum_trim (result)); } } } void DEFUN (bignum_to_digit_stream, (bignum, radix, consumer, context), bignum_type bignum AND unsigned int radix AND void EXFUN ((*consumer), (bignum_procedure_context, long)) AND bignum_procedure_context context) { BIGNUM_ASSERT ((radix > 1) && (radix <= BIGNUM_RADIX_ROOT)); if (! (BIGNUM_ZERO_P (bignum))) { fast bignum_type working_copy = (bignum_copy (bignum)); fast bignum_digit_type * start = (BIGNUM_START_PTR (working_copy)); fast bignum_digit_type * scan = (start + (BIGNUM_LENGTH (working_copy))); while (start < scan) { if ((scan[-1]) == 0) scan -= 1; else (*consumer) (context, (bignum_destructive_scale_down (working_copy, radix))); } BIGNUM_DEALLOCATE (working_copy); } return; } long DEFUN_VOID (bignum_max_digit_stream_radix) { return (BIGNUM_RADIX_ROOT); } /* Comparisons */ static int DEFUN (bignum_equal_p_unsigned, (x, y), bignum_type x AND bignum_type y) { bignum_length_type length = (BIGNUM_LENGTH (x)); if (length != ((bignum_length_type) (BIGNUM_LENGTH (y)))) return (0); else { fast bignum_digit_type * scan_x = (BIGNUM_START_PTR (x)); fast bignum_digit_type * scan_y = (BIGNUM_START_PTR (y)); fast bignum_digit_type * end_x = (scan_x + length); while (scan_x < end_x) if ((*scan_x++) != (*scan_y++)) return (0); return (1); } } static enum bignum_comparison DEFUN (bignum_compare_unsigned, (x, y), bignum_type x AND bignum_type y) { bignum_length_type x_length = (BIGNUM_LENGTH (x)); bignum_length_type y_length = (BIGNUM_LENGTH (y)); if (x_length < y_length) return (bignum_comparison_less); if (x_length > y_length) return (bignum_comparison_greater); { fast bignum_digit_type * start_x = (BIGNUM_START_PTR (x)); fast bignum_digit_type * scan_x = (start_x + x_length); fast bignum_digit_type * scan_y = ((BIGNUM_START_PTR (y)) + y_length); while (start_x < scan_x) { fast bignum_digit_type digit_x = (*--scan_x); fast bignum_digit_type digit_y = (*--scan_y); if (digit_x < digit_y) return (bignum_comparison_less); if (digit_x > digit_y) return (bignum_comparison_greater); } } return (bignum_comparison_equal); } /* Addition */ static bignum_type DEFUN (bignum_add_unsigned, (x, y, negative_p), bignum_type x AND bignum_type y AND int negative_p) { if ((BIGNUM_LENGTH (y)) > (BIGNUM_LENGTH (x))) { bignum_type z = x; x = y; y = z; } { bignum_length_type x_length = (BIGNUM_LENGTH (x)); bignum_type r = (bignum_allocate ((x_length + 1), negative_p)); fast bignum_digit_type sum; fast bignum_digit_type carry = 0; fast bignum_digit_type * scan_x = (BIGNUM_START_PTR (x)); fast bignum_digit_type * scan_r = (BIGNUM_START_PTR (r)); { fast bignum_digit_type * scan_y = (BIGNUM_START_PTR (y)); fast bignum_digit_type * end_y = (scan_y + (BIGNUM_LENGTH (y))); while (scan_y < end_y) { sum = ((*scan_x++) + (*scan_y++) + carry); if (sum < BIGNUM_RADIX) { (*scan_r++) = sum; carry = 0; } else { (*scan_r++) = (sum - BIGNUM_RADIX); carry = 1; } } } { fast bignum_digit_type * end_x = ((BIGNUM_START_PTR (x)) + x_length); if (carry != 0) while (scan_x < end_x) { sum = ((*scan_x++) + 1); if (sum < BIGNUM_RADIX) { (*scan_r++) = sum; carry = 0; break; } else (*scan_r++) = (sum - BIGNUM_RADIX); } while (scan_x < end_x) (*scan_r++) = (*scan_x++); } if (carry != 0) { (*scan_r) = 1; return (r); } return (bignum_shorten_length (r, x_length)); } } /* Subtraction */ static bignum_type DEFUN (bignum_subtract_unsigned, (x, y), bignum_type x AND bignum_type y) { int negative_p = 0; switch (bignum_compare_unsigned (x, y)) { case bignum_comparison_equal: return (BIGNUM_ZERO ()); case bignum_comparison_less: { bignum_type z = x; x = y; y = z; } negative_p = 1; break; case bignum_comparison_greater: negative_p = 0; break; } { bignum_length_type x_length = (BIGNUM_LENGTH (x)); bignum_type r = (bignum_allocate (x_length, negative_p)); fast bignum_digit_type difference; fast bignum_digit_type borrow = 0; fast bignum_digit_type * scan_x = (BIGNUM_START_PTR (x)); fast bignum_digit_type * scan_r = (BIGNUM_START_PTR (r)); { fast bignum_digit_type * scan_y = (BIGNUM_START_PTR (y)); fast bignum_digit_type * end_y = (scan_y + (BIGNUM_LENGTH (y))); while (scan_y < end_y) { difference = (((*scan_x++) - (*scan_y++)) - borrow); if (difference < 0) { (*scan_r++) = (difference + BIGNUM_RADIX); borrow = 1; } else { (*scan_r++) = difference; borrow = 0; } } } { fast bignum_digit_type * end_x = ((BIGNUM_START_PTR (x)) + x_length); if (borrow != 0) while (scan_x < end_x) { difference = ((*scan_x++) - borrow); if (difference < 0) (*scan_r++) = (difference + BIGNUM_RADIX); else { (*scan_r++) = difference; borrow = 0; break; } } BIGNUM_ASSERT (borrow == 0); while (scan_x < end_x) (*scan_r++) = (*scan_x++); } return (bignum_trim (r)); } } /* Multiplication Maximum value for product_low or product_high: ((R * R) + (R * (R - 2)) + (R - 1)) Maximum value for carry: ((R * (R - 1)) + (R - 1)) where R == BIGNUM_RADIX_ROOT */ static bignum_type DEFUN (bignum_multiply_unsigned, (x, y, negative_p), bignum_type x AND bignum_type y AND int negative_p) { if ((BIGNUM_LENGTH (y)) > (BIGNUM_LENGTH (x))) { bignum_type z = x; x = y; y = z; } { fast bignum_digit_type carry; fast bignum_digit_type y_digit_low; fast bignum_digit_type y_digit_high; fast bignum_digit_type x_digit_low; fast bignum_digit_type x_digit_high; bignum_digit_type product_low; fast bignum_digit_type * scan_r; fast bignum_digit_type * scan_y; bignum_length_type x_length = (BIGNUM_LENGTH (x)); bignum_length_type y_length = (BIGNUM_LENGTH (y)); bignum_type r = (bignum_allocate_zeroed ((x_length + y_length), negative_p)); bignum_digit_type * scan_x = (BIGNUM_START_PTR (x)); bignum_digit_type * end_x = (scan_x + x_length); bignum_digit_type * start_y = (BIGNUM_START_PTR (y)); bignum_digit_type * end_y = (start_y + y_length); bignum_digit_type * start_r = (BIGNUM_START_PTR (r)); #define x_digit x_digit_high #define y_digit y_digit_high #define product_high carry while (scan_x < end_x) { x_digit = (*scan_x++); x_digit_low = (HD_LOW (x_digit)); x_digit_high = (HD_HIGH (x_digit)); carry = 0; scan_y = start_y; scan_r = (start_r++); while (scan_y < end_y) { y_digit = (*scan_y++); y_digit_low = (HD_LOW (y_digit)); y_digit_high = (HD_HIGH (y_digit)); product_low = ((*scan_r) + (x_digit_low * y_digit_low) + (HD_LOW (carry))); product_high = ((x_digit_high * y_digit_low) + (x_digit_low * y_digit_high) + (HD_HIGH (product_low)) + (HD_HIGH (carry))); (*scan_r++) = (HD_CONS ((HD_LOW (product_high)), (HD_LOW (product_low)))); carry = ((x_digit_high * y_digit_high) + (HD_HIGH (product_high))); } (*scan_r) += carry; } return (bignum_trim (r)); #undef x_digit #undef y_digit #undef product_high } } static bignum_type DEFUN (bignum_multiply_unsigned_small_factor, (x, y, negative_p), bignum_type x AND bignum_digit_type y AND int negative_p) { bignum_length_type length_x = (BIGNUM_LENGTH (x)); bignum_type p = (bignum_allocate ((length_x + 1), negative_p)); bignum_destructive_copy (x, p); (BIGNUM_REF (p, length_x)) = 0; bignum_destructive_scale_up (p, y); return (bignum_trim (p)); } static void DEFUN (bignum_destructive_scale_up, (bignum, factor), bignum_type bignum AND bignum_digit_type factor) { fast bignum_digit_type carry = 0; fast bignum_digit_type * scan = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type two_digits; fast bignum_digit_type product_low; #define product_high carry bignum_digit_type * end = (scan + (BIGNUM_LENGTH (bignum))); BIGNUM_ASSERT ((factor > 1) && (factor < BIGNUM_RADIX_ROOT)); while (scan < end) { two_digits = (*scan); product_low = ((factor * (HD_LOW (two_digits))) + (HD_LOW (carry))); product_high = ((factor * (HD_HIGH (two_digits))) + (HD_HIGH (product_low)) + (HD_HIGH (carry))); (*scan++) = (HD_CONS ((HD_LOW (product_high)), (HD_LOW (product_low)))); carry = (HD_HIGH (product_high)); } /* A carry here would be an overflow, i.e. it would not fit. Hopefully the callers allocate enough space that this will never happen. */ BIGNUM_ASSERT (carry == 0); return; #undef product_high } static void DEFUN (bignum_destructive_add, (bignum, n), bignum_type bignum AND bignum_digit_type n) { fast bignum_digit_type * scan = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type digit; digit = ((*scan) + n); if (digit < BIGNUM_RADIX) { (*scan) = digit; return; } (*scan++) = (digit - BIGNUM_RADIX); while (1) { digit = ((*scan) + 1); if (digit < BIGNUM_RADIX) { (*scan) = digit; return; } (*scan++) = (digit - BIGNUM_RADIX); } } /* Division */ /* For help understanding this algorithm, see: Knuth, Donald E., "The Art of Computer Programming", volume 2, "Seminumerical Algorithms" section 4.3.1, "Multiple-Precision Arithmetic". */ static void DEFUN (bignum_divide_unsigned_large_denominator, (numerator, denominator, quotient, remainder, q_negative_p, r_negative_p), bignum_type numerator AND bignum_type denominator AND bignum_type * quotient AND bignum_type * remainder AND int q_negative_p AND int r_negative_p) { bignum_length_type length_n = ((BIGNUM_LENGTH (numerator)) + 1); bignum_length_type length_d = (BIGNUM_LENGTH (denominator)); bignum_type q = ((quotient != ((bignum_type *) 0)) ? (bignum_allocate ((length_n - length_d), q_negative_p)) : BIGNUM_OUT_OF_BAND); bignum_type u = (bignum_allocate (length_n, r_negative_p)); int shift = 0; BIGNUM_ASSERT (length_d > 1); { fast bignum_digit_type v1 = (BIGNUM_REF ((denominator), (length_d - 1))); while (v1 < (BIGNUM_RADIX / 2)) { v1 <<= 1; shift += 1; } } if (shift == 0) { bignum_destructive_copy (numerator, u); (BIGNUM_REF (u, (length_n - 1))) = 0; bignum_divide_unsigned_normalized (u, denominator, q); } else { bignum_type v = (bignum_allocate (length_d, 0)); bignum_destructive_normalization (numerator, u, shift); bignum_destructive_normalization (denominator, v, shift); bignum_divide_unsigned_normalized (u, v, q); BIGNUM_DEALLOCATE (v); if (remainder != ((bignum_type *) 0)) bignum_destructive_unnormalization (u, shift); } if (quotient != ((bignum_type *) 0)) (*quotient) = (bignum_trim (q)); if (remainder != ((bignum_type *) 0)) (*remainder) = (bignum_trim (u)); else BIGNUM_DEALLOCATE (u); return; } static void DEFUN (bignum_divide_unsigned_normalized, (u, v, q), bignum_type u AND bignum_type v AND bignum_type q) { bignum_length_type u_length = (BIGNUM_LENGTH (u)); bignum_length_type v_length = (BIGNUM_LENGTH (v)); bignum_digit_type * u_start = (BIGNUM_START_PTR (u)); bignum_digit_type * u_scan = (u_start + u_length); bignum_digit_type * u_scan_limit = (u_start + v_length); bignum_digit_type * u_scan_start = (u_scan - v_length); bignum_digit_type * v_start = (BIGNUM_START_PTR (v)); bignum_digit_type * v_end = (v_start + v_length); bignum_digit_type * q_scan = 0; bignum_digit_type v1 = (v_end[-1]); bignum_digit_type v2 = (v_end[-2]); fast bignum_digit_type ph; /* high half of double-digit product */ fast bignum_digit_type pl; /* low half of double-digit product */ fast bignum_digit_type guess; fast bignum_digit_type gh; /* high half-digit of guess */ fast bignum_digit_type ch; /* high half of double-digit comparand */ fast bignum_digit_type v2l = (HD_LOW (v2)); fast bignum_digit_type v2h = (HD_HIGH (v2)); fast bignum_digit_type cl; /* low half of double-digit comparand */ #define gl ph /* low half-digit of guess */ #define uj pl #define qj ph bignum_digit_type gm; /* memory loc for reference parameter */ if (q != BIGNUM_OUT_OF_BAND) q_scan = ((BIGNUM_START_PTR (q)) + (BIGNUM_LENGTH (q))); while (u_scan_limit < u_scan) { uj = (*--u_scan); if (uj != v1) { /* comparand = (((((uj * BIGNUM_RADIX) + uj1) % v1) * BIGNUM_RADIX) + uj2); guess = (((uj * BIGNUM_RADIX) + uj1) / v1); */ cl = (u_scan[-2]); ch = (bignum_digit_divide (uj, (u_scan[-1]), v1, (&gm))); guess = gm; } else { cl = (u_scan[-2]); ch = ((u_scan[-1]) + v1); guess = (BIGNUM_RADIX - 1); } while (1) { /* product = (guess * v2); */ gl = (HD_LOW (guess)); gh = (HD_HIGH (guess)); pl = (v2l * gl); ph = ((v2l * gh) + (v2h * gl) + (HD_HIGH (pl))); pl = (HD_CONS ((HD_LOW (ph)), (HD_LOW (pl)))); ph = ((v2h * gh) + (HD_HIGH (ph))); /* if (comparand >= product) */ if ((ch > ph) || ((ch == ph) && (cl >= pl))) break; guess -= 1; /* comparand += (v1 << BIGNUM_DIGIT_LENGTH) */ ch += v1; /* if (comparand >= (BIGNUM_RADIX * BIGNUM_RADIX)) */ if (ch >= BIGNUM_RADIX) break; } qj = (bignum_divide_subtract (v_start, v_end, guess, (--u_scan_start))); if (q != BIGNUM_OUT_OF_BAND) (*--q_scan) = qj; } return; #undef gl #undef uj #undef qj } static bignum_digit_type DEFUN (bignum_divide_subtract, (v_start, v_end, guess, u_start), bignum_digit_type * v_start AND bignum_digit_type * v_end AND bignum_digit_type guess AND bignum_digit_type * u_start) { bignum_digit_type * v_scan = v_start; bignum_digit_type * u_scan = u_start; fast bignum_digit_type carry = 0; if (guess == 0) return (0); { bignum_digit_type gl = (HD_LOW (guess)); bignum_digit_type gh = (HD_HIGH (guess)); fast bignum_digit_type v; fast bignum_digit_type pl; fast bignum_digit_type vl; #define vh v #define ph carry #define diff pl while (v_scan < v_end) { v = (*v_scan++); vl = (HD_LOW (v)); vh = (HD_HIGH (v)); pl = ((vl * gl) + (HD_LOW (carry))); ph = ((vl * gh) + (vh * gl) + (HD_HIGH (pl)) + (HD_HIGH (carry))); diff = ((*u_scan) - (HD_CONS ((HD_LOW (ph)), (HD_LOW (pl))))); if (diff < 0) { (*u_scan++) = (diff + BIGNUM_RADIX); carry = ((vh * gh) + (HD_HIGH (ph)) + 1); } else { (*u_scan++) = diff; carry = ((vh * gh) + (HD_HIGH (ph))); } } if (carry == 0) return (guess); diff = ((*u_scan) - carry); if (diff < 0) (*u_scan) = (diff + BIGNUM_RADIX); else { (*u_scan) = diff; return (guess); } #undef vh #undef ph #undef diff } /* Subtraction generated carry, implying guess is one too large. Add v back in to bring it back down. */ v_scan = v_start; u_scan = u_start; carry = 0; while (v_scan < v_end) { bignum_digit_type sum = ((*v_scan++) + (*u_scan) + carry); if (sum < BIGNUM_RADIX) { (*u_scan++) = sum; carry = 0; } else { (*u_scan++) = (sum - BIGNUM_RADIX); carry = 1; } } if (carry == 1) { bignum_digit_type sum = ((*u_scan) + carry); (*u_scan) = ((sum < BIGNUM_RADIX) ? sum : (sum - BIGNUM_RADIX)); } return (guess - 1); } static void DEFUN (bignum_divide_unsigned_medium_denominator, (numerator, denominator, quotient, remainder, q_negative_p, r_negative_p), bignum_type numerator AND bignum_digit_type denominator AND bignum_type * quotient AND bignum_type * remainder AND int q_negative_p AND int r_negative_p) { bignum_length_type length_n = (BIGNUM_LENGTH (numerator)); bignum_length_type length_q; bignum_type q; int shift = 0; /* Because `bignum_digit_divide' requires a normalized denominator. */ while (denominator < (BIGNUM_RADIX / 2)) { denominator <<= 1; shift += 1; } if (shift == 0) { length_q = length_n; q = (bignum_allocate (length_q, q_negative_p)); bignum_destructive_copy (numerator, q); } else { length_q = (length_n + 1); q = (bignum_allocate (length_q, q_negative_p)); bignum_destructive_normalization (numerator, q, shift); } { fast bignum_digit_type r = 0; fast bignum_digit_type * start = (BIGNUM_START_PTR (q)); fast bignum_digit_type * scan = (start + length_q); bignum_digit_type qj; if (quotient != ((bignum_type *) 0)) { while (start < scan) { r = (bignum_digit_divide (r, (*--scan), denominator, (&qj))); (*scan) = qj; } (*quotient) = (bignum_trim (q)); } else { while (start < scan) r = (bignum_digit_divide (r, (*--scan), denominator, (&qj))); BIGNUM_DEALLOCATE (q); } if (remainder != ((bignum_type *) 0)) { if (shift != 0) r >>= shift; (*remainder) = (bignum_digit_to_bignum (r, r_negative_p)); } } return; } static void DEFUN (bignum_destructive_normalization, (source, target, shift_left), bignum_type source AND bignum_type target AND int shift_left) { fast bignum_digit_type digit; fast bignum_digit_type * scan_source = (BIGNUM_START_PTR (source)); fast bignum_digit_type carry = 0; fast bignum_digit_type * scan_target = (BIGNUM_START_PTR (target)); bignum_digit_type * end_source = (scan_source + (BIGNUM_LENGTH (source))); bignum_digit_type * end_target = (scan_target + (BIGNUM_LENGTH (target))); int shift_right = (BIGNUM_DIGIT_LENGTH - shift_left); bignum_digit_type mask = ((1L << shift_right) - 1); while (scan_source < end_source) { digit = (*scan_source++); (*scan_target++) = (((digit & mask) << shift_left) | carry); carry = (digit >> shift_right); } if (scan_target < end_target) (*scan_target) = carry; else BIGNUM_ASSERT (carry == 0); return; } static void DEFUN (bignum_destructive_unnormalization, (bignum, shift_right), bignum_type bignum AND int shift_right) { bignum_digit_type * start = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type * scan = (start + (BIGNUM_LENGTH (bignum))); fast bignum_digit_type digit; fast bignum_digit_type carry = 0; int shift_left = (BIGNUM_DIGIT_LENGTH - shift_right); bignum_digit_type mask = ((1L << shift_right) - 1); while (start < scan) { digit = (*--scan); (*scan) = ((digit >> shift_right) | carry); carry = ((digit & mask) << shift_left); } BIGNUM_ASSERT (carry == 0); return; } /* This is a reduced version of the division algorithm, applied to the case of dividing two bignum digits by one bignum digit. It is assumed that the numerator and denominator are normalized. */ #define BDD_STEP(qn, j) \ { \ uj = (u[j]); \ if (uj != v1) \ { \ uj_uj1 = (HD_CONS (uj, (u[j + 1]))); \ guess = (uj_uj1 / v1); \ comparand = (HD_CONS ((uj_uj1 % v1), (u[j + 2]))); \ } \ else \ { \ guess = (BIGNUM_RADIX_ROOT - 1); \ comparand = (HD_CONS (((u[j + 1]) + v1), (u[j + 2]))); \ } \ while ((guess * v2) > comparand) \ { \ guess -= 1; \ comparand += (v1 << BIGNUM_HALF_DIGIT_LENGTH); \ if (comparand >= BIGNUM_RADIX) \ break; \ } \ qn = (bignum_digit_divide_subtract (v1, v2, guess, (&u[j]))); \ } static bignum_digit_type DEFUN (bignum_digit_divide, (uh, ul, v, q), bignum_digit_type uh AND bignum_digit_type ul AND bignum_digit_type v AND bignum_digit_type * q) /* return value */ { fast bignum_digit_type guess; fast bignum_digit_type comparand; fast bignum_digit_type v1 = (HD_HIGH (v)); fast bignum_digit_type v2 = (HD_LOW (v)); fast bignum_digit_type uj; fast bignum_digit_type uj_uj1; bignum_digit_type q1; bignum_digit_type q2; bignum_digit_type u [4]; if (uh == 0) { if (ul < v) { (*q) = 0; return (ul); } else if (ul == v) { (*q) = 1; return (0); } } (u[0]) = (HD_HIGH (uh)); (u[1]) = (HD_LOW (uh)); (u[2]) = (HD_HIGH (ul)); (u[3]) = (HD_LOW (ul)); v1 = (HD_HIGH (v)); v2 = (HD_LOW (v)); BDD_STEP (q1, 0); BDD_STEP (q2, 1); (*q) = (HD_CONS (q1, q2)); return (HD_CONS ((u[2]), (u[3]))); } #undef BDD_STEP #define BDDS_MULSUB(vn, un, carry_in) \ { \ product = ((vn * guess) + carry_in); \ diff = (un - (HD_LOW (product))); \ if (diff < 0) \ { \ un = (diff + BIGNUM_RADIX_ROOT); \ carry = ((HD_HIGH (product)) + 1); \ } \ else \ { \ un = diff; \ carry = (HD_HIGH (product)); \ } \ } #define BDDS_ADD(vn, un, carry_in) \ { \ sum = (vn + un + carry_in); \ if (sum < BIGNUM_RADIX_ROOT) \ { \ un = sum; \ carry = 0; \ } \ else \ { \ un = (sum - BIGNUM_RADIX_ROOT); \ carry = 1; \ } \ } static bignum_digit_type DEFUN (bignum_digit_divide_subtract, (v1, v2, guess, u), bignum_digit_type v1 AND bignum_digit_type v2 AND bignum_digit_type guess AND bignum_digit_type * u) { { fast bignum_digit_type product; fast bignum_digit_type diff; fast bignum_digit_type carry; BDDS_MULSUB (v2, (u[2]), 0); BDDS_MULSUB (v1, (u[1]), carry); if (carry == 0) return (guess); diff = ((u[0]) - carry); if (diff < 0) (u[0]) = (diff + BIGNUM_RADIX); else { (u[0]) = diff; return (guess); } } { fast bignum_digit_type sum; fast bignum_digit_type carry; BDDS_ADD(v2, (u[2]), 0); BDDS_ADD(v1, (u[1]), carry); if (carry == 1) (u[0]) += 1; } return (guess - 1); } #undef BDDS_MULSUB #undef BDDS_ADD static void DEFUN (bignum_divide_unsigned_small_denominator, (numerator, denominator, quotient, remainder, q_negative_p, r_negative_p), bignum_type numerator AND bignum_digit_type denominator AND bignum_type * quotient AND bignum_type * remainder AND int q_negative_p AND int r_negative_p) { bignum_type q = (bignum_new_sign (numerator, q_negative_p)); bignum_digit_type r = (bignum_destructive_scale_down (q, denominator)); (*quotient) = (bignum_trim (q)); if (remainder != ((bignum_type *) 0)) (*remainder) = (bignum_digit_to_bignum (r, r_negative_p)); return; } /* Given (denominator > 1), it is fairly easy to show that (quotient_high < BIGNUM_RADIX_ROOT), after which it is easy to see that all digits are < BIGNUM_RADIX. */ static bignum_digit_type DEFUN (bignum_destructive_scale_down, (bignum, denominator), bignum_type bignum AND fast bignum_digit_type denominator) { fast bignum_digit_type numerator; fast bignum_digit_type remainder = 0; fast bignum_digit_type two_digits; #define quotient_high remainder bignum_digit_type * start = (BIGNUM_START_PTR (bignum)); bignum_digit_type * scan = (start + (BIGNUM_LENGTH (bignum))); BIGNUM_ASSERT ((denominator > 1) && (denominator < BIGNUM_RADIX_ROOT)); while (start < scan) { two_digits = (*--scan); numerator = (HD_CONS (remainder, (HD_HIGH (two_digits)))); quotient_high = (numerator / denominator); numerator = (HD_CONS ((numerator % denominator), (HD_LOW (two_digits)))); (*scan) = (HD_CONS (quotient_high, (numerator / denominator))); remainder = (numerator % denominator); } return (remainder); #undef quotient_high } static bignum_type DEFUN (bignum_remainder_unsigned_small_denominator, (n, d, negative_p), bignum_type n AND bignum_digit_type d AND int negative_p) { fast bignum_digit_type two_digits; bignum_digit_type * start = (BIGNUM_START_PTR (n)); fast bignum_digit_type * scan = (start + (BIGNUM_LENGTH (n))); fast bignum_digit_type r = 0; BIGNUM_ASSERT ((d > 1) && (d < BIGNUM_RADIX_ROOT)); while (start < scan) { two_digits = (*--scan); r = ((HD_CONS (((HD_CONS (r, (HD_HIGH (two_digits)))) % d), (HD_LOW (two_digits)))) % d); } return (bignum_digit_to_bignum (r, negative_p)); } static bignum_type DEFUN (bignum_digit_to_bignum, (digit, negative_p), fast bignum_digit_type digit AND int negative_p) { if (digit == 0) return (BIGNUM_ZERO ()); else { fast bignum_type result = (bignum_allocate (1, negative_p)); (BIGNUM_REF (result, 0)) = digit; return (result); } } /* Allocation */ static bignum_type DEFUN (bignum_allocate, (length, negative_p), fast bignum_length_type length AND int negative_p) { BIGNUM_ASSERT ((length >= 0) || (length < BIGNUM_RADIX)); { fast bignum_type result = (BIGNUM_ALLOCATE (length)); BIGNUM_SET_HEADER (result, length, negative_p); return (result); } } static bignum_type DEFUN (bignum_allocate_zeroed, (length, negative_p), fast bignum_length_type length AND int negative_p) { BIGNUM_ASSERT ((length >= 0) || (length < BIGNUM_RADIX)); { fast bignum_type result = (BIGNUM_ALLOCATE (length)); fast bignum_digit_type * scan = (BIGNUM_START_PTR (result)); fast bignum_digit_type * end = (scan + length); BIGNUM_SET_HEADER (result, length, negative_p); while (scan < end) (*scan++) = 0; return (result); } } static bignum_type DEFUN (bignum_shorten_length, (bignum, length), fast bignum_type bignum AND fast bignum_length_type length) { fast bignum_length_type current_length = (BIGNUM_LENGTH (bignum)); BIGNUM_ASSERT ((length >= 0) || (length <= current_length)); if (length < current_length) { BIGNUM_SET_HEADER (bignum, length, ((length != 0) && (BIGNUM_NEGATIVE_P (bignum)))); BIGNUM_REDUCE_LENGTH (bignum, bignum, length); } return (bignum); } static bignum_type DEFUN (bignum_trim, (bignum), bignum_type bignum) { fast bignum_digit_type * start = (BIGNUM_START_PTR (bignum)); fast bignum_digit_type * end = (start + (BIGNUM_LENGTH (bignum))); fast bignum_digit_type * scan = end; while ((start <= scan) && ((*--scan) == 0)) ; scan += 1; if (scan < end) { fast bignum_length_type length = (scan - start); BIGNUM_SET_HEADER (bignum, length, ((length != 0) && (BIGNUM_NEGATIVE_P (bignum)))); BIGNUM_REDUCE_LENGTH (bignum, bignum, length); } return (bignum); } /* Copying */ static bignum_type DEFUN (bignum_copy, (source), fast bignum_type source) { fast bignum_type target = (bignum_allocate ((BIGNUM_LENGTH (source)), (BIGNUM_NEGATIVE_P (source)))); bignum_destructive_copy (source, target); return (target); } static bignum_type DEFUN (bignum_new_sign, (bignum, negative_p), fast bignum_type bignum AND int negative_p) { fast bignum_type result = (bignum_allocate ((BIGNUM_LENGTH (bignum)), negative_p)); bignum_destructive_copy (bignum, result); return (result); } static bignum_type DEFUN (bignum_maybe_new_sign, (bignum, negative_p), fast bignum_type bignum AND int negative_p) { #ifndef BIGNUM_FORCE_NEW_RESULTS if ((BIGNUM_NEGATIVE_P (bignum)) ? negative_p : (! negative_p)) return (bignum); else #endif /* not BIGNUM_FORCE_NEW_RESULTS */ { fast bignum_type result = (bignum_allocate ((BIGNUM_LENGTH (bignum)), negative_p)); bignum_destructive_copy (bignum, result); return (result); } } static void DEFUN (bignum_destructive_copy, (source, target), bignum_type source AND bignum_type target) { fast bignum_digit_type * scan_source = (BIGNUM_START_PTR (source)); fast bignum_digit_type * end_source = (scan_source + (BIGNUM_LENGTH (source))); fast bignum_digit_type * scan_target = (BIGNUM_START_PTR (target)); while (scan_source < end_source) (*scan_target++) = (*scan_source++); return; }