/*++
Copyright (c) 2006 Microsoft Corporation

Module Name:

    mpz.cpp

Abstract:

    <abstract>

Author:

    Leonardo de Moura (leonardo) 2010-06-17.

Revision History:

--*/
#include<sstream>
#include"mpz.h"
#include"buffer.h"
#include"trace.h"
#include"hash.h"
#include"bit_util.h"

#if defined(_MP_INTERNAL)
#include"mpn.h"
#elif defined(_MP_GMP)
#include<gmp.h>
#else
#error No multi-precision library selected.
#endif

// Available GCD algorithms
// #define EUCLID_GCD
// #define BINARY_GCD
// #define LS_BINARY_GCD
// #define LEHMER_GCD

#if defined(_MP_GMP) || (defined(_MP_MSBIGNUM) && defined(_AMD64_))
// Use LEHMER only if not using GMP
// LEHMER assumes 32-bit digits, so it cannot be used with MSBIGNUM library + 64-bit binary
#define EUCLID_GCD
#else
#define LEHMER_GCD
#endif

template<typename T>
static T gcd_core(T u, T v) {
    if (u == 0) 
        return v;
    if (v == 0)
        return u;

    int k;
    
    for (k = 0; ((u | v) & 1) == 0; ++k) {
        u >>= 1;
        v >>= 1;
    }
 
    while ((u & 1) == 0)
        u >>= 1;
 
    do {
        while ((v & 1) == 0) 
            v >>= 1;
 
        if (u < v) {
            v -= u;
        } 
        else {
            T diff = u - v;
            u = v;
            v = diff;
        }
        v >>= 1;
    } while (v != 0);
 
    return u << k;
}

unsigned u_gcd(unsigned u, unsigned v) { return gcd_core(u, v); }
uint64 u64_gcd(uint64 u, uint64 v) { return gcd_core(u, v); }

template<bool SYNCH>
mpz_manager<SYNCH>::mpz_manager():
    m_allocator("mpz_manager") {
    if (SYNCH)
        omp_init_nest_lock(&m_lock);
#ifndef _MP_GMP
    if (sizeof(digit_t) == sizeof(uint64)) {
        // 64-bit machine
        m_init_cell_capacity = 4;
    }
    else {
        m_init_cell_capacity = 6;
    }
    for (unsigned i = 0; i < 2; i++) {
        m_tmp[i] = allocate(m_init_cell_capacity);
        m_arg[i] = allocate(m_init_cell_capacity);
        m_arg[i]->m_size = 1;
    }
    set(m_int_min, -static_cast<int64>(INT_MIN));
#else
    // GMP
    mpz_init(m_tmp);
    mpz_init(m_tmp2);
    mpz_init(m_two32);
    mpz_set_ui(m_two32, UINT_MAX);
    mpz_set_ui(m_tmp, 1);
    mpz_add(m_two32, m_two32, m_tmp);
    m_arg[0] = allocate();
    m_arg[1] = allocate();
    mpz_init(m_uint64_max);
    unsigned max_l = static_cast<unsigned>(UINT64_MAX);
    unsigned max_h = static_cast<unsigned>(UINT64_MAX >> 32);
    mpz_set_ui(m_uint64_max, max_h);
    mpz_mul(m_uint64_max, m_two32, m_uint64_max);
    mpz_set_ui(m_tmp, max_l);
    mpz_add(m_uint64_max, m_uint64_max, m_tmp);
    mpz_init(m_int64_max);

    max_l = static_cast<unsigned>(INT64_MAX % static_cast<int64>(UINT_MAX));
    max_h = static_cast<unsigned>(INT64_MAX / static_cast<int64>(UINT_MAX));
    mpz_set_ui(m_int64_max, max_h);
    mpz_set_ui(m_tmp, UINT_MAX);
    mpz_mul(m_int64_max, m_tmp, m_int64_max);
    mpz_set_ui(m_tmp, max_l);
    mpz_add(m_int64_max, m_tmp, m_int64_max);
#endif
    
    mpz one(1);
    set(m_two64, UINT64_MAX);
    add(m_two64, one, m_two64);
}

template<bool SYNCH>
mpz_manager<SYNCH>::~mpz_manager() {
    del(m_two64);
#ifndef _MP_GMP
    del(m_int_min);
    for (unsigned i = 0; i < 2; i++) {
        deallocate(m_tmp[i]);
        deallocate(m_arg[i]);
    }
#else
    mpz_clear(m_tmp);
    mpz_clear(m_tmp2);
    mpz_clear(m_two32);
    deallocate(m_arg[0]);
    deallocate(m_arg[1]);
    mpz_clear(m_uint64_max);
    mpz_clear(m_int64_max);
#endif
    if (SYNCH)
        omp_destroy_nest_lock(&m_lock);
}

template<bool SYNCH>
void mpz_manager<SYNCH>::set_big_i64(mpz & c, int64 v) {
#ifndef _MP_GMP
    if (is_small(c)) {
        c.m_ptr = allocate(m_init_cell_capacity);
    }
    SASSERT(capacity(c) >= m_init_cell_capacity);
    uint64 _v;
    if (v < 0) {
        _v = -v;
        c.m_val = -1;
    }
    else {
        _v = v;
        c.m_val = 1;
    }
    if (sizeof(digit_t) == sizeof(uint64)) {
        // 64-bit machine
        digits(c)[0] = static_cast<digit_t>(_v);
        c.m_ptr->m_size = 1;
    }
    else {
        // 32-bit machine
        digits(c)[0] = static_cast<unsigned>(_v);
        digits(c)[1] = static_cast<unsigned>(_v >> 32);
        c.m_ptr->m_size = digits(c)[1] == 0 ? 1 : 2;
    }
#else
    if (is_small(c)) {
        c.m_ptr = allocate();
    }
    uint64 _v;
    bool sign;
    if (v < 0) {
        _v   = -v;
        sign = true;
    }
    else {
        _v   = v;
        sign = false;
    }
    mpz_set_ui(*c.m_ptr, static_cast<unsigned>(_v));
    mpz_set_ui(m_tmp,    static_cast<unsigned>(_v >> 32));
    mpz_mul(m_tmp, m_tmp, m_two32);
    mpz_add(*c.m_ptr, *c.m_ptr, m_tmp);
    if (sign)
        mpz_neg(*c.m_ptr, *c.m_ptr);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::set_big_ui64(mpz & c, uint64 v) {
#ifndef _MP_GMP
    if (is_small(c)) {
        c.m_ptr = allocate(m_init_cell_capacity);
    }
    SASSERT(capacity(c) >= m_init_cell_capacity);
    c.m_val = 1;
    if (sizeof(digit_t) == sizeof(uint64)) {
        // 64-bit machine
        digits(c)[0] = static_cast<digit_t>(v);
        c.m_ptr->m_size = 1;
    }
    else {
        // 32-bit machine
        digits(c)[0] = static_cast<unsigned>(v);
        digits(c)[1] = static_cast<unsigned>(v >> 32);
        c.m_ptr->m_size = digits(c)[1] == 0 ? 1 : 2;
    }
#else
    if (is_small(c)) {
        c.m_ptr = allocate();
    }
    mpz_set_ui(*c.m_ptr, static_cast<unsigned>(v));
    mpz_set_ui(m_tmp,    static_cast<unsigned>(v >> 32));
    mpz_mul(m_tmp, m_tmp, m_two32);
    mpz_add(*c.m_ptr, *c.m_ptr, m_tmp);
#endif
}

#ifndef _MP_GMP
template<bool SYNCH>
template<int IDX>
void mpz_manager<SYNCH>::set(mpz & a, int sign, unsigned sz) {
#if 0
    static unsigned max_sz = 0;
    if (sz > max_sz) {
        max_sz = sz;
        verbose_stream() << "max_sz: " << max_sz << "\n";
    }
#endif
    unsigned i = sz;
    for (; i > 0; --i) {
        if (m_tmp[IDX]->m_digits[i-1] != 0)
            break;
    }

    if (i == 0) {
        // m_tmp[IDX] is zero
        reset(a);
        return;
    }
    
    if (i == 1 && m_tmp[IDX]->m_digits[0] <= INT_MAX) {
        // m_tmp[IDX] fits is a fixnum
        del(a);
        a.m_val = sign < 0 ? -static_cast<int>(m_tmp[IDX]->m_digits[0]) : static_cast<int>(m_tmp[IDX]->m_digits[0]);
        return;
    }

    a.m_val = sign;
    std::swap(a.m_ptr, m_tmp[IDX]);
    a.m_ptr->m_size = i;
    if (!m_tmp[IDX]) // 'a' was a small number
        m_tmp[IDX] = allocate(m_init_cell_capacity);
}
#endif

template<bool SYNCH>
void mpz_manager<SYNCH>::set(mpz & a, char const * val) {
    reset(a);
    mpz ten(10);
    mpz tmp;
    char const * str = val;
    bool sign = false;
    while (str[0] == ' ') ++str;
    if (str[0] == '-') 
        sign = true;
    while (str[0]) {
        if ('0' <= str[0] && str[0] <= '9') {
            SASSERT(str[0] - '0' <= 9);
            mul(a, ten, tmp);
            add(tmp, mk_z(str[0] - '0'), a); 
        }
        ++str;
    }
    del(tmp);
    if (sign)
        neg(a);
}

template<bool SYNCH>
void mpz_manager<SYNCH>::set(mpz & target, unsigned sz, digit_t const * digits) {
    // remove zero digits
    while (sz > 0 && digits[sz - 1] == 0)
        sz--;
    if (sz == 0)
        reset(target);
    else if (sz == 1)
        set(target, digits[0]);
    else {
#ifndef _MP_GMP
        target.m_val = 1; // number is positive.
        if (is_small(target)) {
            unsigned c = sz < m_init_cell_capacity ? m_init_cell_capacity : sz;
            target.m_ptr             = allocate(c);
            target.m_ptr->m_size     = sz;
            target.m_ptr->m_capacity = c;
            memcpy(target.m_ptr->m_digits, digits, sizeof(digit_t) * sz);
        }
        else {
            if (capacity(target) < sz) {
                SASSERT(sz > m_init_cell_capacity);
                deallocate(target.m_ptr);
                target.m_ptr = allocate(sz);
                target.m_ptr->m_size     = sz;
                target.m_ptr->m_capacity = sz;
                memcpy(target.m_ptr->m_digits, digits, sizeof(digit_t) * sz);
            }
            else {
                target.m_ptr->m_size = sz;
                memcpy(target.m_ptr->m_digits, digits, sizeof(digit_t) * sz);
            }
        }
#else
	mk_big(target);
	// reset
	mpz_set_ui(*target.m_ptr, digits[sz - 1]);
	SASSERT(sz > 0);
	unsigned i = sz - 1;
	while (i > 0) {
	  --i;
	  mpz_mul_2exp(*target.m_ptr, *target.m_ptr, 32);
	  mpz_set_ui(m_tmp, digits[i]);
	  mpz_add(*target.m_ptr, *target.m_ptr, m_tmp);
	}
#endif        
    }
}

#ifndef _MP_GMP
template<bool SYNCH>
template<bool SUB>
void mpz_manager<SYNCH>::big_add_sub(mpz const & a, mpz const & b, mpz & c) {
    int sign_a;
    int sign_b;
    mpz_cell * cell_a;
    mpz_cell * cell_b;
    get_sign_cell<0>(a, sign_a, cell_a);
    get_sign_cell<1>(b, sign_b, cell_b);
    if (SUB)
        sign_b = -sign_b;
    size_t real_sz;
    if (sign_a == sign_b) {
        unsigned sz  = max(cell_a->m_size, cell_b->m_size)+1;
        ensure_tmp_capacity<0>(sz);
        add_full(cell_a->m_digits,
                 cell_a->m_size,
                 cell_b->m_digits,
                 cell_b->m_size,
                 m_tmp[0]->m_digits,
                 sz,
                 &real_sz,
                 0);
        SASSERT(real_sz <= sz);
        set<0>(c, sign_a, static_cast<unsigned>(real_sz));
    }
    else {
        digit_t borrow;
        int r = compare_diff(cell_a->m_digits,
                             cell_a->m_size,
                             cell_b->m_digits,
                             cell_b->m_size);
        if (r == 0) {
            reset(c);
        }
        else if (r < 0) {
            // a < b
            unsigned sz = cell_b->m_size;
            ensure_tmp_capacity<0>(sz);
            sub_diff(cell_b->m_digits,
                     cell_b->m_size,
                     cell_a->m_digits,
                     cell_a->m_size,
                     m_tmp[0]->m_digits,
                     &borrow,
                     0);
            SASSERT(borrow == 0);
            set<0>(c, sign_b, sz);
        }
        else {
            // a > b
            unsigned sz = cell_a->m_size;
            ensure_tmp_capacity<0>(sz);
            sub_diff(cell_a->m_digits,
                     cell_a->m_size,
                     cell_b->m_digits,
                     cell_b->m_size,
                     m_tmp[0]->m_digits,
                     &borrow,
                     0);
            SASSERT(borrow == 0);
            set<0>(c, sign_a, sz);
        }
    }
}
#endif

template<bool SYNCH>
void mpz_manager<SYNCH>::big_add(mpz const & a, mpz const & b, mpz & c) {
#ifndef _MP_GMP
    big_add_sub<false>(a, b, c);
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    mk_big(c);
    mpz_add(*c.m_ptr, *arg0, *arg1);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::big_sub(mpz const & a, mpz const & b, mpz & c) {
#ifndef _MP_GMP
    big_add_sub<true>(a, b, c);
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    mk_big(c);
    mpz_sub(*c.m_ptr, *arg0, *arg1);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::big_mul(mpz const & a, mpz const & b, mpz & c) {
#ifndef _MP_GMP
    int sign_a;
    int sign_b;
    mpz_cell * cell_a;
    mpz_cell * cell_b;
    get_sign_cell<0>(a, sign_a, cell_a);
    get_sign_cell<1>(b, sign_b, cell_b);
    unsigned sz  = cell_a->m_size + cell_b->m_size;
    ensure_tmp_capacity<0>(sz);
    multiply(cell_a->m_digits,
             cell_a->m_size,
             cell_b->m_digits,
             cell_b->m_size,
             m_tmp[0]->m_digits,
             0);
    set<0>(c, sign_a == sign_b ? 1 : -1, sz);
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    mk_big(c);
    mpz_mul(*c.m_ptr, *arg0, *arg1);
#endif
}

#ifndef _MP_GMP
template<bool SYNCH>
template<qr_mode MODE>
void mpz_manager<SYNCH>::quot_rem_core(mpz const & a, mpz const & b, mpz & q, mpz & r) {
    /*
      +26 / +7 = +3, remainder is +5
      -26 / +7 = -3, remainder is -5
      +26 / -7 = -3, remainder is +5
      -26 / -7 = +3, remainder is -5 
    */
    int sign_a;
    int sign_b;
    mpz_cell * cell_a;
    mpz_cell * cell_b;
    get_sign_cell<0>(a, sign_a, cell_a);
    get_sign_cell<1>(b, sign_b, cell_b);
    if (cell_b->m_size > cell_a->m_size) {
        if (MODE == REM_ONLY || MODE == QUOT_AND_REM)
            set(r, a); 
        if (MODE == QUOT_ONLY || MODE == QUOT_AND_REM)
            reset(q);
        return;
    }
    unsigned q_sz = cell_a->m_size - cell_b->m_size + 1;
    unsigned r_sz = cell_b->m_size;
    ensure_tmp_capacity<0>(q_sz);
    ensure_tmp_capacity<1>(r_sz);
    divide(cell_a->m_digits,
           cell_a->m_size,
           cell_b->m_digits,
           cell_b->m_size,
           reciprocal_1_NULL,
           m_tmp[0]->m_digits,
           m_tmp[1]->m_digits,
           0);
    if (MODE == QUOT_ONLY || MODE == QUOT_AND_REM)
        set<0>(q, sign_a == sign_b ? 1 : -1, q_sz);
    if (MODE == REM_ONLY || MODE == QUOT_AND_REM)
        set<1>(r, sign_a, r_sz);
}
#endif

template<bool SYNCH>
void mpz_manager<SYNCH>::big_div_rem(mpz const & a, mpz const & b, mpz & q, mpz & r) {
#ifndef _MP_GMP
    quot_rem_core<QUOT_AND_REM>(a, b, q, r);
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    mk_big(q);
    mk_big(r);
    mpz_tdiv_qr(*q.m_ptr, *r.m_ptr, *arg0, *arg1);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::big_div(mpz const & a, mpz const & b, mpz & c) {
#ifndef _MP_GMP
    mpz dummy;
    quot_rem_core<QUOT_ONLY>(a, b, c, dummy);
    SASSERT(is_zero(dummy));
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    mk_big(c);
    mpz_tdiv_q(*c.m_ptr, *arg0, *arg1);
#endif
} 

template<bool SYNCH>
void mpz_manager<SYNCH>::big_rem(mpz const & a, mpz const & b, mpz & c) {
#ifndef _MP_GMP
    mpz dummy;
    quot_rem_core<REM_ONLY>(a, b, dummy, c);
    SASSERT(is_zero(dummy));
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    mk_big(c);
    mpz_tdiv_r(*c.m_ptr, *arg0, *arg1);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::gcd(mpz const & a, mpz const & b, mpz & c) {
    if (is_small(a) && is_small(b)) {
        int _a = a.m_val;
        int _b = b.m_val;
        if (_a < 0) _a = -_a;
        if (_b < 0) _b = -_b;
        unsigned r = u_gcd(_a, _b);
        // Remark: r is (INT_MAX + 1)
        // If a == b == INT_MIN
        set(c, r);
    }
    else {
#ifdef _MP_GMP
        mpz_t * arg0;
        mpz_t * arg1;
        get_arg<0>(a, arg0);
        get_arg<1>(b, arg1);
        mk_big(c);
        mpz_gcd(*c.m_ptr, *arg0, *arg1);
        return;
#endif
        if (is_zero(a)) {
            set(c, b);
            abs(c);
            return;
        }
        if (is_zero(b)) {
            set(c, a);
            abs(c);
            return;
        }
#ifdef BINARY_GCD
        // Binary GCD for big numbers
        // - It doesn't use division
        // - The initial experiments, don't show any performance improvement
        // - It only works with _MP_INTERNAL and _MP_MSBIGNUM
        mpz u, v, diff;
        set(u, a);
        set(v, b);
        abs(u);
        abs(v);

        unsigned k_u = power_of_two_multiple(u);
        unsigned k_v = power_of_two_multiple(v);
        unsigned k   = k_u < k_v ? k_u : k_v;

        machine_div2k(u, k_u);

        while (true) {
            machine_div2k(v, k_v);
 
            if (lt(u, v)) {
                sub(v, u, v);
            } 
            else {
                sub(u, v, diff);
                swap(u, v);
                swap(v, diff);
            }
            
            if (is_zero(v) || is_one(v))
                break;
            
            // reset least significant bit
            if (is_small(v))
                v.m_val &= ~1;
            else
                v.m_ptr->m_digits[0] &= ~static_cast<digit_t>(1);
            k_v = power_of_two_multiple(v);
        }

        mul2k(u, k, c);
        del(u); del(v); del(diff);
#endif // BINARY_GCD

#ifdef EUCLID_GCD
        mpz tmp1;
        mpz tmp2;
        mpz aux;
        set(tmp1, a);
        set(tmp2, b);
        abs(tmp1);
        abs(tmp2);
        if (lt(tmp1, tmp2))
            swap(tmp1, tmp2);
        if (is_zero(tmp2)) {
            swap(c, tmp1);
        }
        else {
            while (true) {
                if (is_uint64(tmp1) && is_uint64(tmp2)) {
                    set(c, u64_gcd(get_uint64(tmp1), get_uint64(tmp2)));
                    break;
                }
                rem(tmp1, tmp2, aux);
                if (is_zero(aux)) {
                    swap(c, tmp2);
                    break;
                }
                swap(tmp1, tmp2);
                swap(tmp2, aux);
            }
        }
        del(tmp1); del(tmp2); del(aux);
#endif // EUCLID_GCD

#ifdef LS_BINARY_GCD
        mpz u, v, t, u1, u2;
        set(u, a);
        set(v, b);
        abs(u);
        abs(v);
        if (lt(u, v))
            swap(u, v);
        while (!is_zero(v)) {
            // Basic idea:
            // compute t = 2^e*v  such that t <= u < 2t
            // u := min{u - t, 2t - u}
            // 
            // The assignment u := min{u - t, 2t - u}
            // can be replaced with u := u - t
            // 
            // Since u and v are positive, we have:
            //    2^{log2(u)}     <= u < 2^{(log2(u) + 1)}
            //    2^{log2(v)}     <= v < 2^{(log2(v) + 1)}
            //  -->
            //    2^{log2(v)}*2^{log2(u)-log2(v)} <= v*2^{log2(u)-log2(v)} < 2^{log2(v) + 1}*2^{log2(u)-log2(v)}
            //  -->
            //    2^{log2(u)} <= v*2^{log2(u)-log2(v)} < 2^{log2(u) + 1}
            //  
            // Now, let t be v*2^{log2(u)-log2(v)}
            // If t <= u, then we found t
            // Otherwise t = t div 2
            unsigned k_u = log2(u);
            unsigned k_v = log2(v);
            SASSERT(k_v <= k_u);
            unsigned e   = k_u - k_v;
            mul2k(v, e, t);
            sub(u, t, u1);
            if (is_neg(u1)) {
                // t is too big
                machine_div2k(t, 1);
                // Now, u1 contains u - 2t
                neg(u1); 
                // Now, u1 contains 2t - u
                sub(u, t, u2); // u2 := u - t
            }
            else {
                // u1 contains u - t
                mul2k(t, 1);
                sub(t, u, u2);
                // u2 contains 2t - u
            }
            SASSERT(is_nonneg(u1));
            SASSERT(is_nonneg(u2));
            if (lt(u1, u2))
                swap(u, u1);
            else
                swap(u, u2);
            if (lt(u, v))
                swap(u,v);
        }
        swap(u, c);
        del(u); del(v); del(t); del(u1); del(u2);
#endif // LS_BINARY_GCD

#ifdef LEHMER_GCD
        // For now, it only works if sizeof(digit_t) == sizeof(unsigned)
        COMPILE_TIME_ASSERT(sizeof(digit_t) == sizeof(unsigned));
        
        int64 a_hat, b_hat, A, B, C, D, T, q, a_sz, b_sz;
        mpz a1, b1, t, r, tmp;
        set(a1, a);
        set(b1, b);
        abs(a1);
        abs(b1);
        if (lt(a1, b1))
            swap(a1, b1);
        while (true) {
            SASSERT(ge(a1, b1));
            if (is_small(b1)) {
                if (is_small(a1)) {
                    unsigned r = u_gcd(a1.m_val, b1.m_val);
                    set(c, r);
                    break;
                }
                else {
                    while (!is_zero(b1)) {
                        SASSERT(ge(a1, b1));
                        rem(a1, b1, tmp);
                        swap(a1, b1);
                        swap(b1, tmp);
                    }
                    swap(c, a1);
                    break;
                }
            }
            SASSERT(!is_small(a1));
            SASSERT(!is_small(b1));
            a_sz  = a1.m_ptr->m_size;
            b_sz  = b1.m_ptr->m_size;
            SASSERT(b_sz <= a_sz);
            a_hat = a1.m_ptr->m_digits[a_sz - 1];
            b_hat = (b_sz == a_sz) ? b1.m_ptr->m_digits[b_sz - 1] : 0;
            A = 1; 
            B = 0;
            C = 0;
            D = 1;
            while (true) {
                // Loop invariants
                SASSERT(a_hat + A <= static_cast<int64>(UINT_MAX) + 1);
                SASSERT(a_hat + B <  static_cast<int64>(UINT_MAX) + 1);
                SASSERT(b_hat + C <  static_cast<int64>(UINT_MAX) + 1);
                SASSERT(b_hat + D <= static_cast<int64>(UINT_MAX) + 1);
                // overflows can't happen since I'm using int64
                if (b_hat + C == 0 || b_hat + D == 0)
                    break;
                q  = (a_hat + A)/(b_hat + C);
                if (q != (a_hat + B)/(b_hat + D))
                    break;
                T = A - q*C;
                A = C;
                C = T;
                T = B - q*D;
                B = D;
                D = T;
                T = a_hat - q*b_hat;
                a_hat = b_hat;
                b_hat = T;
            }
            SASSERT(ge(a1, b1));
            if (B == 0) {
                rem(a1, b1, t);
                swap(a1, b1);
                swap(b1, t);
                SASSERT(ge(a1, b1));
            }
            else {
                // t <- A*a1
                set(tmp, A);
                mul(a1, tmp, t); 
                // t <- t + B*b1
                set(tmp, B);
                addmul(t, tmp, b1, t);
                // r <- C*a1
                set(tmp, C);
                mul(a1, tmp, r);
                // r <- r + D*b1
                set(tmp, D);
                addmul(r, tmp, b1, r);
                // a <- t
                swap(a1, t);
                // b <- r
                swap(b1, r);
                SASSERT(ge(a1, b1));
            }
        }
        del(a1); del(b1); del(r); del(t); del(tmp);
#endif // LEHMER_GCD
    }
}

template<bool SYNCH>
unsigned mpz_manager<SYNCH>::size_info(mpz const & a) {
    if (is_small(a))
        return 1;
#ifndef _MP_GMP
    return a.m_ptr->m_size + 1;
#else
    return mpz_size(*a.m_ptr);
#endif
}

template<bool SYNCH>
struct mpz_manager<SYNCH>::sz_lt {
    mpz_manager<SYNCH> & m;
    mpz const *          m_as;
    
    sz_lt(mpz_manager<SYNCH> & _m, mpz const * as):m(_m), m_as(as) {}

    bool operator()(unsigned p1, unsigned p2) {
        return m.size_info(m_as[p1]) < m.size_info(m_as[p2]);
    }
};

template<bool SYNCH>
void mpz_manager<SYNCH>::gcd(unsigned sz, mpz const * as, mpz & g) {
#if 0
    // Optimization: sort numbers by size. Motivation: compute the gcd of the small ones first.
    // The optimization did not really help.
    switch (sz) {
    case 0:
        reset(g);
        return;
    case 1:
        set(g, as[0]);
        abs(g);
        return;
    case 2:
        gcd(as[0], as[1], g);
        return;
    default:
        break;
    }
    unsigned i;
    for (i = 0; i < sz; i++) {
        if (!is_small(as[i]))
            break;
    }
    if (i != sz) {
        // array has big numbers
        sbuffer<unsigned, 1024> p;
        for (i = 0; i < sz; i++)
            p.push_back(i);
        sz_lt lt(*this, as);
        std::sort(p.begin(), p.end(), lt);
        TRACE("mpz_gcd", for (unsigned i = 0; i < sz; i++) tout << p[i] << ":" << size_info(as[p[i]]) << " "; tout << "\n";);
        gcd(as[p[0]], as[p[1]], g);
        for (i = 2; i < sz; i++) {
            if (is_one(g))
                return;
            gcd(g, as[p[i]], g);
        }
        return;
    }
    else {
        gcd(as[0], as[1], g);
        for (unsigned i = 2; i < sz; i++) {
            if (is_one(g))
                return;
            gcd(g, as[i], g);
        }
    }
#else
    // Vanilla implementation
    switch (sz) {
    case 0:
        reset(g);
        return;
    case 1:
        set(g, as[0]);
        abs(g);
        return;
    default:
        break;
    }
    gcd(as[0], as[1], g);
    for (unsigned i = 2; i < sz; i++) {
        if (is_one(g))
            return;
        gcd(g, as[i], g);
    }
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::gcd(mpz const & r1, mpz const & r2, mpz & a, mpz & b, mpz & r) {
    mpz tmp1, tmp2;
    mpz aux, quot;
    set(tmp1, r1);
    set(tmp2, r2);
    set(a, 1);
    set(b, 0);
    mpz nexta, nextb;
    set(nexta, 0);
    set(nextb, 1);

    abs(tmp1);
    abs(tmp2);
    if (lt(tmp1, tmp2)) {
        swap(tmp1, tmp2);
        swap(nexta, nextb);
        swap(a, b);
    }

    // tmp1 >= tmp2 >= 0
    // quot_rem in one function would be faster.
    while (is_pos(tmp2)) {
        SASSERT(ge(tmp1, tmp2));

        // aux = tmp2
        set(aux, tmp2);
        // quot = div(tmp1, tmp2);
        machine_div(tmp1, tmp2, quot);
        // tmp2 = tmp1 % tmp2
        rem(tmp1, tmp2, tmp2);
        // tmp1 = aux
        set(tmp1, aux);
        // aux = nexta
        set(aux, nexta);
        // nexta = a - (quot*nexta)
        mul(quot, nexta, nexta);
        sub(a, nexta, nexta);
        // a = axu
        set(a, aux);
        // aux = nextb
        set(aux, nextb);
        // nextb = b - (quot*nextb)
        mul(nextb, quot, nextb);
        sub(b, nextb, nextb);
        // b = aux
        set(b, aux);
    }

    if (is_neg(r1))
        neg(a);
    if (is_neg(r2))
        neg(b);

    // SASSERT((a*r1) + (b*r2) == tmp1);
#ifdef Z3DEBUG
    mul(a, r1, nexta);
    mul(b, r2, nextb);
    add(nexta, nextb, nexta);
    SASSERT(eq(nexta, tmp1));
#endif
    set(r, tmp1);
    del(tmp1);
    del(tmp2);
    del(aux);
    del(quot);
    del(nexta);
    del(nextb);
}

template<bool SYNCH>
void mpz_manager<SYNCH>::lcm(mpz const & a, mpz const & b, mpz & c) {
    if (is_one(b)) {
        set(c, a);
        TRACE("lcm_bug", tout << "1. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";);
    }
    else if (is_one(a) || eq(a, b)) {
        set(c, b);
        TRACE("lcm_bug", tout << "2. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";);
    }
    else {
        mpz r;
        gcd(a, b, r);
        TRACE("lcm_bug", tout << "gcd(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(r) << "\n";);
        if (eq(r, a)) {
            set(c, b);
            TRACE("lcm_bug", tout << "3. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";);
        }
        else if (eq(r, b)) {
            set(c, a);
            TRACE("lcm_bug", tout << "4. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";);
        }
        else {
            // c contains gcd(a, b)   
            // so c divides a, and machine_div(a, c) is equal to div(a, c)
            machine_div(a, r, r);
            mul(r, b, c);
            TRACE("lcm_bug", tout << "5. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";);
        }
        del(r);
    }
}

template<bool SYNCH>
void mpz_manager<SYNCH>::bitwise_or(mpz const & a, mpz const & b, mpz & c) {
    SASSERT(is_nonneg(a));
    SASSERT(is_nonneg(b));
    TRACE("mpz", tout << "is_small(a): " << is_small(a) << ", is_small(b): " << is_small(b) << "\n";);
    if (is_small(a) && is_small(b)) {
        del(c);
        c.m_val = a.m_val | b.m_val;
    }
    else {
#ifndef _MP_GMP
        mpz a1, b1, a2, b2, m, tmp;
        set(a1, a);
        set(b1, b);
        set(m, 1);
        reset(c);
        while (!is_zero(a1) && !is_zero(b1)) {
            TRACE("mpz", tout << "a1: " << to_string(a1) << ", b1: " << to_string(b1) << "\n";);
            mod(a1, m_two64, a2);
            mod(b1, m_two64, b2);
            TRACE("mpz", tout << "a2: " << to_string(a2) << ", b2: " << to_string(b2) << "\n";);
            uint64 v = get_uint64(a2) | get_uint64(b2);
            TRACE("mpz", tout << "uint(a2): " << get_uint64(a2) << ", uint(b2): " << get_uint64(b2) << "\n";);
            set(tmp, v);
            mul(tmp, m, tmp);
            add(c, tmp, c); // c += m * v
            mul(m, m_two64, m);
            div(a1, m_two64, a1);
            div(b1, m_two64, b1);
        }
        if (!is_zero(a1)) {
            mul(a1, m, a1);
            add(c, a1, c);
        }
        if (!is_zero(b1)) {
            mul(b1, m, b1);
            add(c, b1, c);
        }
        del(a1); del(b1); del(a2); del(b2); del(m); del(tmp);
#else
        mpz_t * arg0;
        mpz_t * arg1;
        get_arg<0>(a, arg0);
        get_arg<1>(b, arg1);
        mk_big(c);
        mpz_ior(*c.m_ptr, *arg0, *arg1);
#endif
    }
}

template<bool SYNCH>
void mpz_manager<SYNCH>::bitwise_and(mpz const & a, mpz const & b, mpz & c) {
    SASSERT(is_nonneg(a));
    SASSERT(is_nonneg(b));
    if (is_small(a) && is_small(b)) {
        del(c);
        c.m_val = a.m_val & b.m_val;
    }
    else {
#ifndef _MP_GMP
        mpz a1, b1, a2, b2, m, tmp;
        set(a1, a);
        set(b1, b);
        set(m, 1);
        reset(c);
        while (!is_zero(a1) && !is_zero(b1)) {
            mod(a1, m_two64, a2);
            mod(b1, m_two64, b2);
            uint64 v = get_uint64(a2) & get_uint64(b2);
            set(tmp, v);
            mul(tmp, m, tmp);
            add(c, tmp, c); // c += m * v
            mul(m, m_two64, m);
            div(a1, m_two64, a1);
            div(b1, m_two64, b1);
        }
        del(a1); del(b1); del(a2); del(b2); del(m); del(tmp);
#else
        mpz_t * arg0;
        mpz_t * arg1;
        get_arg<0>(a, arg0);
        get_arg<1>(b, arg1);
        mk_big(c);
        mpz_and(*c.m_ptr, *arg0, *arg1);
#endif
    }
}

template<bool SYNCH>
void mpz_manager<SYNCH>::bitwise_xor(mpz const & a, mpz const & b, mpz & c) {
    SASSERT(is_nonneg(a));
    SASSERT(is_nonneg(b));
    if (is_small(a) && is_small(b)) {
        set_i64(c, i64(a) ^ i64(b));
    }
    else {
#ifndef _MP_GMP
        mpz a1, b1, a2, b2, m, tmp;
        set(a1, a);
        set(b1, b);
        set(m, 1);
        reset(c);
        while (!is_zero(a1) && !is_zero(b1)) {
            mod(a1, m_two64, a2);
            mod(b1, m_two64, b2);
            uint64 v = get_uint64(a2) ^ get_uint64(b2);
            set(tmp, v);
            mul(tmp, m, tmp);
            add(c, tmp, c); // c += m * v
            mul(m, m_two64, m);
            div(a1, m_two64, a1);
            div(b1, m_two64, b1);
        }
        if (!is_zero(a1)) {
            mul(a1, m, a1);
            add(c, a1, c);
        }
        if (!is_zero(b1)) {
            mul(b1, m, b1);
            add(c, b1, c);
        }
        del(a1); del(b1); del(a2); del(b2); del(m); del(tmp);
#else
        mpz_t * arg0;
        mpz_t * arg1;
        get_arg<0>(a, arg0);
        get_arg<1>(b, arg1);
        mk_big(c);
        mpz_xor(*c.m_ptr, *arg0, *arg1);
#endif
    }
}

template<bool SYNCH>
void mpz_manager<SYNCH>::bitwise_not(unsigned sz, mpz const & a, mpz & c) {
    SASSERT(is_nonneg(a));
    if (is_small(a) && sz <= 63) {
        int64 mask = (static_cast<int64>(1) << sz) - static_cast<int64>(1);
        set_i64(c, (~ i64(a)) & mask);
    }
    else {
        mpz a1, a2, m, tmp;
        set(a1, a);
        set(m, 1);
        set(c, 0);
        while (sz > 0) {
            mod(a1, m_two64, a2);
            uint64 n = get_uint64(a2);
            uint64 v = ~n;
            SASSERT(~v == n);
            if (sz < 64) {
                uint64 mask = (1ull << static_cast<uint64>(sz)) - 1ull;
                v = mask & v;
            }
            TRACE("bitwise_not", tout << "sz: " << sz << ", v: " << v << ", n: " << n << "\n";);
            set(tmp, v);
            SASSERT(get_uint64(tmp) == v);
            mul(tmp, m, tmp);
            add(c, tmp, c); // c += m * v
            mul(m, m_two64, m);
            div(a1, m_two64, a1);
            sz -= (sz<64) ? sz : 64; 
        }
        del(a1); del(a2); del(m); del(tmp);
        TRACE("bitwise_not", tout << "sz: " << sz << " a: " << to_string(a) << " c: " << to_string(c) << "\n";);
    }
}

template<bool SYNCH>
void mpz_manager<SYNCH>::big_set(mpz & target, mpz const & source) {
#ifndef _MP_GMP
    if (&target == &source)
        return;
    target.m_val = source.m_val;
    if (is_small(target)) {
        target.m_ptr = allocate(capacity(source));
        target.m_ptr->m_size     = size(source);
        target.m_ptr->m_capacity = capacity(source);
        memcpy(target.m_ptr->m_digits, source.m_ptr->m_digits, sizeof(digit_t) * size(source));
    }
    else {
        if (capacity(target) < size(source)) {
            deallocate(target.m_ptr);
            target.m_ptr = allocate(capacity(source));
            target.m_ptr->m_size     = size(source);
            target.m_ptr->m_capacity = capacity(source);
            memcpy(target.m_ptr->m_digits, source.m_ptr->m_digits, sizeof(digit_t) * size(source));
        }
        else {
            target.m_ptr->m_size = size(source);
            memcpy(target.m_ptr->m_digits, source.m_ptr->m_digits, sizeof(digit_t) * size(source));
        }
    }
#else
    // GMP version
    mk_big(target);
    mpz_set(*target.m_ptr, *source.m_ptr);
#endif
}

template<bool SYNCH>
int mpz_manager<SYNCH>::big_compare(mpz const & a, mpz const & b) {
#ifndef _MP_GMP
    int sign_a;
    int sign_b;
    mpz_cell * cell_a;
    mpz_cell * cell_b;
    get_sign_cell<0>(a, sign_a, cell_a);
    get_sign_cell<1>(b, sign_b, cell_b);

    if (sign_a > 0) {
        // a is positive
        if (sign_b > 0) {
            // a & b are positive
            return compare_diff(cell_a->m_digits,
                                cell_a->m_size,
                                cell_b->m_digits,
                                cell_b->m_size);
        }
        else {
            // b is negative
            return 1; // a > b
        }
    }
    else {
        // a is negative
        if (sign_b > 0) {
            // b is positive
            return -1; // a < b
        }
        else {
            // a & b are negative
            return compare_diff(cell_b->m_digits,
                                cell_b->m_size,
                                cell_a->m_digits,
                                cell_a->m_size);
        }
    }
#else
    // GMP version
    mpz_t * arg0;
    mpz_t * arg1;
    get_arg<0>(a, arg0);
    get_arg<1>(b, arg1);
    return mpz_cmp(*arg0, *arg1);
#endif
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::is_uint64(mpz const & a) const {
#ifndef _MP_GMP
    if (a.m_val < 0)
        return false;
    if (is_small(a))
        return true;
    if (sizeof(digit_t) == sizeof(uint64)) {
        return size(a) <= 1;
    }
    else {
        return size(a) <= 2;
    }
#else
    // GMP version
    if (is_small(a))
        return a.m_val >= 0;
    return is_nonneg(a) && mpz_cmp(*a.m_ptr, m_uint64_max) <= 0;
#endif
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::is_int64(mpz const & a) const {
    if (is_small(a))
        return true;
#ifndef _MP_GMP
    if (!is_uint64(a)) 
        return false;
    uint64 num = get_uint64(a);
    uint64 msb = static_cast<uint64>(1) << 63;
    uint64 msb_val = msb & num;
    if (a.m_val >= 0) {
        // non-negative number.
        return (0 == msb_val);
    }
    else {
        // negative number.
        // either the high bit is 0, or
        // the number is 2^64 which can be represented.
        //
        return 0 == msb_val || (msb_val == num);
    }
#else
    // GMP version
    return mpz_cmp(*a.m_ptr, m_int64_max) <= 0;
#endif
}

template<bool SYNCH>
uint64 mpz_manager<SYNCH>::get_uint64(mpz const & a) const {
    if (is_small(a)) 
        return static_cast<uint64>(a.m_val);
#ifndef _MP_GMP
    SASSERT(a.m_ptr->m_size > 0);
    if (a.m_ptr->m_size == 1)
        return digits(a)[0];
    if (sizeof(digit_t) == sizeof(uint64))
        // 64-bit machine
        return digits(a)[0];
    else 
        // 32-bit machine
        return ((static_cast<uint64>(digits(a)[1]) << 32) | (static_cast<uint64>(digits(a)[0])));
#else
    // GMP version
    if (sizeof(uint64) == sizeof(unsigned long)) {
        return mpz_get_ui(*a.m_ptr);
    }
    else {
        mpz_manager * _this = const_cast<mpz_manager*>(this);
        mpz_set(_this->m_tmp, *a.m_ptr);
        mpz_mod(_this->m_tmp, m_tmp, m_two32);
        uint64 r = static_cast<uint64>(mpz_get_ui(m_tmp));
        mpz_set(_this->m_tmp, *a.m_ptr);
        mpz_div(_this->m_tmp, m_tmp, m_two32);
        r += static_cast<uint64>(mpz_get_ui(m_tmp)) << static_cast<uint64>(32);
        return r;
    }
#endif
}

template<bool SYNCH>
int64 mpz_manager<SYNCH>::get_int64(mpz const & a) const {
    if (is_small(a))
        return static_cast<int64>(a.m_val);
#ifndef _MP_GMP
    SASSERT(is_int64(a));
    uint64 num = get_uint64(a);
    if (a.m_val < 0) {
        if (num != 0 && (num << 1) == 0)
            return INT64_MIN;
        return -static_cast<int64>(num);
    }
    return static_cast<int64>(num);
#else
    // GMP
    if (sizeof(int64) == sizeof(long) || mpz_fits_slong_p(*a.m_ptr)) {
        return mpz_get_si(*a.m_ptr);
    }
    else {
        mpz_manager * _this = const_cast<mpz_manager*>(this);
        mpz_mod(_this->m_tmp, *a.m_ptr, m_two32);
        int64 r = static_cast<int64>(mpz_get_ui(m_tmp));
        mpz_div(_this->m_tmp, *a.m_ptr, m_two32);
        r += static_cast<int64>(mpz_get_si(m_tmp)) << static_cast<int64>(32);
        return r;
    }
#endif
}

template<bool SYNCH>
double mpz_manager<SYNCH>::get_double(mpz const & a) const {
    if (is_small(a))
        return static_cast<double>(a.m_val);
#ifndef _MP_GMP
    double r = 0.0;
    double d = 1.0;
    unsigned sz = size(a);
    for (unsigned i = 0; i < sz; i++) {
        r += d * static_cast<double>(digits(a)[i]);
        if (sizeof(digit_t) == sizeof(uint64))
            d *= static_cast<double>(UINT64_MAX); // 64-bit version
        else
            d *= static_cast<double>(UINT_MAX);   // 32-bit version
    }
    return a.m_val < 0 ? -r : r;
#else
    return mpz_get_d(*a.m_ptr);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::display(std::ostream & out, mpz const & a) const {
    if (is_small(a)) {
        out << a.m_val;
    }
    else {
#ifndef _MP_GMP
        if (a.m_val < 0)
            out << "-";
        if (sizeof(digit_t) == 4) {
            sbuffer<char, 1024> buffer(11*size(a), 0);
            out << mp_decimal(digits(a), size(a), buffer.begin(), buffer.size(), 0);
        }
        else {
            sbuffer<char, 1024> buffer(21*size(a), 0);
            out << mp_decimal(digits(a), size(a), buffer.begin(), buffer.size(), 0);
        }
#else
        // GMP version
        size_t sz = mpz_sizeinbase(*a.m_ptr, 10) + 2;
        sbuffer<char, 1024> buffer(sz, 0);
        mpz_get_str(buffer.c_ptr(), 10, *a.m_ptr);
        out << buffer.c_ptr();
#endif
    } 
}

template<bool SYNCH>
void mpz_manager<SYNCH>::display_smt2(std::ostream & out, mpz const & a, bool decimal) const {
    if (is_neg(a)) {
        mpz_manager<SYNCH> * _this = const_cast<mpz_manager<SYNCH>*>(this);
        _scoped_numeral<mpz_manager<SYNCH> > tmp(*_this);
        _this->set(tmp, a);
        _this->neg(tmp);
        out << "(- ";
        display(out, tmp);
        if (decimal)
            out << ".0";
        out << ")";
    }
    else {
        display(out, a);
        if (decimal)
            out << ".0";
    }
}

template<bool SYNCH>
std::string mpz_manager<SYNCH>::to_string(mpz const & a) const {
    std::ostringstream buffer;
    display(buffer, a);
    return buffer.str();
}

template<bool SYNCH>
unsigned mpz_manager<SYNCH>::hash(mpz const & a) {
    if (is_small(a))
        return a.m_val;
#ifndef _MP_GMP
    unsigned sz = size(a);
    if (sz == 1)
        return static_cast<unsigned>(digits(a)[0]);
    return string_hash(reinterpret_cast<char*>(digits(a)), sz * sizeof(digit_t), 17);
#else
    return mpz_get_si(*a.m_ptr);
#endif
}

template<bool SYNCH>
void mpz_manager<SYNCH>::power(mpz const & a, unsigned p, mpz & b) {
#ifdef _MP_GMP
    if (!is_small(a)) {
        mk_big(b);
        mpz_pow_ui(*b.m_ptr, *a.m_ptr, p);
        return;
    }
#endif
#ifndef _MP_GMP
    if (is_small(a)) {
        if (a.m_val == 2) {
            if (p < 8 * sizeof(int) - 1) {
                del(b);
                b.m_val = 1 << p;
            }
            else {
                unsigned sz    = p/(8 * sizeof(digit_t)) + 1;
                unsigned shift = p%(8 * sizeof(digit_t));
                SASSERT(sz > 0);
                allocate_if_needed(b, sz);
                SASSERT(b.m_ptr->m_capacity >= sz);
                b.m_ptr->m_size     = sz;
                for (unsigned i = 0; i < sz - 1; i++)
                    b.m_ptr->m_digits[i] = 0;
                b.m_ptr->m_digits[sz-1] = 1 << shift;
                b.m_val = 1;
            }
            return;
        }
        if (a.m_val == 0) {
            SASSERT(p != 0);
            reset(b);
            return;
        }
        if (a.m_val == 1) {
            set(b, 1);
            return;
        }
    }
#endif
    // general purpose
    unsigned mask = 1;
    mpz power;
    set(power, a);
    set(b, 1);
    while (mask <= p) {
        if (mask & p)
            mul(b, power, b);
        mul(power, power, power);
        mask = mask << 1;
    }
    del(power);
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::is_power_of_two(mpz const & a) {
    unsigned shift;
    return is_power_of_two(a, shift);
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::is_power_of_two(mpz const & a, unsigned & shift) {
    if (is_nonpos(a))
        return false;
    if (is_small(a)) {
        if (::is_power_of_two(a.m_val)) {
            shift = ::log2(a.m_val);
            return true;
        }
        else {
            return false;
        }
    }
#ifndef _MP_GMP
    mpz_cell * c     = a.m_ptr;
    unsigned sz      = c->m_size;
    digit_t * ds     = c->m_digits;
    for (unsigned i = 0; i < sz - 1; i++) {
        if (ds[i] != 0)
            return false;
    }
    digit_t v = ds[sz-1];
    if (!(v & (v - 1)) && v) {
        shift = log2(a);
        return true;
    }
    else {
        return false;
    }
#else
    if (mpz_popcount(*a.m_ptr) == 1) {
        shift = log2(a);
        return true;
    }
    else {
        return false;
    }
#endif
}

// Expand capacity of a
#ifndef _MP_GMP
template<bool SYNCH>
void mpz_manager<SYNCH>::ensure_capacity(mpz & a, unsigned capacity) {
    if (capacity <= 1)
        return;
    if (capacity < m_init_cell_capacity)
        capacity = m_init_cell_capacity;
    if (is_small(a)) {
        a.m_ptr = allocate(capacity);
        SASSERT(a.m_ptr->m_capacity == capacity);
        if (a.m_val == INT_MIN) {
            unsigned intmin_sz = m_int_min.m_ptr->m_size;
            for (unsigned i = 0; i < intmin_sz; i++)
                a.m_ptr->m_digits[i] = m_int_min.m_ptr->m_digits[i];
            a.m_val = -1;
            a.m_ptr->m_size = m_int_min.m_ptr->m_size;
        }
        else if (a.m_val < 0) {
            a.m_ptr->m_digits[0] = -a.m_val;
            a.m_val = -1;
            a.m_ptr->m_size = 1;
        }
        else {
            a.m_ptr->m_digits[0] =  a.m_val;
            a.m_val = 1;
            a.m_ptr->m_size = 1;
        }
    }
    else {
        if (a.m_ptr->m_capacity >= capacity)
            return;
        mpz_cell * new_cell = allocate(capacity);
        SASSERT(new_cell->m_capacity == capacity);
        unsigned old_sz  = a.m_ptr->m_size;
        new_cell->m_size = old_sz;
        for (unsigned i = 0; i < old_sz; i++)
            new_cell->m_digits[i] = a.m_ptr->m_digits[i];
        deallocate(a.m_ptr);
        a.m_ptr = new_cell;
    }
}

template<bool SYNCH>
void mpz_manager<SYNCH>::normalize(mpz & a) {
    mpz_cell * c = a.m_ptr;
    digit_t * ds = c->m_digits;
    unsigned i = c->m_size;
    for (; i > 0; --i) {
        if (ds[i-1] != 0)
            break;
    }

    if (i == 0) {
        // a is zero...
        reset(a);
        return;
    }
    
    if (i == 1 && ds[0] <= INT_MAX) {
        // a is small
        int val = a.m_val < 0 ? -static_cast<int>(ds[0]) : static_cast<int>(ds[0]);
        del(a);
        a.m_val = val;
        return;
    }
    // adjust size
    c->m_size = i;
}
#endif

template<bool SYNCH>
void mpz_manager<SYNCH>::machine_div2k(mpz & a, unsigned k) {
    if (k == 0 || is_zero(a))
        return;
    if (is_small(a)) {
        if (k < 32) {
            int twok = 1 << k;
            a.m_val /= twok;
        }
        else {
            a.m_val = 0;
        }
        return;
    }
#ifndef _MP_GMP
    unsigned digit_shift = k / (8 * sizeof(digit_t));
    mpz_cell * c         = a.m_ptr;
    unsigned sz          = c->m_size;
    if (digit_shift >= sz) {
        reset(a);
        return;
    }
    unsigned bit_shift   = k % (8 * sizeof(digit_t));
    unsigned comp_shift  = (8 * sizeof(digit_t)) - bit_shift;
    unsigned new_sz      = sz - digit_shift;
    SASSERT(new_sz >= 1);
    digit_t * ds = c->m_digits;
    TRACE("mpz_2k", tout << "bit_shift: " << bit_shift << ", comp_shift: " << comp_shift << ", new_sz: " << new_sz << ", sz: " << sz << "\n";);
    if (new_sz < sz) {
        unsigned i       = 0;
        unsigned j       = digit_shift;
        if (bit_shift != 0) {
            for (; i < new_sz - 1; i++, j++) {
                ds[i] = ds[j];
                ds[i] >>= bit_shift;
                ds[i] |= (ds[j+1] << comp_shift);
            }
            ds[i] = ds[j];
            ds[i] >>= bit_shift;
        }
        else {
            for (; i < new_sz; i++, j++) {
                ds[i] = ds[j];
            }
        }
    }
    else {
        SASSERT(new_sz == sz);
        SASSERT(bit_shift != 0);
        unsigned i       = 0;
        for (; i < new_sz - 1; i++) {
            ds[i] >>= bit_shift;
            ds[i] |= (ds[i+1] << comp_shift);
        }
        ds[i] >>= bit_shift;
    }
    
    c->m_size = new_sz;
    normalize(a);
#else
    MPZ_BEGIN_CRITICAL();
    mpz_t * arg0;
    get_arg<0>(a, arg0);
    mpz_tdiv_q_2exp(m_tmp, *arg0, k);
    mk_big(a);
    mpz_swap(*a.m_ptr, m_tmp);
    MPZ_END_CRITICAL();
#endif    
}

template<bool SYNCH>
void mpz_manager<SYNCH>::mul2k(mpz & a, unsigned k) {
    if (k == 0 || is_zero(a))
        return;
    if (is_small(a) && k < 32) {
        set_i64(a, i64(a) * (static_cast<int64>(1) << k));
        return;
    }
#ifndef _MP_GMP
    TRACE("mpz_mul2k", tout << "mul2k\na: " << to_string(a) << "\nk: " << k << "\n";);
    unsigned word_shift  = k / (8 * sizeof(digit_t));
    unsigned bit_shift   = k % (8 * sizeof(digit_t));
    unsigned old_sz      = is_small(a) ? 1 : a.m_ptr->m_size;
    unsigned new_sz      = old_sz + word_shift + 1;
    ensure_capacity(a, new_sz);
    TRACE("mpz_mul2k", tout << "word_shift: " << word_shift << "\nbit_shift: " << bit_shift << "\nold_sz: " << old_sz << "\nnew_sz: " << new_sz 
          << "\na after ensure capacity:\n" << to_string(a) << "\n";);
    SASSERT(!is_small(a));
    mpz_cell * cell_a    = a.m_ptr;
    old_sz = cell_a->m_size;
    digit_t * ds         = cell_a->m_digits;
    for (unsigned i = old_sz; i < new_sz; i++) 
        ds[i] = 0;
    cell_a->m_size       = new_sz;

    if (word_shift > 0) {
        unsigned j = old_sz;
        unsigned i = old_sz + word_shift;
        while (j > 0) {
            --j; --i;
            ds[i] = ds[j];
        }
        while (i > 0) {
            --i;
            ds[i] = 0;
        }
    }
    if (bit_shift > 0) {
        DEBUG_CODE({
            for (unsigned i = 0; i < word_shift; i++) {
                SASSERT(ds[i] == 0);
            }
        });
        unsigned comp_shift = (8 * sizeof(digit_t)) - bit_shift;
        digit_t prev = 0;
        for (unsigned i = word_shift; i < new_sz; i++) {
            digit_t new_prev = (ds[i] >> comp_shift);
            ds[i] <<= bit_shift;
            ds[i] |= prev;
            prev = new_prev;
        }
    }
    normalize(a);
    TRACE("mpz_mul2k", tout << "mul2k result:\n" << to_string(a) << "\n";);
#else
    mpz_t * arg0;
    get_arg<0>(a, arg0);
    mk_big(a);
    mpz_mul_2exp(*a.m_ptr, *arg0, k);
#endif
}

#ifndef _MP_GMP
COMPILE_TIME_ASSERT(sizeof(digit_t) == 4 || sizeof(digit_t) == 8);
#endif

template<bool SYNCH>
unsigned mpz_manager<SYNCH>::power_of_two_multiple(mpz const & a) {
    if (is_zero(a))
        return 0;
    if (is_small(a)) {
        unsigned r = 0;
        int v      = a.m_val;
#define COUNT_DIGIT_RIGHT_ZEROS()               \
        if (v % (1 << 16) == 0) {               \
            r += 16;                            \
            v /= (1 << 16);                     \
        }                                       \
        if (v % (1 << 8) == 0) {                \
            r += 8;                             \
            v /= (1 << 8);                      \
        }                                       \
        if (v % (1 << 4) == 0) {                \
            r += 4;                             \
            v /= (1 << 4);                      \
        }                                       \
        if (v % (1 << 2) == 0) {                \
            r += 2;                             \
            v /= (1 << 2);                      \
        }                                       \
        if (v % 2 == 0) {                       \
            r++;                                \
        }
        COUNT_DIGIT_RIGHT_ZEROS();
        return r;
    }
#ifndef _MP_GMP
    mpz_cell * c        = a.m_ptr;
    unsigned sz         = c->m_size;
    unsigned r          = 0;
    digit_t * source    = c->m_digits; 
    for (unsigned i = 0; i < sz; i++) {
        if (source[i] != 0) {
            digit_t v = source[i];
            if (sizeof(digit_t) == 8) {
                // TODO: we can remove this if after we move to MPN
                // In MPN the digit_t is always an unsigned integer
                if (static_cast<uint64>(v) % (static_cast<uint64>(1) << 32) == 0) {
                    r += 32;                     
                    v = static_cast<digit_t>(static_cast<uint64>(v) / (static_cast<uint64>(1) << 32));
                }                                
            }
            COUNT_DIGIT_RIGHT_ZEROS();
            return r;
        }
        r += (8 * sizeof(digit_t));
    }
    return r;
#else
    return mpz_scan1(*a.m_ptr, 0);
#endif
}

template<bool SYNCH>
unsigned mpz_manager<SYNCH>::log2(mpz const & a) {
    if (is_nonpos(a))
        return 0;
    if (is_small(a))
        return ::log2(a.m_val);
#ifndef _MP_GMP
    COMPILE_TIME_ASSERT(sizeof(digit_t) == 8 || sizeof(digit_t) == 4);
    mpz_cell * c     = a.m_ptr;
    unsigned sz      = c->m_size;
    digit_t * ds     = c->m_digits; 
    if (sizeof(digit_t) == 8) 
        return (sz - 1)*64 + uint64_log2(ds[sz-1]);
    else
        return (sz - 1)*32 + ::log2(static_cast<unsigned>(ds[sz-1]));
#else
    unsigned r = mpz_sizeinbase(*a.m_ptr, 2);
    SASSERT(r > 0);
    return r - 1;
#endif
}

template<bool SYNCH>
unsigned mpz_manager<SYNCH>::mlog2(mpz const & a) {
    if (is_nonneg(a))
        return 0;
    if (is_small(a))
        return ::log2(-a.m_val);
#ifndef _MP_GMP
    COMPILE_TIME_ASSERT(sizeof(digit_t) == 8 || sizeof(digit_t) == 4);
    mpz_cell * c     = a.m_ptr;
    unsigned sz      = c->m_size;
    digit_t * ds     = c->m_digits; 
    if (sizeof(digit_t) == 8)
        return (sz - 1)*64 + uint64_log2(ds[sz-1]);
    else
        return (sz - 1)*32 + ::log2(static_cast<unsigned>(ds[sz-1]));
#else
    mpz_neg(m_tmp, *a.m_ptr);
    unsigned r = mpz_sizeinbase(m_tmp, 2);
    SASSERT(r > 0);
    return r - 1;
#endif
}

template<bool SYNCH>
unsigned mpz_manager<SYNCH>::bitsize(mpz const & a) {
    if (is_nonneg(a)) 
        return log2(a) + 1;
    else
        return mlog2(a) + 1;
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::is_perfect_square(mpz const & a, mpz & root) {
    if (is_neg(a))
        return false;
    reset(root);
    if (is_zero(a)) {
        return true;
    }
    if (is_one(a)) {
        set(root, 1);
        return true;
    }

    mpz lo, hi, mid, sq_lo, sq_hi, sq_mid;
    set(lo, 1);
    set(hi, a);
    set(sq_lo, 1);
    mul(hi, hi, sq_hi);
    bool result;
    // lo*lo <= *this < hi*hi
    while (true) {
        SASSERT(lt(lo, hi));
        SASSERT(le(sq_lo, a) && lt(a, sq_hi));
        if (eq(sq_lo, a)) {
            set(root, lo);
            result = true;
            break;
        }

        mpz & tmp = mid;
        add(lo, mpz(1), tmp);
        if (eq(tmp, hi)) {
            set(root, hi);
            result = false;
            break;
        }
        
        add(hi, lo, tmp);
        div(tmp, mpz(2), mid);
        
        SASSERT(lt(lo, mid) && lt(mid, hi));
        
        mul(mid, mid, sq_mid);

        if (gt(sq_mid, a)) {
            set(hi, mid);
            set(sq_hi, sq_mid);
        }
        else {
            set(lo, mid);
            set(sq_lo, sq_mid);
        }
    }
    del(lo);
    del(hi);
    del(mid);
    del(sq_lo);
    del(sq_hi);
    del(sq_mid);
    return result;
}


static unsigned div_l(unsigned k, unsigned n) {
    return k/n;
}

static unsigned div_u(unsigned k, unsigned n) {
    return k%n == 0 ? k/n : k/n + 1;
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::root(mpz & a, unsigned n) {
    SASSERT(n % 2 != 0 || is_nonneg(a));
    if (is_zero(a)) {
        return true; // precise
    }
    
    // Initial approximation
    //
    // We have that:
    // a >  0 ->    2^{log2(a)}     <= a <= 2^{(log2(a) + 1)}
    // a <  0 ->   -2^{log2(a) + 1} <= a <= -2^{log2(a)}
    //
    // Thus
    // a >  0 ->    2^{div_l(log2(a), n)}     <= a^{1/n} <=  2^{div_u(log2(a) + 1, n)}
    // a <  0 ->   -2^{div_u(log2(a) + 1, n)} <= a^{1/n} <= -2^{div_l(log2(a), n)}
    //
    mpz lower;
    mpz upper;
    mpz mid;
    mpz mid_n;
    
    if (is_pos(a)) {
        unsigned k = log2(a);
        power(mpz(2), div_l(k, n),     lower);
        power(mpz(2), div_u(k + 1, n), upper);
    }
    else {
        unsigned k = mlog2(a);
        power(mpz(2), div_u(k + 1, n), lower);
        power(mpz(2), div_l(k, n),     upper);
        neg(lower);
        neg(upper);
    }

    bool result;
    SASSERT(le(lower, upper));
    if (eq(lower, upper)) {
        swap(a, lower);
        result = true;
    }
    else {
        // Refine using bisection. TODO: use Newton's method if this is a bottleneck
        while (true) {
            add(upper, lower, mid);
            machine_div2k(mid, 1);
            TRACE("mpz", tout << "upper: "; display(tout, upper); tout << "\nlower: "; display(tout, lower); tout << "\nmid: "; display(tout, mid); tout << "\n";);
            power(mid, n, mid_n);
            if (eq(mid_n, a)) {
                swap(a, mid);
                result = true;
                break;
            }
            if (eq(mid, lower) || eq(mid, upper)) {
                swap(a, upper);
                result = false;
                break;
            }
            if (lt(mid_n, a)) {
                // new lower bound
                swap(mid, lower);
            }
            else {
                SASSERT(lt(a, mid_n));
                // new upper bound
                swap(mid, upper);
            }
        }
    }
    del(lower);
    del(upper);
    del(mid);
    del(mid_n);
    return result;
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::decompose(mpz const & a, svector<digit_t> & digits) {
    digits.reset();
    if (is_small(a)) {
        if (a.m_val < 0) {
            digits.push_back(-a.m_val);
            return true;
        }
        else {
            digits.push_back(a.m_val);
            return false;
        }
    }
    else {
#ifndef _MP_GMP
        mpz_cell * cell_a = a.m_ptr;
        unsigned sz = cell_a->m_size;
        for (unsigned i = 0; i < sz; i++) {
            digits.push_back(cell_a->m_digits[i]);
        }
        return a.m_val < 0;
#else
	bool r = is_neg(a);
	mpz_set(m_tmp, *a.m_ptr);
	mpz_abs(m_tmp, m_tmp);
	while (mpz_sgn(m_tmp) != 0) {
	  mpz_tdiv_r_2exp(m_tmp2, m_tmp, 32);
	  unsigned v = mpz_get_ui(m_tmp2);
	  digits.push_back(v);
	  mpz_tdiv_q_2exp(m_tmp, m_tmp, 32);
	}
	return r;
#endif
    }
}

template<bool SYNCH>
bool mpz_manager<SYNCH>::divides(mpz const & a, mpz const & b) {
    _scoped_numeral<mpz_manager<SYNCH> > tmp(*this);
    bool r;
    if (is_zero(a)) {
        // I assume 0 | 0. 
        // Remark a|b is a shorthand for (exists x. a x = b)
        // If b is zero, any x will do. If b != 0, then a does not divide b
        r = is_zero(b);
    }
    else {
        rem(b, a, tmp);
        r = is_zero(tmp);
    }
    STRACE("divides", tout << "[mpz] Divisible["; display(tout, b); tout << ", "; display(tout, a); tout << "] == " << (r?"True":"False") << "\n";);
    TRACE("divides_bug", tout << "tmp: "; display(tout, tmp); tout << "\n";);
    return r;
}

template class mpz_manager<true>;
template class mpz_manager<false>;
