diff options
Diffstat (limited to 'ghc/runtime/gmp/_mpz_get_str.c')
-rw-r--r-- | ghc/runtime/gmp/_mpz_get_str.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/ghc/runtime/gmp/_mpz_get_str.c b/ghc/runtime/gmp/_mpz_get_str.c new file mode 100644 index 0000000000..a83e690cf8 --- /dev/null +++ b/ghc/runtime/gmp/_mpz_get_str.c @@ -0,0 +1,309 @@ +/* _mpz_get_str (string, base, mp_src) -- Convert the multiple precision + number MP_SRC to a string STRING of base BASE. If STRING is NULL + allocate space for the result. In any case, return a pointer to the + result. If STRING is not NULL, the caller must ensure enough space is + available to store the result. + +Copyright (C) 1991, 1993 Free Software Foundation, Inc. + +This file is part of the GNU MP Library. + +The GNU MP Library 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, or (at your option) +any later version. + +The GNU MP Library 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 the GNU MP Library; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "gmp.h" +#include "gmp-impl.h" +#include "longlong.h" + +#ifndef UMUL_TIME +#define UMUL_TIME 1 +#endif + +#ifndef UDIV_TIME +#define UDIV_TIME UMUL_TIME +#endif + +#define udiv_qrnndx(q, r, nh, nl, d, di) \ + do { \ + unsigned long int _q, _ql, _r; \ + unsigned long int _xh, _xl; \ + umul_ppmm (_q, _ql, (nh), (di)); \ + _q += (nh); /* DI is 2**32 too small. Compensate */\ + if (_q < (nh)) \ + { \ + /* Got carry. Propagate it in the multiplication. */ \ + umul_ppmm (_xh, _xl, (d), _q); \ + _xh += (d); \ + } \ + else \ + umul_ppmm (_xh, _xl, (d), _q); \ + sub_ddmmss (_xh, _r, (nh), (nl), _xh, _xl); \ + if (_xh != 0) \ + { \ + sub_ddmmss (_xh, _r, _xh, _r, 0, (d)); \ + _q += 1; \ + if (_xh != 0) \ + { \ + sub_ddmmss (_xh, _r, _xh, _r, 0, (d)); \ + _q += 1; \ + } \ + } \ + if (_r >= (d)) \ + { \ + _r -= (d); \ + _q += 1; \ + } \ + (r) = _r; \ + (q) = _q; \ + } while (0) + +char * +#ifdef __STDC__ +_mpz_get_str (char *str, int base, const MP_INT *m) +#else +_mpz_get_str (str, base, m) + char *str; + int base; + const MP_INT *m; +#endif +{ + mp_ptr tp; + mp_size msize; + mp_limb big_base; +#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME + + int normalization_steps; +#if UDIV_TIME > 2 * UMUL_TIME + mp_limb big_base_inverted; +#endif +#endif + unsigned int dig_per_u; + mp_size out_len; + char *s; + char *num_to_ascii; + + if (base >= 0) + { + if (base == 0) + base = 10; + num_to_ascii = "0123456789abcdefghijklmnopqrstuvwxyz"; + } + else + { + base = -base; + num_to_ascii = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + + dig_per_u = __mp_bases[base].chars_per_limb; + out_len = mpz_sizeinbase (m, base) + 1; + big_base = __mp_bases[base].big_base; + + msize = m->size; + + if (str == NULL) + str = (char *) (*_mp_allocate_func) (out_len + (msize < 0)); + + if (msize < 0) + *str++ = '-'; + s = str; + + msize = ABS (msize); + + /* Special case zero, as the code below doesn't handle it. */ + if (msize == 0) + { + s[0] = '0'; + s[1] = 0; + return str; + } + + if ((base & (base - 1)) == 0) + { + /* The base is a power of 2. Make conversion from most + significant side. */ + mp_limb n1, n0; + int bits_per_digit = big_base; + int x; + int bit_pos; + int i; + unsigned mask = (1 << bits_per_digit) - 1; + + tp = m->d; + n1 = tp[msize - 1]; + count_leading_zeros (x, n1); + + /* BIT_POS should be R when input ends in least sign. nibble, + R + bits_per_digit * n when input ends in n:th least significant + nibble. */ + + { + int bits; + + bits = BITS_PER_MP_LIMB * msize - x; + x = bits % bits_per_digit; + if (x != 0) + bits += bits_per_digit - x; + bit_pos = bits - (msize - 1) * BITS_PER_MP_LIMB; + } + + /* Fast loop for bit output. */ + i = msize - 1; + for (;;) + { + bit_pos -= bits_per_digit; + while (bit_pos >= 0) + { + *s++ = num_to_ascii[(n1 >> bit_pos) & mask]; + bit_pos -= bits_per_digit; + } + i--; + if (i < 0) + break; + n0 = (n1 << -bit_pos) & mask; + n1 = tp[i]; + bit_pos += BITS_PER_MP_LIMB; + *s++ = num_to_ascii[n0 | (n1 >> bit_pos)]; + } + + *s = 0; + } + else + { + /* General case. The base is not a power of 2. Make conversion + from least significant end. */ + + /* If udiv_qrnnd only handles divisors with the most significant bit + set, prepare BIG_BASE for being a divisor by shifting it to the + left exactly enough to set the most significant bit. */ +#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME + count_leading_zeros (normalization_steps, big_base); + big_base <<= normalization_steps; +#if UDIV_TIME > 2 * UMUL_TIME + /* Get the fixed-point approximation to 1/BIG_BASE. */ + big_base_inverted = __mp_bases[base].big_base_inverted; +#endif +#endif + + out_len--; /* now not include terminating \0 */ + s += out_len; + + /* Allocate temporary space and move the multi prec number to + convert there, as we need to overwrite it below, while + computing the successive remainders. */ + tp = (mp_ptr) alloca ((msize + 1) * BYTES_PER_MP_LIMB); + MPN_COPY (tp, m->d, msize); + + while (msize != 0) + { + int i; + mp_limb n0, n1; + +#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME + /* If we shifted BIG_BASE above, shift the dividend too, to get + the right quotient. We need to do this every loop, + as the intermediate quotients are OK, but the quotient from + one turn in the loop is going to be the dividend in the + next turn, and the dividend needs to be up-shifted. */ + if (normalization_steps != 0) + { + n0 = mpn_lshift (tp, tp, msize, normalization_steps); + + /* If the shifting gave a carry out limb, store it and + increase the length. */ + if (n0 != 0) + { + tp[msize] = n0; + msize++; + } + } +#endif + + /* Divide the number at TP with BIG_BASE to get a quotient and a + remainder. The remainder is our new digit in base BIG_BASE. */ + i = msize - 1; + n1 = tp[i]; + + if (n1 >= big_base) + n1 = 0; + else + { + msize--; + i--; + } + + for (; i >= 0; i--) + { + n0 = tp[i]; +#if UDIV_TIME > 2 * UMUL_TIME + udiv_qrnndx (tp[i], n1, n1, n0, big_base, big_base_inverted); +#else + udiv_qrnnd (tp[i], n1, n1, n0, big_base); +#endif + } + +#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME + /* If we shifted above (at previous UDIV_NEEDS_NORMALIZATION tests) + the remainder will be up-shifted here. Compensate. */ + n1 >>= normalization_steps; +#endif + + /* Convert N1 from BIG_BASE to a string of digits in BASE + using single precision operations. */ + for (i = dig_per_u - 1; i >= 0; i--) + { + *--s = num_to_ascii[n1 % base]; + n1 /= base; + /* Break from the loop as soon as we would only write zeros. */ + if (n1 == 0 && msize == 0) + break; + } + } + + /* There should be no leading zeros. */ + if (*s == '0') + abort (); + + if (s == str) + { + /* This should be the common case. */ + s[out_len] = 0; + } + else if (s == str + 1) + { + /* The string became 1 digit shorter than its maximum. */ + /* Need to copy it back one char pos. */ + out_len--; +#ifndef HAS_MEMMOVE + { + size_t i; + + for (i = 0; i < out_len; i++) + str[i] = s[i]; + } +#else + memmove (str, s, out_len); +#endif + str[out_len] = 0; + } + else + { + /* Hopefully never. */ + abort (); + } + } + + alloca (0); + /* Ugly, we incremented str for negative numbers. Fix that here. */ + return str - (m->size < 0); +} |