/*
 * Copyright (c) 2008-2025 Stefan Krah. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "basearith.h"
#include "bits.h"
#include "constants.h"
#include "convolute.h"
#include "crt.h"
#include "mpalloc.h"
#include "mpdecimal.h"
#include "typearith.h"


#ifdef PPRO
  #if defined(_MSC_VER)
    #include <float.h>
    #pragma float_control(precise, on)
    #pragma fenv_access(on)
  #elif !defined(__OpenBSD__) && !defined(__NetBSD__)
    /* The C99 standard requires the pragma */
    #include <fenv.h>
    #pragma STDC FENV_ACCESS ON
  #endif
#endif


/* Disable warning that is part of -Wextra since gcc 7.0 */
#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && __GNUC__ >= 7
  #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#endif


#if defined(_MSC_VER)
  #define ALWAYS_INLINE __forceinline
#elif defined (__IBMC__) || defined(__COMPCERT__) || defined(LEGACY_COMPILER)
  #define ALWAYS_INLINE
  #undef inline
  #define inline
#else
  #ifdef TEST_COVERAGE
    #define ALWAYS_INLINE
  #else
    #define ALWAYS_INLINE inline
  #endif
#endif


#define MPD_NEWTONDIV_CUTOFF 1024L

#define MPD_NEW_STATIC(name, flags, exp, digits, len) \
        mpd_uint_t name##_data[MPD_MINALLOC_MAX];                    \
        mpd_t name = {flags|MPD_STATIC|MPD_STATIC_DATA, exp, digits, \
                      len, MPD_MINALLOC_MAX, name##_data}

#define MPD_NEW_CONST(name, flags, exp, digits, len, alloc, initval) \
        mpd_uint_t name##_data[alloc] = {initval};                   \
        mpd_t name = {flags|MPD_STATIC|MPD_CONST_DATA, exp, digits,  \
                      len, alloc, name##_data}

#define MPD_NEW_SHARED(name, a) \
        mpd_t name = {(a->flags&~MPD_DATAFLAGS)|MPD_STATIC|MPD_SHARED_DATA, \
                      a->exp, a->digits, a->len, a->alloc, a->data}


static mpd_uint_t data_one[1] = {1};
static mpd_uint_t data_zero[1] = {0};
static const mpd_t one = {MPD_STATIC|MPD_CONST_DATA, 0, 1, 1, 1, data_one};
static const mpd_t minus_one = {MPD_NEG|MPD_STATIC|MPD_CONST_DATA, 0, 1, 1, 1,
                                data_one};
static const mpd_t zero = {MPD_STATIC|MPD_CONST_DATA, 0, 1, 1, 1, data_zero};

static inline void _mpd_check_exp(mpd_t *dec, const mpd_context_t *ctx,
                                  uint32_t *status);
static void _settriple(mpd_t *result, uint8_t sign, mpd_uint_t a,
                       mpd_ssize_t exp);
static inline mpd_ssize_t _mpd_real_size(mpd_uint_t *data, mpd_ssize_t size);

static int _mpd_cmp_abs(const mpd_t *a, const mpd_t *b);

static void _mpd_qadd(mpd_t *result, const mpd_t *a, const mpd_t *b,
                      const mpd_context_t *ctx, uint32_t *status);
static inline void _mpd_qmul(mpd_t *result, const mpd_t *a, const mpd_t *b,
                             const mpd_context_t *ctx, uint32_t *status);
static void _mpd_base_ndivmod(mpd_t *q, mpd_t *r, const mpd_t *a,
                              const mpd_t *b, uint32_t *status);
static inline void _mpd_qpow_uint(mpd_t *result, const mpd_t *base,
                                  mpd_uint_t exp, uint8_t resultsign,
                                  const mpd_context_t *ctx, uint32_t *status);

static mpd_uint_t mpd_qsshiftr(mpd_t *result, const mpd_t *a, mpd_ssize_t n);


/******************************************************************************/
/*                                  Version                                   */
/******************************************************************************/

const char *
mpd_version(void)
{
    return MPD_VERSION;
}


/******************************************************************************/
/*                  Performance critical inline functions                     */
/******************************************************************************/

#ifdef CONFIG_64
/* Digits in a word, primarily useful for the most significant word. */
ALWAYS_INLINE int
mpd_word_digits(mpd_uint_t word)
{
    if (word < mpd_pow10[9]) {
        if (word < mpd_pow10[4]) {
            if (word < mpd_pow10[2]) {
                return (word < mpd_pow10[1]) ? 1 : 2;
            }
            return (word < mpd_pow10[3]) ? 3 : 4;
        }
        if (word < mpd_pow10[6]) {
            return (word < mpd_pow10[5]) ? 5 : 6;
        }
        if (word < mpd_pow10[8]) {
            return (word < mpd_pow10[7]) ? 7 : 8;
        }
        return 9;
    }
    if (word < mpd_pow10[14]) {
        if (word < mpd_pow10[11]) {
            return (word < mpd_pow10[10]) ? 10 : 11;
        }
        if (word < mpd_pow10[13]) {
            return (word < mpd_pow10[12]) ? 12 : 13;
        }
        return 14;
    }
    if (word < mpd_pow10[18]) {
        if (word < mpd_pow10[16]) {
            return (word < mpd_pow10[15]) ? 15 : 16;
        }
        return (word < mpd_pow10[17]) ? 17 : 18;
    }

    return (word < mpd_pow10[19]) ? 19 : 20;
}
#else
ALWAYS_INLINE int
mpd_word_digits(mpd_uint_t word)
{
    if (word < mpd_pow10[4]) {
        if (word < mpd_pow10[2]) {
            return (word < mpd_pow10[1]) ? 1 : 2;
        }
        return (word < mpd_pow10[3]) ? 3 : 4;
    }
    if (word < mpd_pow10[6]) {
        return (word < mpd_pow10[5]) ? 5 : 6;
    }
    if (word < mpd_pow10[8]) {
        return (word < mpd_pow10[7]) ? 7 : 8;
    }

    return (word < mpd_pow10[9]) ? 9 : 10;
}
#endif


/* Adjusted exponent */
ALWAYS_INLINE mpd_ssize_t
mpd_adjexp(const mpd_t *dec)
{
    return (dec->exp + dec->digits) - 1;
}

/* Etiny */
ALWAYS_INLINE mpd_ssize_t
mpd_etiny(const mpd_context_t *ctx)
{
    return ctx->emin - (ctx->prec - 1);
}

/* Etop: used for folding down in IEEE clamping */
ALWAYS_INLINE mpd_ssize_t
mpd_etop(const mpd_context_t *ctx)
{
    return ctx->emax - (ctx->prec - 1);
}

/* Most significant word */
ALWAYS_INLINE mpd_uint_t
mpd_msword(const mpd_t *dec)
{
    assert(dec->len > 0);
    return dec->data[dec->len-1];
}

/* Most significant digit of a word */
inline mpd_uint_t
mpd_msd(mpd_uint_t word)
{
    int n;

    n = mpd_word_digits(word);
    return word / mpd_pow10[n-1];
}

/* Least significant digit of a word */
ALWAYS_INLINE mpd_uint_t
mpd_lsd(mpd_uint_t word)
{
    return word % 10;
}

/* Coefficient size needed to store 'digits' */
mpd_ssize_t
mpd_digits_to_size(mpd_ssize_t digits)
{
    mpd_ssize_t q, r;

    _mpd_idiv_word(&q, &r, digits, MPD_RDIGITS);
    return (r == 0) ? q : q+1;
}

/* Number of digits in the exponent. Not defined for MPD_SSIZE_MIN. */
inline int
mpd_exp_digits(mpd_ssize_t exp)
{
    exp = (exp < 0) ? -exp : exp;
    return mpd_word_digits(exp);
}

/* Canonical */
ALWAYS_INLINE int
mpd_iscanonical(const mpd_t *dec)
{
    (void)dec;
    return 1;
}

/* Finite */
ALWAYS_INLINE int
mpd_isfinite(const mpd_t *dec)
{
    return !(dec->flags & MPD_SPECIAL);
}

/* Infinite */
ALWAYS_INLINE int
mpd_isinfinite(const mpd_t *dec)
{
    return dec->flags & MPD_INF;
}

/* NaN */
ALWAYS_INLINE int
mpd_isnan(const mpd_t *dec)
{
    return dec->flags & (MPD_NAN|MPD_SNAN);
}

/* Negative */
ALWAYS_INLINE int
mpd_isnegative(const mpd_t *dec)
{
    return dec->flags & MPD_NEG;
}

/* Positive */
ALWAYS_INLINE int
mpd_ispositive(const mpd_t *dec)
{
    return !(dec->flags & MPD_NEG);
}

/* qNaN */
ALWAYS_INLINE int
mpd_isqnan(const mpd_t *dec)
{
    return dec->flags & MPD_NAN;
}

/* Signed */
ALWAYS_INLINE int
mpd_issigned(const mpd_t *dec)
{
    return dec->flags & MPD_NEG;
}

/* sNaN */
ALWAYS_INLINE int
mpd_issnan(const mpd_t *dec)
{
    return dec->flags & MPD_SNAN;
}

/* Special */
ALWAYS_INLINE int
mpd_isspecial(const mpd_t *dec)
{
    return dec->flags & MPD_SPECIAL;
}

/* Zero */
ALWAYS_INLINE int
mpd_iszero(const mpd_t *dec)
{
    return !mpd_isspecial(dec) && mpd_msword(dec) == 0;
}

/* Test for zero when specials have been ruled out already */
ALWAYS_INLINE int
mpd_iszerocoeff(const mpd_t *dec)
{
    return mpd_msword(dec) == 0;
}

/* Normal */
inline int
mpd_isnormal(const mpd_t *dec, const mpd_context_t *ctx)
{
    if (mpd_isspecial(dec)) return 0;
    if (mpd_iszerocoeff(dec)) return 0;

    return mpd_adjexp(dec) >= ctx->emin;
}

/* Subnormal */
inline int
mpd_issubnormal(const mpd_t *dec, const mpd_context_t *ctx)
{
    if (mpd_isspecial(dec)) return 0;
    if (mpd_iszerocoeff(dec)) return 0;

    return mpd_adjexp(dec) < ctx->emin;
}

/* Odd word */
ALWAYS_INLINE int
mpd_isoddword(mpd_uint_t word)
{
    return word & 1;
}

/* Odd coefficient */
ALWAYS_INLINE int
mpd_isoddcoeff(const mpd_t *dec)
{
    return mpd_isoddword(dec->data[0]);
}

/* 0 if dec is positive, 1 if dec is negative */
ALWAYS_INLINE uint8_t
mpd_sign(const mpd_t *dec)
{
    return dec->flags & MPD_NEG;
}

/* 1 if dec is positive, -1 if dec is negative */
ALWAYS_INLINE int
mpd_arith_sign(const mpd_t *dec)
{
    return 1 - 2 * mpd_isnegative(dec);
}

/* Radix */
ALWAYS_INLINE long
mpd_radix(void)
{
    return 10;
}

/* Dynamic decimal */
ALWAYS_INLINE int
mpd_isdynamic(const mpd_t *dec)
{
    return !(dec->flags & MPD_STATIC);
}

/* Static decimal */
ALWAYS_INLINE int
mpd_isstatic(const mpd_t *dec)
{
    return dec->flags & MPD_STATIC;
}

/* Data of decimal is dynamic */
ALWAYS_INLINE int
mpd_isdynamic_data(const mpd_t *dec)
{
    return !(dec->flags & MPD_DATAFLAGS);
}

/* Data of decimal is static */
ALWAYS_INLINE int
mpd_isstatic_data(const mpd_t *dec)
{
    return dec->flags & MPD_STATIC_DATA;
}

/* Data of decimal is shared */
ALWAYS_INLINE int
mpd_isshared_data(const mpd_t *dec)
{
    return dec->flags & MPD_SHARED_DATA;
}

/* Data of decimal is const */
ALWAYS_INLINE int
mpd_isconst_data(const mpd_t *dec)
{
    return dec->flags & MPD_CONST_DATA;
}


/******************************************************************************/
/*                         Inline memory handling                             */
/******************************************************************************/

/* Fill destination with zeros */
ALWAYS_INLINE void
mpd_uint_zero(mpd_uint_t *dest, mpd_size_t len)
{
    mpd_size_t i;

    for (i = 0; i < len; i++) {
        dest[i] = 0;
    }
}

/* Free a decimal */
ALWAYS_INLINE void
mpd_del(mpd_t *dec)
{
    if (mpd_isdynamic_data(dec)) {
        mpd_free(dec->data);
    }
    if (mpd_isdynamic(dec)) {
        mpd_free(dec);
    }
}

/*
 * Resize the coefficient. Existing data up to 'nwords' is left untouched.
 * Return 1 on success, 0 otherwise.
 *
 * Input invariant: MPD_MINALLOC <= result->alloc.
 *
 * Case nwords == result->alloc:
 *     'result' is unchanged. Return 1.
 *
 * Case nwords > result->alloc:
 *   Case realloc success:
 *     The value of 'result' does not change. Return 1.
 *   Case realloc failure:
 *     'result' is NaN, status is updated with MPD_Malloc_error. Return 0.
 *
 * Case nwords < result->alloc:
 *   Case is_static_data or realloc failure [1]:
 *     'result' is unchanged. Return 1.
 *   Case realloc success:
 *     The value of result is undefined (expected). Return 1.
 *
 *
 * [1] In that case the old (now oversized) area is still valid.
 */
ALWAYS_INLINE int
mpd_qresize(mpd_t *result, mpd_ssize_t nwords, uint32_t *status)
{
    assert(!mpd_isconst_data(result)); /* illegal operation for a const */
    assert(!mpd_isshared_data(result)); /* illegal operation for a shared */
    assert(MPD_MINALLOC <= result->alloc);

    nwords = (nwords <= MPD_MINALLOC) ? MPD_MINALLOC : nwords;
    if (nwords == result->alloc) {
        return 1;
    }
    if (mpd_isstatic_data(result)) {
        if (nwords > result->alloc) {
            return mpd_switch_to_dyn(result, nwords, status);
        }
        return 1;
    }

    return mpd_realloc_dyn(result, nwords, status);
}

/* Same as mpd_qresize, but do not set the result to NaN on failure. */
static ALWAYS_INLINE int
mpd_qresize_cxx(mpd_t *result, mpd_ssize_t nwords)
{
    assert(!mpd_isconst_data(result)); /* illegal operation for a const */
    assert(!mpd_isshared_data(result)); /* illegal operation for a shared */
    assert(MPD_MINALLOC <= result->alloc);

    nwords = (nwords <= MPD_MINALLOC) ? MPD_MINALLOC : nwords;
    if (nwords == result->alloc) {
        return 1;
    }
    if (mpd_isstatic_data(result)) {
        if (nwords > result->alloc) {
            return mpd_switch_to_dyn_cxx(result, nwords);
        }
        return 1;
    }

    return mpd_realloc_dyn_cxx(result, nwords);
}

/* Same as mpd_qresize, but the complete coefficient (including the old
 * memory area!) is initialized to zero. */
ALWAYS_INLINE int
mpd_qresize_zero(mpd_t *result, mpd_ssize_t nwords, uint32_t *status)
{
    assert(!mpd_isconst_data(result)); /* illegal operation for a const */
    assert(!mpd_isshared_data(result)); /* illegal operation for a shared */
    assert(MPD_MINALLOC <= result->alloc);

    nwords = (nwords <= MPD_MINALLOC) ? MPD_MINALLOC : nwords;
    if (nwords != result->alloc) {
        if (mpd_isstatic_data(result)) {
            if (nwords > result->alloc) {
                return mpd_switch_to_dyn_zero(result, nwords, status);
            }
        }
        else if (!mpd_realloc_dyn(result, nwords, status)) {
            return 0;
        }
    }

    mpd_uint_zero(result->data, nwords);
    return 1;
}

/*
 * Reduce memory size for the coefficient to MPD_MINALLOC. In theory,
 * realloc may fail even when reducing the memory size. But in that case
 * the old memory area is always big enough, so checking for MPD_Malloc_error
 * is not imperative.
 */
ALWAYS_INLINE void
mpd_minalloc(mpd_t *result)
{
    assert(!mpd_isconst_data(result)); /* illegal operation for a const */
    assert(!mpd_isshared_data(result)); /* illegal operation for a shared */

    if (!mpd_isstatic_data(result) && result->alloc > MPD_MINALLOC) {
        uint8_t err = 0;
        result->data = mpd_realloc(result->data, MPD_MINALLOC,
                                   sizeof *result->data, &err);
        if (!err) {
            result->alloc = MPD_MINALLOC;
        }
    }
}

int
mpd_resize(mpd_t *result, mpd_ssize_t nwords, mpd_context_t *ctx)
{
    uint32_t status = 0;
    if (!mpd_qresize(result, nwords, &status)) {
        mpd_addstatus_raise(ctx, status);
        return 0;
    }
    return 1;
}

int
mpd_resize_zero(mpd_t *result, mpd_ssize_t nwords, mpd_context_t *ctx)
{
    uint32_t status = 0;
    if (!mpd_qresize_zero(result, nwords, &status)) {
        mpd_addstatus_raise(ctx, status);
        return 0;
    }
    return 1;
}


/******************************************************************************/
/*                       Set attributes of a decimal                          */
/******************************************************************************/

/* Set digits. Assumption: result->len is initialized and > 0. */
inline void
mpd_setdigits(mpd_t *result)
{
    mpd_ssize_t wdigits = mpd_word_digits(mpd_msword(result));
    result->digits = wdigits + (result->len-1) * MPD_RDIGITS;
}

/* Set sign */
ALWAYS_INLINE void
mpd_set_sign(mpd_t *result, uint8_t sign)
{
    result->flags &= ~MPD_NEG;
    result->flags |= sign;
}

/* Copy sign from another decimal */
ALWAYS_INLINE void
mpd_signcpy(mpd_t *result, const mpd_t *a)
{
    uint8_t sign = a->flags&MPD_NEG;

    result->flags &= ~MPD_NEG;
    result->flags |= sign;
}

/* Set infinity */
ALWAYS_INLINE void
mpd_set_infinity(mpd_t *result)
{
    result->flags &= ~MPD_SPECIAL;
    result->flags |= MPD_INF;
}

/* Set qNaN */
ALWAYS_INLINE void
mpd_set_qnan(mpd_t *result)
{
    result->flags &= ~MPD_SPECIAL;
    result->flags |= MPD_NAN;
}

/* Set sNaN */
ALWAYS_INLINE void
mpd_set_snan(mpd_t *result)
{
    result->flags &= ~MPD_SPECIAL;
    result->flags |= MPD_SNAN;
}

/* Set to negative */
ALWAYS_INLINE void
mpd_set_negative(mpd_t *result)
{
    result->flags |= MPD_NEG;
}

/* Set to positive */
ALWAYS_INLINE void
mpd_set_positive(mpd_t *result)
{
    result->flags &= ~MPD_NEG;
}

/* Set to dynamic */
ALWAYS_INLINE void
mpd_set_dynamic(mpd_t *result)
{
    result->flags &= ~MPD_STATIC;
}

/* Set to static */
ALWAYS_INLINE void
mpd_set_static(mpd_t *result)
{
    result->flags |= MPD_STATIC;
}

/* Set data to dynamic */
ALWAYS_INLINE void
mpd_set_dynamic_data(mpd_t *result)
{
    result->flags &= ~MPD_DATAFLAGS;
}

/* Set data to static */
ALWAYS_INLINE void
mpd_set_static_data(mpd_t *result)
{
    result->flags &= ~MPD_DATAFLAGS;
    result->flags |= MPD_STATIC_DATA;
}

/* Set data to shared */
ALWAYS_INLINE void
mpd_set_shared_data(mpd_t *result)
{
    result->flags &= ~MPD_DATAFLAGS;
    result->flags |= MPD_SHARED_DATA;
}

/* Set data to const */
ALWAYS_INLINE void
mpd_set_const_data(mpd_t *result)
{
    result->flags &= ~MPD_DATAFLAGS;
    result->flags |= MPD_CONST_DATA;
}

/* Clear flags, preserving memory attributes. */
ALWAYS_INLINE void
mpd_clear_flags(mpd_t *result)
{
    result->flags &= (MPD_STATIC|MPD_DATAFLAGS);
}

/* Set flags, preserving memory attributes. */
ALWAYS_INLINE void
mpd_set_flags(mpd_t *result, uint8_t flags)
{
    result->flags &= (MPD_STATIC|MPD_DATAFLAGS);
    result->flags |= flags;
}

/* Copy flags, preserving memory attributes of result. */
ALWAYS_INLINE void
mpd_copy_flags(mpd_t *result, const mpd_t *a)
{
    uint8_t aflags = a->flags;
    result->flags &= (MPD_STATIC|MPD_DATAFLAGS);
    result->flags |= (aflags & ~(MPD_STATIC|MPD_DATAFLAGS));
}

/* Initialize a workcontext from ctx. Set traps, flags and newtrap to 0. */
static inline void
mpd_workcontext(mpd_context_t *workctx, const mpd_context_t *ctx)
{
    workctx->prec = ctx->prec;
    workctx->emax = ctx->emax;
    workctx->emin = ctx->emin;
    workctx->round = ctx->round;
    workctx->traps = 0;
    workctx->status = 0;
    workctx->newtrap = 0;
    workctx->clamp = ctx->clamp;
    workctx->allcr = ctx->allcr;
}


/******************************************************************************/
/*                  Getting and setting parts of decimals                     */
/******************************************************************************/

/* Flip the sign of a decimal */
static inline void
_mpd_negate(mpd_t *dec)
{
    dec->flags ^= MPD_NEG;
}

/* Set coefficient to zero */
void
mpd_zerocoeff(mpd_t *result)
{
    mpd_minalloc(result);
    result->digits = 1;
    result->len = 1;
    result->data[0] = 0;
}

/* Set the coefficient to all nines. */
void
mpd_qmaxcoeff(mpd_t *result, const mpd_context_t *ctx, uint32_t *status)
{
    mpd_ssize_t len, r;

    _mpd_idiv_word(&len, &r, ctx->prec, MPD_RDIGITS);
    len = (r == 0) ? len : len+1;

    if (!mpd_qresize(result, len, status)) {
        return;
    }

    result->len = len;
    result->digits = ctx->prec;

    --len;
    if (r > 0) {
        result->data[len--] = mpd_pow10[r]-1;
    }
    for (; len >= 0; --len) {
        result->data[len] = MPD_RADIX-1;
    }
}

/*
 * Cut off the most significant digits so that the rest fits in ctx->prec.
 * Cannot fail.
 */
static void
_mpd_cap(mpd_t *result, const mpd_context_t *ctx)
{
    uint32_t dummy;
    mpd_ssize_t len, r;

    if (result->len > 0 && result->digits > ctx->prec) {
        _mpd_idiv_word(&len, &r, ctx->prec, MPD_RDIGITS);
        len = (r == 0) ? len : len+1;

        if (r != 0) {
            result->data[len-1] %= mpd_pow10[r];
        }

        len = _mpd_real_size(result->data, len);
        /* resize to fewer words cannot fail */
        mpd_qresize(result, len, &dummy);
        result->len = len;
        mpd_setdigits(result);
    }
    if (mpd_iszero(result)) {
        _settriple(result, mpd_sign(result), 0, result->exp);
    }
}

/*
 * Cut off the most significant digits of a NaN payload so that the rest
 * fits in ctx->prec - ctx->clamp. Cannot fail.
 */
static void
_mpd_fix_nan(mpd_t *result, const mpd_context_t *ctx)
{
    uint32_t dummy;
    mpd_ssize_t prec;
    mpd_ssize_t len, r;

    prec = ctx->prec - ctx->clamp;
    if (result->len > 0 && result->digits > prec) {
        if (prec == 0) {
            mpd_minalloc(result);
            result->len = result->digits = 0;
        }
        else {
            _mpd_idiv_word(&len, &r, prec, MPD_RDIGITS);
            len = (r == 0) ? len : len+1;

            if (r != 0) {
                 result->data[len-1] %= mpd_pow10[r];
            }

            len = _mpd_real_size(result->data, len);
            /* resize to fewer words cannot fail */
            mpd_qresize(result, len, &dummy);
            result->len = len;
            mpd_setdigits(result);
            if (mpd_iszerocoeff(result)) {
                /* NaN0 is not a valid representation */
                result->len = result->digits = 0;
            }
        }
    }
}

/*
 * Get n most significant digits from a decimal, where 0 < n <= MPD_UINT_DIGITS.
 * Assumes MPD_UINT_DIGITS == MPD_RDIGITS+1, which is true for 32 and 64 bit
 * machines.
 *
 * The result of the operation will be in lo. If the operation is impossible,
 * hi will be nonzero. This is used to indicate an error.
 */
static inline void
_mpd_get_msdigits(mpd_uint_t *hi, mpd_uint_t *lo, const mpd_t *dec,
                  unsigned int n)
{
    mpd_uint_t r, tmp;

    assert(0 < n && n <= MPD_RDIGITS+1);

    _mpd_div_word(&tmp, &r, dec->digits, MPD_RDIGITS);
    r = (r == 0) ? MPD_RDIGITS : r; /* digits in the most significant word */

    *hi = 0;
    *lo = dec->data[dec->len-1];
    if (n <= r) {
        *lo /= mpd_pow10[r-n];
    }
    else if (dec->len > 1) {
        /* at this point 1 <= r < n <= MPD_RDIGITS+1 */
        _mpd_mul_words(hi, lo, *lo, mpd_pow10[n-r]);
        tmp = dec->data[dec->len-2] / mpd_pow10[MPD_RDIGITS-(n-r)];
        *lo = *lo + tmp;
        if (*lo < tmp) (*hi)++;
    }
}


/******************************************************************************/
/*                   Gathering information about a decimal                    */
/******************************************************************************/

/* The real size of the coefficient without leading zero words. */
static inline mpd_ssize_t
_mpd_real_size(mpd_uint_t *data, mpd_ssize_t size)
{
    while (size > 1 && data[size-1] == 0) {
        size--;
    }

    return size;
}

/* Return number of trailing zeros. No errors are possible. */
mpd_ssize_t
mpd_trail_zeros(const mpd_t *dec)
{
    mpd_uint_t word;
    mpd_ssize_t i, tz = 0;

    for (i=0; i < dec->len; ++i) {
        if (dec->data[i] != 0) {
            word = dec->data[i];
            tz = i * MPD_RDIGITS;
            while (word % 10 == 0) {
                word /= 10;
                tz++;
            }
            break;
        }
    }

    return tz;
}

/* Integer: Undefined for specials */
static int
_mpd_isint(const mpd_t *dec)
{
    mpd_ssize_t tz;

    if (mpd_iszerocoeff(dec)) {
        return 1;
    }

    tz = mpd_trail_zeros(dec);
    return (dec->exp + tz >= 0);
}

/* Integer */
int
mpd_isinteger(const mpd_t *dec)
{
    if (mpd_isspecial(dec)) {
        return 0;
    }
    return _mpd_isint(dec);
}

/* Word is a power of 10 */
static int
mpd_word_ispow10(mpd_uint_t word)
{
    int n;

    n = mpd_word_digits(word);
    if (word == mpd_pow10[n-1]) {
        return 1;
    }

    return 0;
}

/* Coefficient is a power of 10 */
static int
mpd_coeff_ispow10(const mpd_t *dec)
{
    if (mpd_word_ispow10(mpd_msword(dec))) {
        if (_mpd_isallzero(dec->data, dec->len-1)) {
            return 1;
        }
    }

    return 0;
}

/* All digits of a word are nines */
static int
mpd_word_isallnine(mpd_uint_t word)
{
    int n;

    n = mpd_word_digits(word);
    if (word == mpd_pow10[n]-1) {
        return 1;
    }

    return 0;
}

/* All digits of the coefficient are nines */
static int
mpd_coeff_isallnine(const mpd_t *dec)
{
    if (mpd_word_isallnine(mpd_msword(dec))) {
        if (_mpd_isallnine(dec->data, dec->len-1)) {
            return 1;
        }
    }

    return 0;
}

/* Odd decimal: Undefined for non-integers! */
int
mpd_isodd(const mpd_t *dec)
{
    mpd_uint_t q, r;
    assert(mpd_isinteger(dec));
    if (mpd_iszerocoeff(dec)) return 0;
    if (dec->exp < 0) {
        _mpd_div_word(&q, &r, -dec->exp, MPD_RDIGITS);
        q = dec->data[q] / mpd_pow10[r];
        return mpd_isoddword(q);
    }
    return dec->exp == 0 && mpd_isoddword(dec->data[0]);
}

/* Even: Undefined for non-integers! */
int
mpd_iseven(const mpd_t *dec)
{
    return !mpd_isodd(dec);
}

/******************************************************************************/
/*                      Getting and setting decimals                          */
/******************************************************************************/

/* Internal function: Set a static decimal from a triple, no error checking. */
static void
_ssettriple(mpd_t *result, uint8_t sign, mpd_uint_t a, mpd_ssize_t exp)
{
    mpd_set_flags(result, sign);
    result->exp = exp;
    _mpd_div_word(&result->data[1], &result->data[0], a, MPD_RADIX);
    result->len = (result->data[1] == 0) ? 1 : 2;
    mpd_setdigits(result);
}

/* Internal function: Set a decimal from a triple, no error checking. */
static void
_settriple(mpd_t *result, uint8_t sign, mpd_uint_t a, mpd_ssize_t exp)
{
    mpd_minalloc(result);
    mpd_set_flags(result, sign);
    result->exp = exp;
    _mpd_div_word(&result->data[1], &result->data[0], a, MPD_RADIX);
    result->len = (result->data[1] == 0) ? 1 : 2;
    mpd_setdigits(result);
}

/* Set a special number from a triple */
void
mpd_setspecial(mpd_t *result, uint8_t sign, uint8_t type)
{
    mpd_minalloc(result);
    result->flags &= ~(MPD_NEG|MPD_SPECIAL);
    result->flags |= (sign|type);
    result->exp = result->digits = result->len = 0;
}

/* Set result of NaN with an error status */
void
mpd_seterror(mpd_t *result, uint32_t flags, uint32_t *status)
{
    mpd_minalloc(result);
    mpd_set_qnan(result);
    mpd_set_positive(result);
    result->exp = result->digits = result->len = 0;
    *status |= flags;
}

/* quietly set a static decimal from an mpd_ssize_t */
void
mpd_qsset_ssize(mpd_t *result, mpd_ssize_t a, const mpd_context_t *ctx,
                uint32_t *status)
{
    mpd_uint_t u;
    uint8_t sign = MPD_POS;

    if (a < 0) {
        if (a == MPD_SSIZE_MIN) {
            u = (mpd_uint_t)MPD_SSIZE_MAX +
                (-(MPD_SSIZE_MIN+MPD_SSIZE_MAX));
        }
        else {
            u = -a;
        }
        sign = MPD_NEG;
    }
    else {
        u = a;
    }
    _ssettriple(result, sign, u, 0);
    mpd_qfinalize(result, ctx, status);
}

/* quietly set a static decimal from an mpd_uint_t */
void
mpd_qsset_uint(mpd_t *result, mpd_uint_t a, const mpd_context_t *ctx,
               uint32_t *status)
{
    _ssettriple(result, MPD_POS, a, 0);
    mpd_qfinalize(result, ctx, status);
}

/* quietly set a static decimal from an int32_t */
void
mpd_qsset_i32(mpd_t *result, int32_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    mpd_qsset_ssize(result, a, ctx, status);
}

/* quietly set a static decimal from a uint32_t */
void
mpd_qsset_u32(mpd_t *result, uint32_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    mpd_qsset_uint(result, a, ctx, status);
}

#ifdef CONFIG_64
/* quietly set a static decimal from an int64_t */
void
mpd_qsset_i64(mpd_t *result, int64_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    mpd_qsset_ssize(result, a, ctx, status);
}

/* quietly set a static decimal from a uint64_t */
void
mpd_qsset_u64(mpd_t *result, uint64_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    mpd_qsset_uint(result, a, ctx, status);
}
#endif

/* quietly set a decimal from an mpd_ssize_t */
void
mpd_qset_ssize(mpd_t *result, mpd_ssize_t a, const mpd_context_t *ctx,
               uint32_t *status)
{
    mpd_minalloc(result);
    mpd_qsset_ssize(result, a, ctx, status);
}

/* quietly set a decimal from an mpd_uint_t */
void
mpd_qset_uint(mpd_t *result, mpd_uint_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    _settriple(result, MPD_POS, a, 0);
    mpd_qfinalize(result, ctx, status);
}

/* quietly set a decimal from an int32_t */
void
mpd_qset_i32(mpd_t *result, int32_t a, const mpd_context_t *ctx,
             uint32_t *status)
{
    mpd_qset_ssize(result, a, ctx, status);
}

/* quietly set a decimal from a uint32_t */
void
mpd_qset_u32(mpd_t *result, uint32_t a, const mpd_context_t *ctx,
             uint32_t *status)
{
    mpd_qset_uint(result, a, ctx, status);
}

#if defined(CONFIG_32) && !defined(LEGACY_COMPILER)
/* set a decimal from a uint64_t */
static void
_c32setu64(mpd_t *result, uint64_t u, uint8_t sign, uint32_t *status)
{
    mpd_uint_t w[3];
    uint64_t q;
    int i, len;

    len = 0;
    do {
        q = u / MPD_RADIX;
        w[len] = (mpd_uint_t)(u - q * MPD_RADIX);
        u = q; len++;
    } while (u != 0);

    if (!mpd_qresize(result, len, status)) {
        return;
    }
    for (i = 0; i < len; i++) {
        result->data[i] = w[i];
    }

    mpd_set_flags(result, sign);
    result->exp = 0;
    result->len = len;
    mpd_setdigits(result);
}

static void
_c32_qset_u64(mpd_t *result, uint64_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    _c32setu64(result, a, MPD_POS, status);
    mpd_qfinalize(result, ctx, status);
}

/* set a decimal from an int64_t */
static void
_c32_qset_i64(mpd_t *result, int64_t a, const mpd_context_t *ctx,
              uint32_t *status)
{
    uint64_t u;
    uint8_t sign = MPD_POS;

    if (a < 0) {
        if (a == INT64_MIN) {
            u = (uint64_t)INT64_MAX + (-(INT64_MIN+INT64_MAX));
        }
        else {
            u = -a;
        }
        sign = MPD_NEG;
    }
    else {
        u = a;
    }
    _c32setu64(result, u, sign, status);
    mpd_qfinalize(result, ctx, status);
}
#endif /* CONFIG_32 && !LEGACY_COMPILER */

#ifndef LEGACY_COMPILER
/* quietly set a decimal from an int64_t */
void
mpd_qset_i64(mpd_t *result, int64_t a, const mpd_context_t *ctx,
             uint32_t *status)
{
#ifdef CONFIG_64
    mpd_qset_ssize(result, a, ctx, status);
#else
    _c32_qset_i64(result, a, ctx, status);
#endif
}

/* quietly set a decimal from an int64_t, use a maxcontext for conversion */
void
mpd_qset_i64_exact(mpd_t *result, int64_t a, uint32_t *status)
{
    mpd_context_t maxcontext;
    uint32_t workstatus = 0;

    mpd_maxcontext(&maxcontext);
#ifdef CONFIG_64
    mpd_qset_ssize(result, a, &maxcontext, &workstatus);
#else
    _c32_qset_i64(result, a, &maxcontext, &workstatus);
#endif

    if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
        /* we want exact results */
        mpd_seterror(result, MPD_Invalid_operation, status); /* GCOV_NOT_REACHED */
    }
    *status |= (workstatus&MPD_Errors);
}

/* quietly set a decimal from a uint64_t */
void
mpd_qset_u64(mpd_t *result, uint64_t a, const mpd_context_t *ctx,
             uint32_t *status)
{
#ifdef CONFIG_64
    mpd_qset_uint(result, a, ctx, status);
#else
    _c32_qset_u64(result, a, ctx, status);
#endif
}

/* quietly set a decimal from a uint64_t, use a maxcontext for conversion */
void
mpd_qset_u64_exact(mpd_t *result, uint64_t a, uint32_t *status)
{
    mpd_context_t maxcontext;
    uint32_t workstatus = 0;

    mpd_maxcontext(&maxcontext);
#ifdef CONFIG_64
    mpd_qset_uint(result, a, &maxcontext, &workstatus);
#else
    _c32_qset_u64(result, a, &maxcontext, &workstatus);
#endif

    if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
        /* we want exact results */
        mpd_seterror(result, MPD_Invalid_operation, status); /* GCOV_NOT_REACHED */
    }
    *status |= (workstatus&MPD_Errors);
}
#endif /* !LEGACY_COMPILER */

/*
 * Quietly get an mpd_uint_t from a decimal. Assumes
 * MPD_UINT_DIGITS == MPD_RDIGITS+1, which is true for
 * 32 and 64 bit machines.
 *
 * If the operation is impossible, MPD_Invalid_operation is set.
 */
static mpd_uint_t
_mpd_qget_uint(int use_sign, const mpd_t *a, uint32_t *status)
{
    mpd_t tmp;
    mpd_uint_t tmp_data[2];
    mpd_uint_t lo, hi;

    if (mpd_isspecial(a)) {
        *status |= MPD_Invalid_operation;
        return MPD_UINT_MAX;
    }
    if (mpd_iszero(a)) {
        return 0;
    }
    if (use_sign && mpd_isnegative(a)) {
        *status |= MPD_Invalid_operation;
        return MPD_UINT_MAX;
    }

    if (a->digits+a->exp > MPD_RDIGITS+1) {
        *status |= MPD_Invalid_operation;
        return MPD_UINT_MAX;
    }

    if (a->exp < 0) {
        if (!_mpd_isint(a)) {
            *status |= MPD_Invalid_operation;
            return MPD_UINT_MAX;
        }
        /* At this point a->digits+a->exp <= MPD_RDIGITS+1,
         * so the shift fits. */
        tmp.data = tmp_data;
        tmp.flags = MPD_STATIC|MPD_STATIC_DATA;
        tmp.alloc = 2;
        mpd_qsshiftr(&tmp, a, -a->exp);
        tmp.exp = 0;
        a = &tmp;
    }

    _mpd_get_msdigits(&hi, &lo, a, MPD_RDIGITS+1);
    if (hi) {
        *status |= MPD_Invalid_operation;
        return MPD_UINT_MAX;
    }

    if (a->exp > 0) {
        _mpd_mul_words(&hi, &lo, lo, mpd_pow10[a->exp]);
        if (hi) {
            *status |= MPD_Invalid_operation;
            return MPD_UINT_MAX;
        }
    }

    return lo;
}

/*
 * Sets Invalid_operation for:
 *   - specials
 *   - negative numbers (except negative zero)
 *   - non-integers
 *   - overflow
 */
mpd_uint_t
mpd_qget_uint(const mpd_t *a, uint32_t *status)
{
    return _mpd_qget_uint(1, a, status);
}

/* Same as above, but gets the absolute value, i.e. the sign is ignored. */
mpd_uint_t
mpd_qabs_uint(const mpd_t *a, uint32_t *status)
{
    return _mpd_qget_uint(0, a, status);
}

/* quietly get an mpd_ssize_t from a decimal */
mpd_ssize_t
mpd_qget_ssize(const mpd_t *a, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_uint_t u;
    int isneg;

    u = mpd_qabs_uint(a, &workstatus);
    if (workstatus&MPD_Invalid_operation) {
        *status |= workstatus;
        return MPD_SSIZE_MAX;
    }

    isneg = mpd_isnegative(a);
    if (u <= MPD_SSIZE_MAX) {
        return isneg ? -((mpd_ssize_t)u) : (mpd_ssize_t)u;
    }
    else if (isneg && u+(mpd_uint_t)(MPD_SSIZE_MIN+MPD_SSIZE_MAX) == MPD_SSIZE_MAX) {
        return MPD_SSIZE_MIN;
    }

    *status |= MPD_Invalid_operation;
    return MPD_SSIZE_MAX;
}

#if defined(CONFIG_32) && !defined(LEGACY_COMPILER)
/*
 * Quietly get a uint64_t from a decimal. If the operation is impossible,
 * MPD_Invalid_operation is set.
 */
static uint64_t
_c32_qget_u64(int use_sign, const mpd_t *a, uint32_t *status)
{
    MPD_NEW_STATIC(tmp,0,0,20,3);
    mpd_context_t maxcontext;
    uint64_t ret;

    tmp_data[0] = 709551615;
    tmp_data[1] = 446744073;
    tmp_data[2] = 18;

    if (mpd_isspecial(a)) {
        *status |= MPD_Invalid_operation;
        return UINT64_MAX;
    }
    if (mpd_iszero(a)) {
        return 0;
    }
    if (use_sign && mpd_isnegative(a)) {
        *status |= MPD_Invalid_operation;
        return UINT64_MAX;
    }
    if (!_mpd_isint(a)) {
        *status |= MPD_Invalid_operation;
        return UINT64_MAX;
    }

    if (_mpd_cmp_abs(a, &tmp) > 0) {
        *status |= MPD_Invalid_operation;
        return UINT64_MAX;
    }

    mpd_maxcontext(&maxcontext);
    mpd_qrescale(&tmp, a, 0, &maxcontext, &maxcontext.status);
    maxcontext.status &= ~MPD_Rounded;
    if (maxcontext.status != 0) {
        *status |= (maxcontext.status|MPD_Invalid_operation); /* GCOV_NOT_REACHED */
        return UINT64_MAX; /* GCOV_NOT_REACHED */
    }

    ret = 0;
    switch (tmp.len) {
    case 3:
        ret += (uint64_t)tmp_data[2] * 1000000000000000000ULL;
    case 2:
        ret += (uint64_t)tmp_data[1] * 1000000000ULL;
    case 1:
        ret += tmp_data[0];
        break;
    default:
        abort(); /* GCOV_NOT_REACHED */
    }

    return ret;
}

static int64_t
_c32_qget_i64(const mpd_t *a, uint32_t *status)
{
    uint64_t u;
    int isneg;

    u = _c32_qget_u64(0, a, status);
    if (*status&MPD_Invalid_operation) {
        return INT64_MAX;
    }

    isneg = mpd_isnegative(a);
    if (u <= INT64_MAX) {
        return isneg ? -((int64_t)u) : (int64_t)u;
    }
    else if (isneg && u+(uint64_t)(INT64_MIN+INT64_MAX) == INT64_MAX) {
        return INT64_MIN;
    }

    *status |= MPD_Invalid_operation;
    return INT64_MAX;
}
#endif /* CONFIG_32 && !LEGACY_COMPILER */

#ifdef CONFIG_64
/* quietly get a uint64_t from a decimal */
uint64_t
mpd_qget_u64(const mpd_t *a, uint32_t *status)
{
    return mpd_qget_uint(a, status);
}

/* quietly get an int64_t from a decimal */
int64_t
mpd_qget_i64(const mpd_t *a, uint32_t *status)
{
    return mpd_qget_ssize(a, status);
}

/* quietly get a uint32_t from a decimal */
uint32_t
mpd_qget_u32(const mpd_t *a, uint32_t *status)
{
    uint32_t workstatus = 0;
    uint64_t x = mpd_qget_uint(a, &workstatus);

    if (workstatus&MPD_Invalid_operation) {
        *status |= workstatus;
        return UINT32_MAX;
    }
    if (x > UINT32_MAX) {
        *status |= MPD_Invalid_operation;
        return UINT32_MAX;
    }

    return (uint32_t)x;
}

/* quietly get an int32_t from a decimal */
int32_t
mpd_qget_i32(const mpd_t *a, uint32_t *status)
{
    uint32_t workstatus = 0;
    int64_t x = mpd_qget_ssize(a, &workstatus);

    if (workstatus&MPD_Invalid_operation) {
        *status |= workstatus;
        return INT32_MAX;
    }
    if (x < INT32_MIN || x > INT32_MAX) {
        *status |= MPD_Invalid_operation;
        return INT32_MAX;
    }

    return (int32_t)x;
}
#else
#ifndef LEGACY_COMPILER
/* quietly get a uint64_t from a decimal */
uint64_t
mpd_qget_u64(const mpd_t *a, uint32_t *status)
{
    uint32_t workstatus = 0;
    uint64_t x = _c32_qget_u64(1, a, &workstatus);
    *status |= workstatus;
    return x;
}

/* quietly get an int64_t from a decimal */
int64_t
mpd_qget_i64(const mpd_t *a, uint32_t *status)
{
    uint32_t workstatus = 0;
    int64_t x = _c32_qget_i64(a, &workstatus);
    *status |= workstatus;
    return x;
}
#endif

/* quietly get a uint32_t from a decimal */
uint32_t
mpd_qget_u32(const mpd_t *a, uint32_t *status)
{
    return mpd_qget_uint(a, status);
}

/* quietly get an int32_t from a decimal */
int32_t
mpd_qget_i32(const mpd_t *a, uint32_t *status)
{
    return mpd_qget_ssize(a, status);
}
#endif


/******************************************************************************/
/*         Filtering input of functions, finalizing output of functions       */
/******************************************************************************/

/*
 * Check if the operand is NaN, copy to result and return 1 if this is
 * the case. Copying can fail since NaNs are allowed to have a payload that
 * does not fit in MPD_MINALLOC.
 */
int
mpd_qcheck_nan(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
               uint32_t *status)
{
    if (mpd_isnan(a)) {
        *status |= mpd_issnan(a) ? MPD_Invalid_operation : 0;
        mpd_qcopy(result, a, status);
        mpd_set_qnan(result);
        _mpd_fix_nan(result, ctx);
        return 1;
    }
    return 0;
}

/*
 * Check if either operand is NaN, copy to result and return 1 if this
 * is the case. Copying can fail since NaNs are allowed to have a payload
 * that does not fit in MPD_MINALLOC.
 */
int
mpd_qcheck_nans(mpd_t *result, const mpd_t *a, const mpd_t *b,
                const mpd_context_t *ctx, uint32_t *status)
{
    if ((a->flags|b->flags)&(MPD_NAN|MPD_SNAN)) {
        const mpd_t *choice = b;
        if (mpd_issnan(a)) {
            choice = a;
            *status |= MPD_Invalid_operation;
        }
        else if (mpd_issnan(b)) {
            *status |= MPD_Invalid_operation;
        }
        else if (mpd_isqnan(a)) {
            choice = a;
        }
        mpd_qcopy(result, choice, status);
        mpd_set_qnan(result);
        _mpd_fix_nan(result, ctx);
        return 1;
    }
    return 0;
}

/*
 * Check if one of the operands is NaN, copy to result and return 1 if this
 * is the case. Copying can fail since NaNs are allowed to have a payload
 * that does not fit in MPD_MINALLOC.
 */
static int
mpd_qcheck_3nans(mpd_t *result, const mpd_t *a, const mpd_t *b, const mpd_t *c,
                 const mpd_context_t *ctx, uint32_t *status)
{
    if ((a->flags|b->flags|c->flags)&(MPD_NAN|MPD_SNAN)) {
        const mpd_t *choice = c;
        if (mpd_issnan(a)) {
            choice = a;
            *status |= MPD_Invalid_operation;
        }
        else if (mpd_issnan(b)) {
            choice = b;
            *status |= MPD_Invalid_operation;
        }
        else if (mpd_issnan(c)) {
            *status |= MPD_Invalid_operation;
        }
        else if (mpd_isqnan(a)) {
            choice = a;
        }
        else if (mpd_isqnan(b)) {
            choice = b;
        }
        mpd_qcopy(result, choice, status);
        mpd_set_qnan(result);
        _mpd_fix_nan(result, ctx);
        return 1;
    }
    return 0;
}

/* Check if rounding digit 'rnd' leads to an increment. */
static inline int
_mpd_rnd_incr(const mpd_t *dec, mpd_uint_t rnd, const mpd_context_t *ctx)
{
    int ld;

    switch (ctx->round) {
    case MPD_ROUND_DOWN: case MPD_ROUND_TRUNC:
        return 0;
    case MPD_ROUND_HALF_UP:
        return (rnd >= 5);
    case MPD_ROUND_HALF_EVEN:
        return (rnd > 5) || ((rnd == 5) && mpd_isoddcoeff(dec));
    case MPD_ROUND_CEILING:
        return !(rnd == 0 || mpd_isnegative(dec));
    case MPD_ROUND_FLOOR:
        return !(rnd == 0 || mpd_ispositive(dec));
    case MPD_ROUND_HALF_DOWN:
        return (rnd > 5);
    case MPD_ROUND_UP:
        return !(rnd == 0);
    case MPD_ROUND_05UP:
        ld = (int)mpd_lsd(dec->data[0]);
        return (!(rnd == 0) && (ld == 0 || ld == 5));
    default:
        /* Without a valid context, further results will be undefined. */
        return 0; /* GCOV_NOT_REACHED */
    }
}

/*
 * Apply rounding to a decimal that has been right-shifted into a full
 * precision decimal. If an increment leads to an overflow of the precision,
 * adjust the coefficient and the exponent and check the new exponent for
 * overflow.
 */
static inline void
_mpd_apply_round(mpd_t *dec, mpd_uint_t rnd, const mpd_context_t *ctx,
                 uint32_t *status)
{
    if (_mpd_rnd_incr(dec, rnd, ctx)) {
        /* We have a number with exactly ctx->prec digits. The increment
         * can only lead to an overflow if the decimal is all nines. In
         * that case, the result is a power of ten with prec+1 digits.
         *
         * If the precision is a multiple of MPD_RDIGITS, this situation is
         * detected by _mpd_baseincr returning a carry.
         * If the precision is not a multiple of MPD_RDIGITS, we have to
         * check if the result has one digit too many.
         */
        mpd_uint_t carry = _mpd_baseincr(dec->data, dec->len);
        if (carry) {
            dec->data[dec->len-1] = mpd_pow10[MPD_RDIGITS-1];
            dec->exp += 1;
            _mpd_check_exp(dec, ctx, status);
            return;
        }
        mpd_setdigits(dec);
        if (dec->digits > ctx->prec) {
            mpd_qshiftr_inplace(dec, 1);
            dec->exp += 1;
            dec->digits = ctx->prec;
            _mpd_check_exp(dec, ctx, status);
        }
    }
}

/*
 * Apply rounding to a decimal. Allow overflow of the precision.
 */
static inline void
_mpd_apply_round_excess(mpd_t *dec, mpd_uint_t rnd, const mpd_context_t *ctx,
                        uint32_t *status)
{
    if (_mpd_rnd_incr(dec, rnd, ctx)) {
        mpd_uint_t carry = _mpd_baseincr(dec->data, dec->len);
        if (carry) {
            if (!mpd_qresize(dec, dec->len+1, status)) {
                return;
            }
            dec->data[dec->len] = 1;
            dec->len += 1;
        }
        mpd_setdigits(dec);
    }
}

/*
 * Apply rounding to a decimal that has been right-shifted into a decimal
 * with full precision or less. Return failure if an increment would
 * overflow the precision.
 */
static inline int
_mpd_apply_round_fit(mpd_t *dec, mpd_uint_t rnd, const mpd_context_t *ctx,
                     uint32_t *status)
{
    if (_mpd_rnd_incr(dec, rnd, ctx)) {
        mpd_uint_t carry = _mpd_baseincr(dec->data, dec->len);
        if (carry) {
            if (!mpd_qresize(dec, dec->len+1, status)) {
                return 0;
            }
            dec->data[dec->len] = 1;
            dec->len += 1;
        }
        mpd_setdigits(dec);
        if (dec->digits > ctx->prec) {
            mpd_seterror(dec, MPD_Invalid_operation, status);
            return 0;
        }
    }
    return 1;
}

/* Check a normal number for overflow, underflow, clamping. If the operand
   is modified, it will be zero, special or (sub)normal with a coefficient
   that fits into the current context precision. */
static inline void
_mpd_check_exp(mpd_t *dec, const mpd_context_t *ctx, uint32_t *status)
{
    mpd_ssize_t adjexp, etiny, shift;
    int rnd;

    adjexp = mpd_adjexp(dec);
    if (adjexp > ctx->emax) {

        if (mpd_iszerocoeff(dec)) {
            dec->exp = ctx->emax;
            if (ctx->clamp) {
                dec->exp -= (ctx->prec-1);
            }
            mpd_zerocoeff(dec);
            *status |= MPD_Clamped;
            return;
        }

        switch (ctx->round) {
        case MPD_ROUND_HALF_UP: case MPD_ROUND_HALF_EVEN:
        case MPD_ROUND_HALF_DOWN: case MPD_ROUND_UP:
        case MPD_ROUND_TRUNC:
            mpd_setspecial(dec, mpd_sign(dec), MPD_INF);
            break;
        case MPD_ROUND_DOWN: case MPD_ROUND_05UP:
            mpd_qmaxcoeff(dec, ctx, status);
            dec->exp = ctx->emax - ctx->prec + 1;
            break;
        case MPD_ROUND_CEILING:
            if (mpd_isnegative(dec)) {
                mpd_qmaxcoeff(dec, ctx, status);
                dec->exp = ctx->emax - ctx->prec + 1;
            }
            else {
                mpd_setspecial(dec, MPD_POS, MPD_INF);
            }
            break;
        case MPD_ROUND_FLOOR:
            if (mpd_ispositive(dec)) {
                mpd_qmaxcoeff(dec, ctx, status);
                dec->exp = ctx->emax - ctx->prec + 1;
            }
            else {
                mpd_setspecial(dec, MPD_NEG, MPD_INF);
            }
            break;
        default: /* debug */
            abort(); /* GCOV_NOT_REACHED */
        }

        *status |= MPD_Overflow|MPD_Inexact|MPD_Rounded;

    } /* fold down */
    else if (ctx->clamp && dec->exp > mpd_etop(ctx)) {
        /* At this point adjexp=exp+digits-1 <= emax and exp > etop=emax-prec+1:
         *   (1) shift = exp -emax+prec-1 > 0
         *   (2) digits+shift = exp+digits-1 - emax + prec <= prec */
        shift = dec->exp - mpd_etop(ctx);
        if (!mpd_qshiftl(dec, dec, shift, status)) {
            return;
        }
        dec->exp -= shift;
        *status |= MPD_Clamped;
        if (!mpd_iszerocoeff(dec) && adjexp < ctx->emin) {
            /* Underflow is impossible, since exp < etiny=emin-prec+1
             * and exp > etop=emax-prec+1 would imply emax < emin. */
            *status |= MPD_Subnormal;
        }
    }
    else if (adjexp < ctx->emin) {

        etiny = mpd_etiny(ctx);

        if (mpd_iszerocoeff(dec)) {
            if (dec->exp < etiny) {
                dec->exp = etiny;
                mpd_zerocoeff(dec);
                *status |= MPD_Clamped;
            }
            return;
        }

        *status |= MPD_Subnormal;
        if (dec->exp < etiny) {
            /* At this point adjexp=exp+digits-1 < emin and exp < etiny=emin-prec+1:
             *   (1) shift = emin-prec+1 - exp > 0
             *   (2) digits-shift = exp+digits-1 - emin + prec < prec */
            shift = etiny - dec->exp;
            rnd = (int)mpd_qshiftr_inplace(dec, shift);
            dec->exp = etiny;
            /* We always have a spare digit in case of an increment. */
            _mpd_apply_round_excess(dec, rnd, ctx, status);
            *status |= MPD_Rounded;
            if (rnd) {
                *status |= (MPD_Inexact|MPD_Underflow);
                if (mpd_iszerocoeff(dec)) {
                    mpd_zerocoeff(dec);
                    *status |= MPD_Clamped;
                }
            }
        }
        /* Case exp >= etiny=emin-prec+1:
         *   (1) adjexp=exp+digits-1 < emin
         *   (2) digits < emin-exp+1 <= prec */
    }
}

/* Transcendental functions do not always set Underflow reliably,
 * since they only use as much precision as is necessary for correct
 * rounding. If a result like 1.0000000000e-101 is finalized, there
 * is no rounding digit that would trigger Underflow. But we can
 * assume Inexact, so a short check suffices. */
static inline void
mpd_check_underflow(mpd_t *dec, const mpd_context_t *ctx, uint32_t *status)
{
    if (mpd_adjexp(dec) < ctx->emin && !mpd_iszero(dec) &&
        dec->exp < mpd_etiny(ctx)) {
        *status |= MPD_Underflow;
    }
}

/* Check if a normal number must be rounded after the exponent has been checked. */
static inline void
_mpd_check_round(mpd_t *dec, const mpd_context_t *ctx, uint32_t *status)
{
    mpd_uint_t rnd;
    mpd_ssize_t shift;

    /* must handle specials: _mpd_check_exp() can produce infinities or NaNs */
    if (mpd_isspecial(dec)) {
        return;
    }

    if (dec->digits > ctx->prec) {
        shift = dec->digits - ctx->prec;
        rnd = mpd_qshiftr_inplace(dec, shift);
        dec->exp += shift;
        _mpd_apply_round(dec, rnd, ctx, status);
        *status |= MPD_Rounded;
        if (rnd) {
            *status |= MPD_Inexact;
        }
    }
}

/* Finalize all operations. */
void
mpd_qfinalize(mpd_t *result, const mpd_context_t *ctx, uint32_t *status)
{
    if (mpd_isspecial(result)) {
        if (mpd_isnan(result)) {
            _mpd_fix_nan(result, ctx);
        }
        return;
    }

    _mpd_check_exp(result, ctx, status);
    _mpd_check_round(result, ctx, status);
}


/******************************************************************************/
/*                                 Copying                                    */
/******************************************************************************/

/* Internal function: Copy a decimal, share data with src: USE WITH CARE! */
static inline void
_mpd_copy_shared(mpd_t *dest, const mpd_t *src)
{
    dest->flags = src->flags;
    dest->exp = src->exp;
    dest->digits = src->digits;
    dest->len = src->len;
    dest->alloc = src->alloc;
    dest->data = src->data;

    mpd_set_shared_data(dest);
}

/*
 * Copy a decimal. In case of an error, status is set to MPD_Malloc_error.
 */
int
mpd_qcopy(mpd_t *result, const mpd_t *a, uint32_t *status)
{
    if (result == a) return 1;

    if (!mpd_qresize(result, a->len, status)) {
        return 0;
    }

    mpd_copy_flags(result, a);
    result->exp = a->exp;
    result->digits = a->digits;
    result->len = a->len;
    memcpy(result->data, a->data, a->len * (sizeof *result->data));

    return 1;
}

/* Same as mpd_qcopy, but do not set the result to NaN on failure. */
int
mpd_qcopy_cxx(mpd_t *result, const mpd_t *a)
{
    if (result == a) return 1;

    if (!mpd_qresize_cxx(result, a->len)) {
        return 0;
    }

    mpd_copy_flags(result, a);
    result->exp = a->exp;
    result->digits = a->digits;
    result->len = a->len;
    memcpy(result->data, a->data, a->len * (sizeof *result->data));

    return 1;
}

/*
 * Copy to a decimal with a static buffer. The caller has to make sure that
 * the buffer is big enough. Cannot fail.
 */
static void
mpd_qcopy_static(mpd_t *result, const mpd_t *a)
{
    if (result == a) return;

    memcpy(result->data, a->data, a->len * (sizeof *result->data));

    mpd_copy_flags(result, a);
    result->exp = a->exp;
    result->digits = a->digits;
    result->len = a->len;
}

/*
 * Return a newly allocated copy of the operand. In case of an error,
 * status is set to MPD_Malloc_error and the return value is NULL.
 */
mpd_t *
mpd_qncopy(const mpd_t *a)
{
    mpd_t *result;

    if ((result = mpd_qnew_size(a->len)) == NULL) {
        return NULL;
    }
    memcpy(result->data, a->data, a->len * (sizeof *result->data));
    mpd_copy_flags(result, a);
    result->exp = a->exp;
    result->digits = a->digits;
    result->len = a->len;

    return result;
}

/*
 * Copy a decimal and set the sign to positive. In case of an error, the
 * status is set to MPD_Malloc_error.
 */
int
mpd_qcopy_abs(mpd_t *result, const mpd_t *a, uint32_t *status)
{
    if (!mpd_qcopy(result, a, status)) {
        return 0;
    }
    mpd_set_positive(result);
    return 1;
}

/*
 * Copy a decimal and negate the sign. In case of an error, the
 * status is set to MPD_Malloc_error.
 */
int
mpd_qcopy_negate(mpd_t *result, const mpd_t *a, uint32_t *status)
{
    if (!mpd_qcopy(result, a, status)) {
        return 0;
    }
    _mpd_negate(result);
    return 1;
}

/*
 * Copy a decimal, setting the sign of the first operand to the sign of the
 * second operand. In case of an error, the status is set to MPD_Malloc_error.
 */
int
mpd_qcopy_sign(mpd_t *result, const mpd_t *a, const mpd_t *b, uint32_t *status)
{
    uint8_t sign_b = mpd_sign(b); /* result may equal b! */

    if (!mpd_qcopy(result, a, status)) {
        return 0;
    }
    mpd_set_sign(result, sign_b);
    return 1;
}


/******************************************************************************/
/*                                Comparisons                                 */
/******************************************************************************/

/*
 * For all functions that compare two operands and return an int the usual
 * convention applies to the return value:
 *
 * -1 if op1 < op2
 *  0 if op1 == op2
 *  1 if op1 > op2
 *
 *  INT_MAX for error
 */


/* Convenience macro. If a and b are not equal, return from the calling
 * function with the correct comparison value. */
#define CMP_EQUAL_OR_RETURN(a, b)  \
        if (a != b) {              \
                if (a < b) {       \
                        return -1; \
                }                  \
                return 1;          \
        }

/*
 * Compare the data of big and small. This function does the equivalent
 * of first shifting small to the left and then comparing the data of
 * big and small, except that no allocation for the left shift is needed.
 */
static int
_mpd_basecmp(mpd_uint_t *big, mpd_uint_t *small, mpd_size_t n, mpd_size_t m,
             mpd_size_t shift)
{
#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__)
    /* spurious uninitialized warnings */
    mpd_uint_t l=l, lprev=lprev, h=h;
#else
    mpd_uint_t l, lprev, h;
#endif
    mpd_uint_t q, r;
    mpd_uint_t ph, x;

    assert(m > 0 && n >= m && shift > 0);

    _mpd_div_word(&q, &r, (mpd_uint_t)shift, MPD_RDIGITS);

    if (r != 0) {

        ph = mpd_pow10[r];

        --m; --n;
        _mpd_divmod_pow10(&h, &lprev, small[m--], MPD_RDIGITS-r);
        if (h != 0) {
            CMP_EQUAL_OR_RETURN(big[n], h)
            --n;
        }
        for (; m != MPD_SIZE_MAX; m--,n--) {
            _mpd_divmod_pow10(&h, &l, small[m], MPD_RDIGITS-r);
            x = ph * lprev + h;
            CMP_EQUAL_OR_RETURN(big[n], x)
            lprev = l;
        }
        x = ph * lprev;
        CMP_EQUAL_OR_RETURN(big[q], x)
    }
    else {
        while (--m != MPD_SIZE_MAX) {
            CMP_EQUAL_OR_RETURN(big[m+q], small[m])
        }
    }

    return !_mpd_isallzero(big, q);
}

/* Compare two decimals with the same adjusted exponent. */
static int
_mpd_cmp_same_adjexp(const mpd_t *a, const mpd_t *b)
{
    mpd_ssize_t shift, i;

    if (a->exp != b->exp) {
        /* Cannot wrap: a->exp + a->digits = b->exp + b->digits, so
         * a->exp - b->exp = b->digits - a->digits. */
        shift = a->exp - b->exp;
        if (shift > 0) {
            return -1 * _mpd_basecmp(b->data, a->data, b->len, a->len, shift);
        }
        else {
            return _mpd_basecmp(a->data, b->data, a->len, b->len, -shift);
        }
    }

    /*
     * At this point adjexp(a) == adjexp(b) and a->exp == b->exp,
     * so a->digits == b->digits, therefore a->len == b->len.
     */
    for (i = a->len-1; i >= 0; --i) {
        CMP_EQUAL_OR_RETURN(a->data[i], b->data[i])
    }

    return 0;
}

/* Compare two numerical values. */
static int
_mpd_cmp(const mpd_t *a, const mpd_t *b)
{
    mpd_ssize_t adjexp_a, adjexp_b;

    /* equal pointers */
    if (a == b) {
        return 0;
    }

    /* infinities */
    if (mpd_isinfinite(a)) {
        if (mpd_isinfinite(b)) {
            return mpd_isnegative(b) - mpd_isnegative(a);
        }
        return mpd_arith_sign(a);
    }
    if (mpd_isinfinite(b)) {
        return -mpd_arith_sign(b);
    }

    /* zeros */
    if (mpd_iszerocoeff(a)) {
        if (mpd_iszerocoeff(b)) {
            return 0;
        }
        return -mpd_arith_sign(b);
    }
    if (mpd_iszerocoeff(b)) {
        return mpd_arith_sign(a);
    }

    /* different signs */
    if (mpd_sign(a) != mpd_sign(b)) {
        return mpd_sign(b) - mpd_sign(a);
    }

    /* different adjusted exponents */
    adjexp_a = mpd_adjexp(a);
    adjexp_b = mpd_adjexp(b);
    if (adjexp_a != adjexp_b) {
        if (adjexp_a < adjexp_b) {
            return -1 * mpd_arith_sign(a);
        }
        return mpd_arith_sign(a);
    }

    /* same adjusted exponents */
    return _mpd_cmp_same_adjexp(a, b) * mpd_arith_sign(a);
}

/* Compare the absolutes of two numerical values. */
static int
_mpd_cmp_abs(const mpd_t *a, const mpd_t *b)
{
    mpd_ssize_t adjexp_a, adjexp_b;

    /* equal pointers */
    if (a == b) {
        return 0;
    }

    /* infinities */
    if (mpd_isinfinite(a)) {
        if (mpd_isinfinite(b)) {
            return 0;
        }
        return 1;
    }
    if (mpd_isinfinite(b)) {
        return -1;
    }

    /* zeros */
    if (mpd_iszerocoeff(a)) {
        if (mpd_iszerocoeff(b)) {
            return 0;
        }
        return -1;
    }
    if (mpd_iszerocoeff(b)) {
        return 1;
    }

    /* different adjusted exponents */
    adjexp_a = mpd_adjexp(a);
    adjexp_b = mpd_adjexp(b);
    if (adjexp_a != adjexp_b) {
        if (adjexp_a < adjexp_b) {
            return -1;
        }
        return 1;
    }

    /* same adjusted exponents */
    return _mpd_cmp_same_adjexp(a, b);
}

/* Compare two values and return an integer result. */
int
mpd_qcmp(const mpd_t *a, const mpd_t *b, uint32_t *status)
{
    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_isnan(a) || mpd_isnan(b)) {
            *status |= MPD_Invalid_operation;
            return INT_MAX;
        }
    }

    return _mpd_cmp(a, b);
}

/*
 * Compare a and b, convert the usual integer result to a decimal and
 * store it in 'result'. For convenience, the integer result of the comparison
 * is returned. Comparisons involving NaNs return NaN/INT_MAX.
 */
int
mpd_qcompare(mpd_t *result, const mpd_t *a, const mpd_t *b,
             const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return INT_MAX;
        }
    }

    c = _mpd_cmp(a, b);
    _settriple(result, (c < 0), (c != 0), 0);
    return c;
}

/* Same as mpd_compare(), but signal for all NaNs, i.e. also for quiet NaNs. */
int
mpd_qcompare_signal(mpd_t *result, const mpd_t *a, const mpd_t *b,
                    const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            *status |= MPD_Invalid_operation;
            return INT_MAX;
        }
    }

    c = _mpd_cmp(a, b);
    _settriple(result, (c < 0), (c != 0), 0);
    return c;
}

/* Compare the operands using a total order. */
int
mpd_cmp_total(const mpd_t *a, const mpd_t *b)
{
    mpd_t aa, bb;
    int nan_a, nan_b;
    int c;

    if (mpd_sign(a) != mpd_sign(b)) {
        return mpd_sign(b) - mpd_sign(a);
    }


    if (mpd_isnan(a)) {
        c = 1;
        if (mpd_isnan(b)) {
            nan_a = (mpd_isqnan(a)) ? 1 : 0;
            nan_b = (mpd_isqnan(b)) ? 1 : 0;
            if (nan_b == nan_a) {
                if (a->len > 0 && b->len > 0) {
                    _mpd_copy_shared(&aa, a);
                    _mpd_copy_shared(&bb, b);
                    aa.exp = bb.exp = 0;
                    /* compare payload */
                    c = _mpd_cmp_abs(&aa, &bb);
                }
                else {
                    c = (a->len > 0) - (b->len > 0);
                }
            }
            else {
                c = nan_a - nan_b;
            }
        }
    }
    else if (mpd_isnan(b)) {
        c = -1;
    }
    else {
        c = _mpd_cmp_abs(a, b);
        if (c == 0 && a->exp != b->exp) {
            c = (a->exp < b->exp) ? -1 : 1;
        }
    }

    return c * mpd_arith_sign(a);
}

/*
 * Compare a and b according to a total order, convert the usual integer result
 * to a decimal and store it in 'result'. For convenience, the integer result
 * of the comparison is returned.
 */
int
mpd_compare_total(mpd_t *result, const mpd_t *a, const mpd_t *b)
{
    int c;

    c = mpd_cmp_total(a, b);
    _settriple(result, (c < 0), (c != 0), 0);
    return c;
}

/* Compare the magnitude of the operands using a total order. */
int
mpd_cmp_total_mag(const mpd_t *a, const mpd_t *b)
{
    mpd_t aa, bb;

    _mpd_copy_shared(&aa, a);
    _mpd_copy_shared(&bb, b);

    mpd_set_positive(&aa);
    mpd_set_positive(&bb);

    return mpd_cmp_total(&aa, &bb);
}

/*
 * Compare the magnitude of a and b according to a total order, convert the
 * the usual integer result to a decimal and store it in 'result'.
 * For convenience, the integer result of the comparison is returned.
 */
int
mpd_compare_total_mag(mpd_t *result, const mpd_t *a, const mpd_t *b)
{
    int c;

    c = mpd_cmp_total_mag(a, b);
    _settriple(result, (c < 0), (c != 0), 0);
    return c;
}

/* Determine an ordering for operands that are numerically equal. */
static inline int
_mpd_cmp_numequal(const mpd_t *a, const mpd_t *b)
{
    int sign_a, sign_b;
    int c;

    sign_a = mpd_sign(a);
    sign_b = mpd_sign(b);
    if (sign_a != sign_b) {
        c = sign_b - sign_a;
    }
    else {
        c = (a->exp < b->exp) ? -1 : 1;
        c *= mpd_arith_sign(a);
    }

    return c;
}


/******************************************************************************/
/*                         Shifting the coefficient                           */
/******************************************************************************/

/*
 * Shift the coefficient of the operand to the left, no check for specials.
 * Both operands may be the same pointer. If the result length has to be
 * increased, mpd_qresize() might fail with MPD_Malloc_error.
 */
int
mpd_qshiftl(mpd_t *result, const mpd_t *a, mpd_ssize_t n, uint32_t *status)
{
    mpd_ssize_t size;

    assert(!mpd_isspecial(a));
    assert(n >= 0);

    if (mpd_iszerocoeff(a) || n == 0) {
        return mpd_qcopy(result, a, status);
    }

    size = mpd_digits_to_size(a->digits+n);
    if (!mpd_qresize(result, size, status)) {
        return 0; /* result is NaN */
    }

    _mpd_baseshiftl(result->data, a->data, size, a->len, n);

    mpd_copy_flags(result, a);
    result->exp = a->exp;
    result->digits = a->digits+n;
    result->len = size;

    return 1;
}

/* Determine the rounding indicator if all digits of the coefficient are shifted
 * out of the picture. */
static mpd_uint_t
_mpd_get_rnd(const mpd_uint_t *data, mpd_ssize_t len, int use_msd)
{
    mpd_uint_t rnd = 0, rest = 0, word;

    word = data[len-1];
    /* special treatment for the most significant digit if shift == digits */
    if (use_msd) {
        _mpd_divmod_pow10(&rnd, &rest, word, mpd_word_digits(word)-1);
        if (len > 1 && rest == 0) {
             rest = !_mpd_isallzero(data, len-1);
        }
    }
    else {
        rest = !_mpd_isallzero(data, len);
    }

    return (rnd == 0 || rnd == 5) ? rnd + !!rest : rnd;
}

/*
 * Same as mpd_qshiftr(), but 'result' is an mpd_t with a static coefficient.
 * It is the caller's responsibility to ensure that the coefficient is big
 * enough. The function cannot fail.
 */
static mpd_uint_t
mpd_qsshiftr(mpd_t *result, const mpd_t *a, mpd_ssize_t n)
{
    mpd_uint_t rnd;
    mpd_ssize_t size;

    assert(!mpd_isspecial(a));
    assert(n >= 0);

    if (mpd_iszerocoeff(a) || n == 0) {
        mpd_qcopy_static(result, a);
        return 0;
    }

    if (n >= a->digits) {
        rnd = _mpd_get_rnd(a->data, a->len, (n==a->digits));
        mpd_zerocoeff(result);
    }
    else {
        result->digits = a->digits-n;
        size = mpd_digits_to_size(result->digits);
        rnd = _mpd_baseshiftr(result->data, a->data, a->len, n);
        result->len = size;
    }

    mpd_copy_flags(result, a);
    result->exp = a->exp;

    return rnd;
}

/*
 * Inplace shift of the coefficient to the right, no check for specials.
 * Returns the rounding indicator for mpd_rnd_incr().
 * The function cannot fail.
 */
mpd_uint_t
mpd_qshiftr_inplace(mpd_t *result, mpd_ssize_t n)
{
    uint32_t dummy;
    mpd_uint_t rnd;
    mpd_ssize_t size;

    assert(!mpd_isspecial(result));
    assert(n >= 0);

    if (mpd_iszerocoeff(result) || n == 0) {
        return 0;
    }

    if (n >= result->digits) {
        rnd = _mpd_get_rnd(result->data, result->len, (n==result->digits));
        mpd_zerocoeff(result);
    }
    else {
        rnd = _mpd_baseshiftr(result->data, result->data, result->len, n);
        result->digits -= n;
        size = mpd_digits_to_size(result->digits);
        /* reducing the size cannot fail */
        mpd_qresize(result, size, &dummy);
        result->len = size;
    }

    return rnd;
}

/*
 * Shift the coefficient of the operand to the right, no check for specials.
 * Both operands may be the same pointer. Returns the rounding indicator to
 * be used by mpd_rnd_incr(). If the result length has to be increased,
 * mpd_qcopy() or mpd_qresize() might fail with MPD_Malloc_error. In those
 * cases, MPD_UINT_MAX is returned.
 */
mpd_uint_t
mpd_qshiftr(mpd_t *result, const mpd_t *a, mpd_ssize_t n, uint32_t *status)
{
    mpd_uint_t rnd;
    mpd_ssize_t size;

    assert(!mpd_isspecial(a));
    assert(n >= 0);

    if (mpd_iszerocoeff(a) || n == 0) {
        if (!mpd_qcopy(result, a, status)) {
            return MPD_UINT_MAX;
        }
        return 0;
    }

    if (n >= a->digits) {
        rnd = _mpd_get_rnd(a->data, a->len, (n==a->digits));
        mpd_zerocoeff(result);
    }
    else {
        result->digits = a->digits-n;
        size = mpd_digits_to_size(result->digits);
        if (result == a) {
            rnd = _mpd_baseshiftr(result->data, a->data, a->len, n);
            /* reducing the size cannot fail */
            mpd_qresize(result, size, status);
        }
        else {
            if (!mpd_qresize(result, size, status)) {
                return MPD_UINT_MAX;
            }
            rnd = _mpd_baseshiftr(result->data, a->data, a->len, n);
        }
        result->len = size;
    }

    mpd_copy_flags(result, a);
    result->exp = a->exp;

    return rnd;
}


/******************************************************************************/
/*                         Miscellaneous operations                           */
/******************************************************************************/

/* Logical And */
void
mpd_qand(mpd_t *result, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    const mpd_t *big = a, *small = b;
    mpd_uint_t x, y, z, xbit, ybit;
    int k, mswdigits;
    mpd_ssize_t i;

    if (mpd_isspecial(a) || mpd_isspecial(b) ||
        mpd_isnegative(a) || mpd_isnegative(b) ||
        a->exp != 0 || b->exp != 0) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (b->digits > a->digits) {
        big = b;
        small = a;
    }
    if (!mpd_qresize(result, big->len, status)) {
        return;
    }


    /* full words */
    for (i = 0; i < small->len-1; i++) {
        x = small->data[i];
        y = big->data[i];
        z = 0;
        for (k = 0; k < MPD_RDIGITS; k++) {
            xbit = x % 10;
            x /= 10;
            ybit = y % 10;
            y /= 10;
            if (xbit > 1 || ybit > 1) {
                goto invalid_operation;
            }
            z += (xbit&ybit) ? mpd_pow10[k] : 0;
        }
        result->data[i] = z;
    }
    /* most significant word of small */
    x = small->data[i];
    y = big->data[i];
    z = 0;
    mswdigits = mpd_word_digits(x);
    for (k = 0; k < mswdigits; k++) {
        xbit = x % 10;
        x /= 10;
        ybit = y % 10;
        y /= 10;
        if (xbit > 1 || ybit > 1) {
            goto invalid_operation;
        }
        z += (xbit&ybit) ? mpd_pow10[k] : 0;
    }
    result->data[i++] = z;

    /* scan the rest of y for digits > 1 */
    for (; k < MPD_RDIGITS; k++) {
        ybit = y % 10;
        y /= 10;
        if (ybit > 1) {
            goto invalid_operation;
        }
    }
    /* scan the rest of big for digits > 1 */
    for (; i < big->len; i++) {
        y = big->data[i];
        for (k = 0; k < MPD_RDIGITS; k++) {
            ybit = y % 10;
            y /= 10;
            if (ybit > 1) {
                goto invalid_operation;
            }
        }
    }

    mpd_clear_flags(result);
    result->exp = 0;
    result->len = _mpd_real_size(result->data, small->len);
    mpd_qresize(result, result->len, status);
    mpd_setdigits(result);
    _mpd_cap(result, ctx);
    return;

invalid_operation:
    mpd_seterror(result, MPD_Invalid_operation, status);
}

/* Class of an operand. Returns a pointer to the constant name. */
const char *
mpd_class(const mpd_t *a, const mpd_context_t *ctx)
{
    if (mpd_isnan(a)) {
        if (mpd_isqnan(a))
            return "NaN";
        else
            return "sNaN";
    }
    else if (mpd_ispositive(a)) {
        if (mpd_isinfinite(a))
            return "+Infinity";
        else if (mpd_iszero(a))
            return "+Zero";
        else if (mpd_isnormal(a, ctx))
            return "+Normal";
        else
            return "+Subnormal";
    }
    else {
        if (mpd_isinfinite(a))
            return "-Infinity";
        else if (mpd_iszero(a))
            return "-Zero";
        else if (mpd_isnormal(a, ctx))
            return "-Normal";
        else
            return "-Subnormal";
    }
}

/* Logical Xor */
void
mpd_qinvert(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
            uint32_t *status)
{
    mpd_uint_t x, z, xbit;
    mpd_ssize_t i, digits, len;
    mpd_ssize_t q, r;
    int k;

    if (mpd_isspecial(a) || mpd_isnegative(a) || a->exp != 0) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    digits = (a->digits < ctx->prec) ? ctx->prec : a->digits;
    _mpd_idiv_word(&q, &r, digits, MPD_RDIGITS);
    len = (r == 0) ? q : q+1;
    if (!mpd_qresize(result, len, status)) {
        return;
    }

    for (i = 0; i < len; i++) {
        x = (i < a->len) ? a->data[i] : 0;
        z = 0;
        for (k = 0; k < MPD_RDIGITS; k++) {
            xbit = x % 10;
            x /= 10;
            if (xbit > 1) {
                goto invalid_operation;
            }
            z += !xbit ? mpd_pow10[k] : 0;
        }
        result->data[i] = z;
    }

    mpd_clear_flags(result);
    result->exp = 0;
    result->len = _mpd_real_size(result->data, len);
    mpd_qresize(result, result->len, status);
    mpd_setdigits(result);
    _mpd_cap(result, ctx);
    return;

invalid_operation:
    mpd_seterror(result, MPD_Invalid_operation, status);
}

/* Exponent of the magnitude of the most significant digit of the operand. */
void
mpd_qlogb(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
          uint32_t *status)
{
    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        mpd_setspecial(result, MPD_POS, MPD_INF);
    }
    else if (mpd_iszerocoeff(a)) {
        mpd_setspecial(result, MPD_NEG, MPD_INF);
        *status |= MPD_Division_by_zero;
    }
    else {
        mpd_qset_ssize(result, mpd_adjexp(a), ctx, status);
    }
}

/* Logical Or */
void
mpd_qor(mpd_t *result, const mpd_t *a, const mpd_t *b,
        const mpd_context_t *ctx, uint32_t *status)
{
    const mpd_t *big = a, *small = b;
    mpd_uint_t x, y, z, xbit, ybit;
    int k, mswdigits;
    mpd_ssize_t i;

    if (mpd_isspecial(a) || mpd_isspecial(b) ||
        mpd_isnegative(a) || mpd_isnegative(b) ||
        a->exp != 0 || b->exp != 0) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (b->digits > a->digits) {
        big = b;
        small = a;
    }
    if (!mpd_qresize(result, big->len, status)) {
        return;
    }


    /* full words */
    for (i = 0; i < small->len-1; i++) {
        x = small->data[i];
        y = big->data[i];
        z = 0;
        for (k = 0; k < MPD_RDIGITS; k++) {
            xbit = x % 10;
            x /= 10;
            ybit = y % 10;
            y /= 10;
            if (xbit > 1 || ybit > 1) {
                goto invalid_operation;
            }
            z += (xbit|ybit) ? mpd_pow10[k] : 0;
        }
        result->data[i] = z;
    }
    /* most significant word of small */
    x = small->data[i];
    y = big->data[i];
    z = 0;
    mswdigits = mpd_word_digits(x);
    for (k = 0; k < mswdigits; k++) {
        xbit = x % 10;
        x /= 10;
        ybit = y % 10;
        y /= 10;
        if (xbit > 1 || ybit > 1) {
            goto invalid_operation;
        }
        z += (xbit|ybit) ? mpd_pow10[k] : 0;
    }

    /* scan for digits > 1 and copy the rest of y */
    for (; k < MPD_RDIGITS; k++) {
        ybit = y % 10;
        y /= 10;
        if (ybit > 1) {
            goto invalid_operation;
        }
        z += ybit*mpd_pow10[k];
    }
    result->data[i++] = z;
    /* scan for digits > 1 and copy the rest of big */
    for (; i < big->len; i++) {
        y = big->data[i];
        for (k = 0; k < MPD_RDIGITS; k++) {
            ybit = y % 10;
            y /= 10;
            if (ybit > 1) {
                goto invalid_operation;
            }
        }
        result->data[i] = big->data[i];
    }

    mpd_clear_flags(result);
    result->exp = 0;
    result->len = _mpd_real_size(result->data, big->len);
    mpd_qresize(result, result->len, status);
    mpd_setdigits(result);
    _mpd_cap(result, ctx);
    return;

invalid_operation:
    mpd_seterror(result, MPD_Invalid_operation, status);
}

/*
 * Rotate the coefficient of 'a' by 'b' digits. 'b' must be an integer with
 * exponent 0.
 */
void
mpd_qrotate(mpd_t *result, const mpd_t *a, const mpd_t *b,
            const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    MPD_NEW_STATIC(tmp,0,0,0,0);
    MPD_NEW_STATIC(big,0,0,0,0);
    MPD_NEW_STATIC(small,0,0,0,0);
    mpd_ssize_t n, lshift, rshift;

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
    }
    if (b->exp != 0 || mpd_isinfinite(b)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    n = mpd_qget_ssize(b, &workstatus);
    if (workstatus&MPD_Invalid_operation) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (n > ctx->prec || n < -ctx->prec) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mpd_isinfinite(a)) {
        mpd_qcopy(result, a, status);
        return;
    }

    if (n >= 0) {
        lshift = n;
        rshift = ctx->prec-n;
    }
    else {
        lshift = ctx->prec+n;
        rshift = -n;
    }

    if (a->digits > ctx->prec) {
        if (!mpd_qcopy(&tmp, a, status)) {
            mpd_seterror(result, MPD_Malloc_error, status);
            goto finish;
        }
        _mpd_cap(&tmp, ctx);
        a = &tmp;
    }

    if (!mpd_qshiftl(&big, a, lshift, status)) {
        mpd_seterror(result, MPD_Malloc_error, status);
        goto finish;
    }
    _mpd_cap(&big, ctx);

    if (mpd_qshiftr(&small, a, rshift, status) == MPD_UINT_MAX) {
        mpd_seterror(result, MPD_Malloc_error, status);
        goto finish;
    }
    _mpd_qadd(result, &big, &small, ctx, status);


finish:
    mpd_del(&tmp);
    mpd_del(&big);
    mpd_del(&small);
}

/*
 * b must be an integer with exponent 0 and in the range +-2*(emax + prec).
 * XXX: In my opinion +-(2*emax + prec) would be more sensible.
 * The result is a with the value of b added to its exponent.
 */
void
mpd_qscaleb(mpd_t *result, const mpd_t *a, const mpd_t *b,
            const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_uint_t n, maxjump;
#ifndef LEGACY_COMPILER
    int64_t exp;
#else
    mpd_uint_t x;
    int x_sign, n_sign;
    mpd_ssize_t exp;
#endif

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
    }
    if (b->exp != 0 || mpd_isinfinite(b)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    n = mpd_qabs_uint(b, &workstatus);
    /* the spec demands this */
    maxjump = 2 * (mpd_uint_t)(ctx->emax + ctx->prec);

    if (n > maxjump || workstatus&MPD_Invalid_operation) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mpd_isinfinite(a)) {
        mpd_qcopy(result, a, status);
        return;
    }

#ifndef LEGACY_COMPILER
    exp = a->exp + (int64_t)n * mpd_arith_sign(b);
    exp = (exp > MPD_EXP_INF) ? MPD_EXP_INF : exp;
    exp = (exp < MPD_EXP_CLAMP) ? MPD_EXP_CLAMP : exp;
#else
    x = (a->exp < 0) ? -a->exp : a->exp;
    x_sign = (a->exp < 0) ? 1 : 0;
    n_sign = mpd_isnegative(b) ? 1 : 0;

    if (x_sign == n_sign) {
        x = x + n;
        if (x < n) x = MPD_UINT_MAX;
    }
    else {
        x_sign = (x >= n) ? x_sign : n_sign;
        x = (x >= n) ? x - n : n - x;
    }
    if (!x_sign && x > MPD_EXP_INF) x = MPD_EXP_INF;
    if (x_sign && x > -MPD_EXP_CLAMP) x = -MPD_EXP_CLAMP;
    exp = x_sign ? -((mpd_ssize_t)x) : (mpd_ssize_t)x;
#endif

    mpd_qcopy(result, a, status);
    result->exp = (mpd_ssize_t)exp;

    mpd_qfinalize(result, ctx, status);
}

/*
 * Shift the coefficient by n digits, positive n is a left shift. In the case
 * of a left shift, the result is decapitated to fit the context precision. If
 * you don't want that, use mpd_shiftl().
 */
void
mpd_qshiftn(mpd_t *result, const mpd_t *a, mpd_ssize_t n, const mpd_context_t *ctx,
            uint32_t *status)
{
    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        mpd_qcopy(result, a, status);
        return;
    }

    if (n >= 0 && n <= ctx->prec) {
        mpd_qshiftl(result, a, n, status);
        _mpd_cap(result, ctx);
    }
    else if (n < 0 && n >= -ctx->prec) {
        if (!mpd_qcopy(result, a, status)) {
            return;
        }
        _mpd_cap(result, ctx);
        mpd_qshiftr_inplace(result, -n);
    }
    else {
        mpd_seterror(result, MPD_Invalid_operation, status);
    }
}

/*
 * Same as mpd_shiftn(), but the shift is specified by the decimal b, which
 * must be an integer with a zero exponent. Infinities remain infinities.
 */
void
mpd_qshift(mpd_t *result, const mpd_t *a, const mpd_t *b, const mpd_context_t *ctx,
           uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_ssize_t n;

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
    }
    if (b->exp != 0 || mpd_isinfinite(b)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    n = mpd_qget_ssize(b, &workstatus);
    if (workstatus&MPD_Invalid_operation) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (n > ctx->prec || n < -ctx->prec) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mpd_isinfinite(a)) {
        mpd_qcopy(result, a, status);
        return;
    }

    if (n >= 0) {
        mpd_qshiftl(result, a, n, status);
        _mpd_cap(result, ctx);
    }
    else {
        if (!mpd_qcopy(result, a, status)) {
            return;
        }
        _mpd_cap(result, ctx);
        mpd_qshiftr_inplace(result, -n);
    }
}

/* Logical Xor */
void
mpd_qxor(mpd_t *result, const mpd_t *a, const mpd_t *b,
        const mpd_context_t *ctx, uint32_t *status)
{
    const mpd_t *big = a, *small = b;
    mpd_uint_t x, y, z, xbit, ybit;
    int k, mswdigits;
    mpd_ssize_t i;

    if (mpd_isspecial(a) || mpd_isspecial(b) ||
        mpd_isnegative(a) || mpd_isnegative(b) ||
        a->exp != 0 || b->exp != 0) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (b->digits > a->digits) {
        big = b;
        small = a;
    }
    if (!mpd_qresize(result, big->len, status)) {
        return;
    }


    /* full words */
    for (i = 0; i < small->len-1; i++) {
        x = small->data[i];
        y = big->data[i];
        z = 0;
        for (k = 0; k < MPD_RDIGITS; k++) {
            xbit = x % 10;
            x /= 10;
            ybit = y % 10;
            y /= 10;
            if (xbit > 1 || ybit > 1) {
                goto invalid_operation;
            }
            z += (xbit^ybit) ? mpd_pow10[k] : 0;
        }
        result->data[i] = z;
    }
    /* most significant word of small */
    x = small->data[i];
    y = big->data[i];
    z = 0;
    mswdigits = mpd_word_digits(x);
    for (k = 0; k < mswdigits; k++) {
        xbit = x % 10;
        x /= 10;
        ybit = y % 10;
        y /= 10;
        if (xbit > 1 || ybit > 1) {
            goto invalid_operation;
        }
        z += (xbit^ybit) ? mpd_pow10[k] : 0;
    }

    /* scan for digits > 1 and copy the rest of y */
    for (; k < MPD_RDIGITS; k++) {
        ybit = y % 10;
        y /= 10;
        if (ybit > 1) {
            goto invalid_operation;
        }
        z += ybit*mpd_pow10[k];
    }
    result->data[i++] = z;
    /* scan for digits > 1 and copy the rest of big */
    for (; i < big->len; i++) {
        y = big->data[i];
        for (k = 0; k < MPD_RDIGITS; k++) {
            ybit = y % 10;
            y /= 10;
            if (ybit > 1) {
                goto invalid_operation;
            }
        }
        result->data[i] = big->data[i];
    }

    mpd_clear_flags(result);
    result->exp = 0;
    result->len = _mpd_real_size(result->data, big->len);
    mpd_qresize(result, result->len, status);
    mpd_setdigits(result);
    _mpd_cap(result, ctx);
    return;

invalid_operation:
    mpd_seterror(result, MPD_Invalid_operation, status);
}


/******************************************************************************/
/*                         Arithmetic operations                              */
/******************************************************************************/

/*
 * The absolute value of a. If a is negative, the result is the same
 * as the result of the minus operation. Otherwise, the result is the
 * result of the plus operation.
 */
void
mpd_qabs(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
         uint32_t *status)
{
    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
    }

    if (mpd_isnegative(a)) {
        mpd_qminus(result, a, ctx, status);
    }
    else {
        mpd_qplus(result, a, ctx, status);
    }
}

static inline void
_mpd_ptrswap(const mpd_t **a, const mpd_t **b)
{
    const mpd_t *t = *a;
    *a = *b;
    *b = t;
}

/* Add or subtract infinities. */
static void
_mpd_qaddsub_inf(mpd_t *result, const mpd_t *a, const mpd_t *b, uint8_t sign_b,
                 uint32_t *status)
{
    if (mpd_isinfinite(a)) {
        if (mpd_sign(a) != sign_b && mpd_isinfinite(b)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
        }
        else {
            mpd_setspecial(result, mpd_sign(a), MPD_INF);
        }
        return;
    }
    assert(mpd_isinfinite(b));
    mpd_setspecial(result, sign_b, MPD_INF);
}

/* Add or subtract non-special numbers. */
static void
_mpd_qaddsub(mpd_t *result, const mpd_t *a, const mpd_t *b, uint8_t sign_b,
             const mpd_context_t *ctx, uint32_t *status)
{
    const mpd_t *big, *small;
    MPD_NEW_STATIC(big_aligned,0,0,0,0);
    MPD_NEW_CONST(tiny,0,0,1,1,1,1);
    mpd_uint_t carry;
    mpd_ssize_t newsize, shift;
    mpd_ssize_t exp, i;
    int swap = 0;


    /* compare exponents */
    big = a; small = b;
    if (big->exp != small->exp) {
        if (small->exp > big->exp) {
            _mpd_ptrswap(&big, &small);
            swap++;
        }
        /* align the coefficients */
        if (!mpd_iszerocoeff(big)) {
            exp = big->exp - 1;
            exp += (big->digits > ctx->prec) ? 0 : big->digits-ctx->prec-1;
            if (mpd_adjexp(small) < exp) {
                /*
                 * Avoid huge shifts by substituting a value for small that is
                 * guaranteed to produce the same results.
                 *
                 * adjexp(small) < exp if and only if:
                 *
                 *   bdigits <= prec AND
                 *   bdigits+shift >= prec+2+sdigits AND
                 *   exp = bexp+bdigits-prec-2
                 *
                 *     1234567000000000  ->  bdigits + shift
                 *     ----------XX1234  ->  sdigits
                 *     ----------X1      ->  tiny-digits
                 *     |- prec -|
                 *
                 *      OR
                 *
                 *   bdigits > prec AND
                 *   shift > sdigits AND
                 *   exp = bexp-1
                 *
                 *     1234567892100000  ->  bdigits + shift
                 *     ----------XX1234  ->  sdigits
                 *     ----------X1      ->  tiny-digits
                 *     |- prec -|
                 *
                 * If tiny is zero, adding or subtracting is a no-op.
                 * Otherwise, adding tiny generates a non-zero digit either
                 * below the rounding digit or the least significant digit
                 * of big. When subtracting, tiny is in the same position as
                 * the carry that would be generated by subtracting sdigits.
                 */
                mpd_copy_flags(&tiny, small);
                tiny.exp = exp;
                tiny.digits = 1;
                tiny.len = 1;
                tiny.data[0] = mpd_iszerocoeff(small) ? 0 : 1;
                small = &tiny;
            }
            /* This cannot wrap: the difference is positive and <= maxprec */
            shift = big->exp - small->exp;
            if (!mpd_qshiftl(&big_aligned, big, shift, status)) {
                mpd_seterror(result, MPD_Malloc_error, status);
                goto finish;
            }
            big = &big_aligned;
        }
    }
    result->exp = small->exp;


    /* compare length of coefficients */
    if (big->len < small->len) {
        _mpd_ptrswap(&big, &small);
        swap++;
    }

    newsize = big->len;
    if (!mpd_qresize(result, newsize, status)) {
        goto finish;
    }

    if (mpd_sign(a) == sign_b) {

        carry = _mpd_baseadd(result->data, big->data, small->data,
                             big->len, small->len);

        if (carry) {
            newsize = big->len + 1;
            if (!mpd_qresize(result, newsize, status)) {
                goto finish;
            }
            result->data[newsize-1] = carry;
        }

        result->len = newsize;
        mpd_set_flags(result, sign_b);
    }
    else {
        if (big->len == small->len) {
            for (i=big->len-1; i >= 0; --i) {
                if (big->data[i] != small->data[i]) {
                    if (big->data[i] < small->data[i]) {
                        _mpd_ptrswap(&big, &small);
                        swap++;
                    }
                    break;
                }
            }
        }

        _mpd_basesub(result->data, big->data, small->data,
                     big->len, small->len);
        newsize = _mpd_real_size(result->data, big->len);
        /* resize to smaller cannot fail */
        (void)mpd_qresize(result, newsize, status);

        result->len = newsize;
        sign_b = (swap & 1) ? sign_b : mpd_sign(a);
        mpd_set_flags(result, sign_b);

        if (mpd_iszerocoeff(result)) {
            mpd_set_positive(result);
            if (ctx->round == MPD_ROUND_FLOOR) {
                mpd_set_negative(result);
            }
        }
    }

    mpd_setdigits(result);

finish:
    mpd_del(&big_aligned);
}

/* Add a and b. No specials, no finalizing. */
static void
_mpd_qadd(mpd_t *result, const mpd_t *a, const mpd_t *b,
          const mpd_context_t *ctx, uint32_t *status)
{
    _mpd_qaddsub(result, a, b, mpd_sign(b), ctx, status);
}

/* Subtract b from a. No specials, no finalizing. */
static void
_mpd_qsub(mpd_t *result, const mpd_t *a, const mpd_t *b,
          const mpd_context_t *ctx, uint32_t *status)
{
     _mpd_qaddsub(result, a, b, !mpd_sign(b), ctx, status);
}

/* Add a and b. */
void
mpd_qadd(mpd_t *result, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
        _mpd_qaddsub_inf(result, a, b, mpd_sign(b), status);
        return;
    }

    _mpd_qaddsub(result, a, b, mpd_sign(b), ctx, status);
    mpd_qfinalize(result, ctx, status);
}

/* Add a and b. Set NaN/Invalid_operation if the result is inexact. */
static void
_mpd_qadd_exact(mpd_t *result, const mpd_t *a, const mpd_t *b,
                const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;

    mpd_qadd(result, a, b, ctx, &workstatus);
    *status |= workstatus;
    if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
    }
}

/* Subtract b from a. */
void
mpd_qsub(mpd_t *result, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
        _mpd_qaddsub_inf(result, a, b, !mpd_sign(b), status);
        return;
    }

    _mpd_qaddsub(result, a, b, !mpd_sign(b), ctx, status);
    mpd_qfinalize(result, ctx, status);
}

/* Subtract b from a. Set NaN/Invalid_operation if the result is inexact. */
static void
_mpd_qsub_exact(mpd_t *result, const mpd_t *a, const mpd_t *b,
                const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;

    mpd_qsub(result, a, b, ctx, &workstatus);
    *status |= workstatus;
    if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
    }
}

/* Add decimal and mpd_ssize_t. */
void
mpd_qadd_ssize(mpd_t *result, const mpd_t *a, mpd_ssize_t b,
               const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_ssize(&bb, b, &maxcontext, status);
    mpd_qadd(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Add decimal and mpd_uint_t. */
void
mpd_qadd_uint(mpd_t *result, const mpd_t *a, mpd_uint_t b,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_uint(&bb, b, &maxcontext, status);
    mpd_qadd(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Subtract mpd_ssize_t from decimal. */
void
mpd_qsub_ssize(mpd_t *result, const mpd_t *a, mpd_ssize_t b,
               const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_ssize(&bb, b, &maxcontext, status);
    mpd_qsub(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Subtract mpd_uint_t from decimal. */
void
mpd_qsub_uint(mpd_t *result, const mpd_t *a, mpd_uint_t b,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_uint(&bb, b, &maxcontext, status);
    mpd_qsub(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Add decimal and int32_t. */
void
mpd_qadd_i32(mpd_t *result, const mpd_t *a, int32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qadd_ssize(result, a, b, ctx, status);
}

/* Add decimal and uint32_t. */
void
mpd_qadd_u32(mpd_t *result, const mpd_t *a, uint32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qadd_uint(result, a, b, ctx, status);
}

#ifdef CONFIG_64
/* Add decimal and int64_t. */
void
mpd_qadd_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qadd_ssize(result, a, b, ctx, status);
}

/* Add decimal and uint64_t. */
void
mpd_qadd_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qadd_uint(result, a, b, ctx, status);
}
#elif !defined(LEGACY_COMPILER)
/* Add decimal and int64_t. */
void
mpd_qadd_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_i64(&bb, b, &maxcontext, status);
    mpd_qadd(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Add decimal and uint64_t. */
void
mpd_qadd_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_u64(&bb, b, &maxcontext, status);
    mpd_qadd(result, a, &bb, ctx, status);
    mpd_del(&bb);
}
#endif

/* Subtract int32_t from decimal. */
void
mpd_qsub_i32(mpd_t *result, const mpd_t *a, int32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qsub_ssize(result, a, b, ctx, status);
}

/* Subtract uint32_t from decimal. */
void
mpd_qsub_u32(mpd_t *result, const mpd_t *a, uint32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qsub_uint(result, a, b, ctx, status);
}

#ifdef CONFIG_64
/* Subtract int64_t from decimal. */
void
mpd_qsub_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qsub_ssize(result, a, b, ctx, status);
}

/* Subtract uint64_t from decimal. */
void
mpd_qsub_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qsub_uint(result, a, b, ctx, status);
}
#elif !defined(LEGACY_COMPILER)
/* Subtract int64_t from decimal. */
void
mpd_qsub_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_i64(&bb, b, &maxcontext, status);
    mpd_qsub(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Subtract uint64_t from decimal. */
void
mpd_qsub_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_u64(&bb, b, &maxcontext, status);
    mpd_qsub(result, a, &bb, ctx, status);
    mpd_del(&bb);
}
#endif


/* Divide infinities. */
static void
_mpd_qdiv_inf(mpd_t *result, const mpd_t *a, const mpd_t *b,
              const mpd_context_t *ctx, uint32_t *status)
{
    if (mpd_isinfinite(a)) {
        if (mpd_isinfinite(b)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        mpd_setspecial(result, mpd_sign(a)^mpd_sign(b), MPD_INF);
        return;
    }
    assert(mpd_isinfinite(b));
    _settriple(result, mpd_sign(a)^mpd_sign(b), 0, mpd_etiny(ctx));
    *status |= MPD_Clamped;
}

enum {NO_IDEAL_EXP, SET_IDEAL_EXP};
/* Divide a by b. */
static void
_mpd_qdiv(int action, mpd_t *q, const mpd_t *a, const mpd_t *b,
          const mpd_context_t *ctx, uint32_t *status)
{
    MPD_NEW_STATIC(aligned,0,0,0,0);
    mpd_uint_t ld;
    mpd_ssize_t shift, exp, tz;
    mpd_ssize_t newsize;
    mpd_ssize_t ideal_exp;
    mpd_uint_t rem;
    uint8_t sign_a = mpd_sign(a);
    uint8_t sign_b = mpd_sign(b);


    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(q, a, b, ctx, status)) {
            return;
        }
        _mpd_qdiv_inf(q, a, b, ctx, status);
        return;
    }
    if (mpd_iszerocoeff(b)) {
        if (mpd_iszerocoeff(a)) {
            mpd_seterror(q, MPD_Division_undefined, status);
        }
        else {
            mpd_setspecial(q, sign_a^sign_b, MPD_INF);
            *status |= MPD_Division_by_zero;
        }
        return;
    }
    if (mpd_iszerocoeff(a)) {
        exp = a->exp - b->exp;
        _settriple(q, sign_a^sign_b, 0, exp);
        mpd_qfinalize(q, ctx, status);
        return;
    }

    shift = (b->digits - a->digits) + ctx->prec + 1;
    ideal_exp = a->exp - b->exp;
    exp = ideal_exp - shift;
    if (shift > 0) {
        if (!mpd_qshiftl(&aligned, a, shift, status)) {
            mpd_seterror(q, MPD_Malloc_error, status);
            goto finish;
        }
        a = &aligned;
    }
    else if (shift < 0) {
        shift = -shift;
        if (!mpd_qshiftl(&aligned, b, shift, status)) {
            mpd_seterror(q, MPD_Malloc_error, status);
            goto finish;
        }
        b = &aligned;
    }


    newsize = a->len - b->len + 1;
    if ((q != b && q != a) || (q == b && newsize > b->len)) {
        if (!mpd_qresize(q, newsize, status)) {
            mpd_seterror(q, MPD_Malloc_error, status);
            goto finish;
        }
    }


    if (b->len == 1) {
        rem = _mpd_shortdiv(q->data, a->data, a->len, b->data[0]);
    }
    else if (b->len <= MPD_NEWTONDIV_CUTOFF) {
        int ret = _mpd_basedivmod(q->data, NULL, a->data, b->data,
                                  a->len, b->len);
        if (ret < 0) {
            mpd_seterror(q, MPD_Malloc_error, status);
            goto finish;
        }
        rem = ret;
    }
    else {
        MPD_NEW_STATIC(r,0,0,0,0);
        _mpd_base_ndivmod(q, &r, a, b, status);
        if (mpd_isspecial(q) || mpd_isspecial(&r)) {
            mpd_setspecial(q, MPD_POS, MPD_NAN);
            mpd_del(&r);
            goto finish;
        }
        rem = !mpd_iszerocoeff(&r);
        mpd_del(&r);
        newsize = q->len;
    }

    newsize = _mpd_real_size(q->data, newsize);
    /* resize to smaller cannot fail */
    mpd_qresize(q, newsize, status);
    mpd_set_flags(q, sign_a^sign_b);
    q->len = newsize;
    mpd_setdigits(q);

    shift = ideal_exp - exp;
    if (rem) {
        ld = mpd_lsd(q->data[0]);
        if (ld == 0 || ld == 5) {
            q->data[0] += 1;
        }
    }
    else if (action == SET_IDEAL_EXP && shift > 0) {
        tz = mpd_trail_zeros(q);
        shift = (tz > shift) ? shift : tz;
        mpd_qshiftr_inplace(q, shift);
        exp += shift;
    }

    q->exp = exp;


finish:
    mpd_del(&aligned);
    mpd_qfinalize(q, ctx, status);
}

/* Divide a by b. */
void
mpd_qdiv(mpd_t *q, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    MPD_NEW_STATIC(aa,0,0,0,0);
    MPD_NEW_STATIC(bb,0,0,0,0);
    uint32_t xstatus = 0;

    if (q == a) {
        if (!mpd_qcopy(&aa, a, status)) {
            mpd_seterror(q, MPD_Malloc_error, status);
            goto out;
        }
        a = &aa;
    }

    if (q == b) {
        if (!mpd_qcopy(&bb, b, status)) {
            mpd_seterror(q, MPD_Malloc_error, status);
            goto out;
        }
        b = &bb;
    }

    _mpd_qdiv(SET_IDEAL_EXP, q, a, b, ctx, &xstatus);

    if (xstatus & (MPD_Malloc_error|MPD_Division_impossible)) {
        /* Inexact quotients (the usual case) fill the entire context precision,
         * which can lead to the above errors for very high precisions. Retry
         * the operation with a lower precision in case the result is exact.
         *
         * We need an upper bound for the number of digits of a_coeff / b_coeff
         * when the result is exact.  If a_coeff' * 1 / b_coeff' is in lowest
         * terms, then maxdigits(a_coeff') + maxdigits(1 / b_coeff') is a suitable
         * bound.
         *
         * 1 / b_coeff' is exact iff b_coeff' exclusively has prime factors 2 or 5.
         * The largest amount of digits is generated if b_coeff' is a power of 2 or
         * a power of 5 and is less than or equal to log5(b_coeff') <= log2(b_coeff').
         *
         * We arrive at a total upper bound:
         *
         *   maxdigits(a_coeff') + maxdigits(1 / b_coeff') <=
         *   log10(a_coeff) + log2(b_coeff) =
         *   log10(a_coeff) + log10(b_coeff) / log10(2) <=
         *   a->digits + b->digits * 4;
         */
        uint32_t ystatus = 0;
        mpd_context_t workctx;
        mpd_workcontext(&workctx, ctx);

        workctx.prec = a->digits + b->digits * 4;
        if (workctx.prec >= ctx->prec) {
            *status |= (xstatus&MPD_Errors);
            goto out;  /* No point in retrying, keep the original error. */
        }

        _mpd_qdiv(SET_IDEAL_EXP, q, a, b, &workctx, &ystatus);
        if (ystatus != 0) {
            ystatus = *status | ((ystatus|xstatus)&MPD_Errors);
            mpd_seterror(q, ystatus, status);
        }
    }
    else {
        *status |= xstatus;
    }


out:
    mpd_del(&aa);
    mpd_del(&bb);
}

/* Internal function. */
static void
_mpd_qdivmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
             const mpd_context_t *ctx, uint32_t *status)
{
    MPD_NEW_STATIC(aligned,0,0,0,0);
    mpd_ssize_t qsize, rsize;
    mpd_ssize_t ideal_exp, expdiff, shift;
    uint8_t sign_a = mpd_sign(a);
    uint8_t sign_ab = mpd_sign(a)^mpd_sign(b);


    ideal_exp = (a->exp > b->exp) ?  b->exp : a->exp;
    if (mpd_iszerocoeff(a)) {
        if (!mpd_qcopy(r, a, status)) {
            goto nanresult; /* GCOV_NOT_REACHED */
        }
        r->exp = ideal_exp;
        _settriple(q, sign_ab, 0, 0);
        return;
    }

    expdiff = mpd_adjexp(a) - mpd_adjexp(b);
    if (expdiff < 0) {
        if (a->exp > b->exp) {
            /* positive and less than b->digits - a->digits */
            shift = a->exp - b->exp;
            if (!mpd_qshiftl(r, a, shift, status)) {
                goto nanresult;
            }
            r->exp = ideal_exp;
        }
        else {
            if (!mpd_qcopy(r, a, status)) {
                goto nanresult;
            }
        }
        _settriple(q, sign_ab, 0, 0);
        return;
    }
    if (expdiff > ctx->prec) {
        *status |= MPD_Division_impossible;
        goto nanresult;
    }


    /*
     * At this point we have:
     *   (1) 0 <= a->exp + a->digits - b->exp - b->digits <= prec
     *   (2) a->exp - b->exp >= b->digits - a->digits
     *   (3) a->exp - b->exp <= prec + b->digits - a->digits
     */
    if (a->exp != b->exp) {
        shift = a->exp - b->exp;
        if (shift > 0) {
            /* by (3), after the shift a->digits <= prec + b->digits */
            if (!mpd_qshiftl(&aligned, a, shift, status)) {
                goto nanresult;
            }
            a = &aligned;
        }
        else  {
            shift = -shift;
            /* by (2), after the shift b->digits <= a->digits */
            if (!mpd_qshiftl(&aligned, b, shift, status)) {
                goto nanresult;
            }
            b = &aligned;
        }
    }


    qsize = a->len - b->len + 1;
    if (!(q == a && qsize < a->len) && !(q == b && qsize < b->len)) {
        if (!mpd_qresize(q, qsize, status)) {
            goto nanresult;
        }
    }

    rsize = b->len;
    if (!(r == a && rsize < a->len)) {
        if (!mpd_qresize(r, rsize, status)) {
            goto nanresult;
        }
    }

    if (b->len == 1) {
        assert(b->data[0] != 0); /* annotation for scan-build */
        if (a->len == 1) {
            _mpd_div_word(&q->data[0], &r->data[0], a->data[0], b->data[0]);
        }
        else {
            r->data[0] = _mpd_shortdiv(q->data, a->data, a->len, b->data[0]);
        }
    }
    else if (b->len <= MPD_NEWTONDIV_CUTOFF) {
        int ret;
        ret = _mpd_basedivmod(q->data, r->data, a->data, b->data,
                              a->len, b->len);
        if (ret == -1) {
            *status |= MPD_Malloc_error;
            goto nanresult;
        }
    }
    else {
        _mpd_base_ndivmod(q, r, a, b, status);
        if (mpd_isspecial(q) || mpd_isspecial(r)) {
            goto nanresult;
        }
        qsize = q->len;
        rsize = r->len;
    }

    qsize = _mpd_real_size(q->data, qsize);
    /* resize to smaller cannot fail */
    mpd_qresize(q, qsize, status);
    q->len = qsize;
    mpd_setdigits(q);
    mpd_set_flags(q, sign_ab);
    q->exp = 0;
    if (q->digits > ctx->prec) {
        *status |= MPD_Division_impossible;
        goto nanresult;
    }

    rsize = _mpd_real_size(r->data, rsize);
    /* resize to smaller cannot fail */
    mpd_qresize(r, rsize, status);
    r->len = rsize;
    mpd_setdigits(r);
    mpd_set_flags(r, sign_a);
    r->exp = ideal_exp;

out:
    mpd_del(&aligned);
    return;

nanresult:
    mpd_setspecial(q, MPD_POS, MPD_NAN);
    mpd_setspecial(r, MPD_POS, MPD_NAN);
    goto out;
}

/* Integer division with remainder. */
void
mpd_qdivmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
            const mpd_context_t *ctx, uint32_t *status)
{
    uint8_t sign = mpd_sign(a)^mpd_sign(b);

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(q, a, b, ctx, status)) {
            mpd_qcopy(r, q, status);
            return;
        }
        if (mpd_isinfinite(a)) {
            if (mpd_isinfinite(b)) {
                mpd_setspecial(q, MPD_POS, MPD_NAN);
            }
            else {
                mpd_setspecial(q, sign, MPD_INF);
            }
            mpd_setspecial(r, MPD_POS, MPD_NAN);
            *status |= MPD_Invalid_operation;
            return;
        }
        if (mpd_isinfinite(b)) {
            if (!mpd_qcopy(r, a, status)) {
                mpd_seterror(q, MPD_Malloc_error, status);
                return;
            }
            mpd_qfinalize(r, ctx, status);
            _settriple(q, sign, 0, 0);
            return;
        }
        /* debug */
        abort(); /* GCOV_NOT_REACHED */
    }
    if (mpd_iszerocoeff(b)) {
        if (mpd_iszerocoeff(a)) {
            mpd_setspecial(q, MPD_POS, MPD_NAN);
            mpd_setspecial(r, MPD_POS, MPD_NAN);
            *status |= MPD_Division_undefined;
        }
        else {
            mpd_setspecial(q, sign, MPD_INF);
            mpd_setspecial(r, MPD_POS, MPD_NAN);
            *status |= (MPD_Division_by_zero|MPD_Invalid_operation);
        }
        return;
    }

    _mpd_qdivmod(q, r, a, b, ctx, status);
    mpd_qfinalize(q, ctx, status);
    mpd_qfinalize(r, ctx, status);
}

void
mpd_qdivint(mpd_t *q, const mpd_t *a, const mpd_t *b,
            const mpd_context_t *ctx, uint32_t *status)
{
    MPD_NEW_STATIC(r,0,0,0,0);
    uint8_t sign = mpd_sign(a)^mpd_sign(b);

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(q, a, b, ctx, status)) {
            return;
        }
        if (mpd_isinfinite(a) && mpd_isinfinite(b)) {
            mpd_seterror(q, MPD_Invalid_operation, status);
            return;
        }
        if (mpd_isinfinite(a)) {
            mpd_setspecial(q, sign, MPD_INF);
            return;
        }
        if (mpd_isinfinite(b)) {
            _settriple(q, sign, 0, 0);
            return;
        }
        /* debug */
        abort(); /* GCOV_NOT_REACHED */
    }
    if (mpd_iszerocoeff(b)) {
        if (mpd_iszerocoeff(a)) {
            mpd_seterror(q, MPD_Division_undefined, status);
        }
        else {
            mpd_setspecial(q, sign, MPD_INF);
            *status |= MPD_Division_by_zero;
        }
        return;
    }


    _mpd_qdivmod(q, &r, a, b, ctx, status);
    mpd_del(&r);
    mpd_qfinalize(q, ctx, status);
}

/* Divide decimal by mpd_ssize_t. */
void
mpd_qdiv_ssize(mpd_t *result, const mpd_t *a, mpd_ssize_t b,
               const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_ssize(&bb, b, &maxcontext, status);
    mpd_qdiv(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Divide decimal by mpd_uint_t. */
void
mpd_qdiv_uint(mpd_t *result, const mpd_t *a, mpd_uint_t b,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_uint(&bb, b, &maxcontext, status);
    mpd_qdiv(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Divide decimal by int32_t. */
void
mpd_qdiv_i32(mpd_t *result, const mpd_t *a, int32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qdiv_ssize(result, a, b, ctx, status);
}

/* Divide decimal by uint32_t. */
void
mpd_qdiv_u32(mpd_t *result, const mpd_t *a, uint32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qdiv_uint(result, a, b, ctx, status);
}

#ifdef CONFIG_64
/* Divide decimal by int64_t. */
void
mpd_qdiv_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qdiv_ssize(result, a, b, ctx, status);
}

/* Divide decimal by uint64_t. */
void
mpd_qdiv_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qdiv_uint(result, a, b, ctx, status);
}
#elif !defined(LEGACY_COMPILER)
/* Divide decimal by int64_t. */
void
mpd_qdiv_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_i64(&bb, b, &maxcontext, status);
    mpd_qdiv(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Divide decimal by uint64_t. */
void
mpd_qdiv_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_u64(&bb, b, &maxcontext, status);
    mpd_qdiv(result, a, &bb, ctx, status);
    mpd_del(&bb);
}
#endif

/* Pad the result with trailing zeros if it has fewer digits than prec. */
static void
_mpd_zeropad(mpd_t *result, const mpd_context_t *ctx, uint32_t *status)
{
    if (!mpd_isspecial(result) && !mpd_iszero(result) &&
        result->digits < ctx->prec) {
       mpd_ssize_t shift = ctx->prec - result->digits;
       mpd_qshiftl(result, result, shift, status);
       result->exp -= shift;
    }
}

/* Check if the result is guaranteed to be one. */
static int
_mpd_qexp_check_one(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
                    uint32_t *status)
{
    MPD_NEW_CONST(lim,0,-(ctx->prec+1),1,1,1,9);
    MPD_NEW_SHARED(aa, a);

    mpd_set_positive(&aa);

    /* abs(a) <= 9 * 10**(-prec-1) */
    if (_mpd_cmp(&aa, &lim) <= 0) {
        _settriple(result, 0, 1, 0);
        *status |= MPD_Rounded|MPD_Inexact;
        return 1;
    }

    return 0;
}

/*
 * Get the number of iterations for the Horner scheme in _mpd_qexp().
 */
static inline mpd_ssize_t
_mpd_get_exp_iterations(const mpd_t *r, mpd_ssize_t p)
{
    mpd_ssize_t log10pbyr; /* lower bound for log10(p / abs(r)) */
    mpd_ssize_t n;

    assert(p >= 10);
    assert(!mpd_iszero(r));
    assert(-p < mpd_adjexp(r) && mpd_adjexp(r) <= -1);

#ifdef CONFIG_64
    if (p > (mpd_ssize_t)(1ULL<<52)) {
        return MPD_SSIZE_MAX;
    }
#endif

    /*
     * Lower bound for log10(p / abs(r)): adjexp(p) - (adjexp(r) + 1)
     * At this point (for CONFIG_64, CONFIG_32 is not problematic):
     *    1) 10 <= p <= 2**52
     *    2) -p < adjexp(r) <= -1
     *    3) 1 <= log10pbyr <= 2**52 + 14
     */
    log10pbyr = (mpd_word_digits(p)-1) - (mpd_adjexp(r)+1);

    /*
     * The numerator in the paper is 1.435 * p - 1.182, calculated
     * exactly. We compensate for rounding errors by using 1.43503.
     * ACL2 proofs:
     *    1) exp-iter-approx-lower-bound: The term below evaluated
     *       in 53-bit floating point arithmetic is greater than or
     *       equal to the exact term used in the paper.
     *    2) exp-iter-approx-upper-bound: The term below is less than
     *       or equal to 3/2 * p <= 3/2 * 2**52.
     */
    n = (mpd_ssize_t)ceil((1.43503*(double)p - 1.182) / (double)log10pbyr);
    return n >= 3 ? n : 3;
}

/*
 * Internal function, specials have been dealt with. Apart from Overflow
 * and Underflow, two cases must be considered for the error of the result:
 *
 *   1) abs(a) <= 9 * 10**(-prec-1)  ==>  result == 1
 *
 *      Absolute error: abs(1 - e**x) < 10**(-prec)
 *      -------------------------------------------
 *
 *   2) abs(a) > 9 * 10**(-prec-1)
 *
 *      Relative error: abs(result - e**x) < 0.5 * 10**(-prec) * e**x
 *      -------------------------------------------------------------
 *
 * The algorithm is from Hull&Abrham, Variable Precision Exponential Function,
 * ACM Transactions on Mathematical Software, Vol. 12, No. 2, June 1986.
 *
 * Main differences:
 *
 *  - The number of iterations for the Horner scheme is calculated using
 *    53-bit floating point arithmetic.
 *
 *  - In the error analysis for ER (relative error accumulated in the
 *    evaluation of the truncated series) the reduced operand r may
 *    have any number of digits.
 *    ACL2 proof: exponent-relative-error
 *
 *  - The analysis for early abortion has been adapted for the mpd_t
 *    ranges.
 */
static int
_mpd_qexp(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
          uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_STATIC(tmp,0,0,0,0);
    MPD_NEW_STATIC(sum,0,0,0,0);
    MPD_NEW_CONST(word,0,0,1,1,1,1);
    mpd_ssize_t j, n, t;

    assert(!mpd_isspecial(a));

    if (mpd_iszerocoeff(a)) {
        _settriple(result, MPD_POS, 1, 0);
        return 1;
    }

    /*
     * We are calculating e^x = e^(r*10^t) = (e^r)^(10^t), where abs(r) < 1 and t >= 0.
     *
     * If t > 0, we have:
     *
     *   (1) 0.1 <= r < 1, so e^0.1 <= e^r. If t > MAX_T, overflow occurs:
     *
     *     MAX-EMAX+1 < log10(e^(0.1*10*t)) <= log10(e^(r*10^t)) < adjexp(e^(r*10^t))+1
     *
     *   (2) -1 < r <= -0.1, so e^r <= e^-0.1. If t > MAX_T, underflow occurs:
     *
     *     adjexp(e^(r*10^t)) <= log10(e^(r*10^t)) <= log10(e^(-0.1*10^t)) < MIN-ETINY
     */
#if defined(CONFIG_64)
    #define MPD_EXP_MAX_T 19
#elif defined(CONFIG_32)
    #define MPD_EXP_MAX_T 10
#endif
    t = a->digits + a->exp;
    t = (t > 0) ? t : 0;
    if (t > MPD_EXP_MAX_T) {
        if (mpd_ispositive(a)) {
            mpd_setspecial(result, MPD_POS, MPD_INF);
            *status |= MPD_Overflow|MPD_Inexact|MPD_Rounded;
        }
        else {
            _settriple(result, MPD_POS, 0, mpd_etiny(ctx));
            *status |= (MPD_Inexact|MPD_Rounded|MPD_Subnormal|
                        MPD_Underflow|MPD_Clamped);
        }
        return 1;
    }

    /* abs(a) <= 9 * 10**(-prec-1) */
    if (_mpd_qexp_check_one(result, a, ctx, status)) {
        return 1;
    }

    mpd_maxcontext(&workctx);
    workctx.prec = ctx->prec + t + 2;
    workctx.prec = (workctx.prec < 10) ? 10 : workctx.prec;
    workctx.round = MPD_ROUND_HALF_EVEN;

    if (!mpd_qcopy(result, a, status)) {
        return 1;
    }
    result->exp -= t;

    /*
     * At this point:
     *    1) 9 * 10**(-prec-1) < abs(a)
     *    2) 9 * 10**(-prec-t-1) < abs(r)
     *    3) log10(9) - prec - t - 1 < log10(abs(r)) < adjexp(abs(r)) + 1
     *    4) - prec - t - 2 < adjexp(abs(r)) <= -1
     */
    n = _mpd_get_exp_iterations(result, workctx.prec);
    if (n == MPD_SSIZE_MAX) {
        mpd_seterror(result, MPD_Invalid_operation, status); /* GCOV_UNLIKELY */
        return 1; /* GCOV_UNLIKELY */
    }

    _settriple(&sum, MPD_POS, 1, 0);

    for (j = n-1; j >= 1; j--) {
        word.data[0] = j;
        mpd_setdigits(&word);
        mpd_qdiv(&tmp, result, &word, &workctx, &workctx.status);
        mpd_qfma(&sum, &sum, &tmp, &one, &workctx, &workctx.status);
    }

#ifdef CONFIG_64
    _mpd_qpow_uint(result, &sum, mpd_pow10[t], MPD_POS, &workctx, status);
#else
    if (t <= MPD_MAX_POW10) {
        _mpd_qpow_uint(result, &sum, mpd_pow10[t], MPD_POS, &workctx, status);
    }
    else {
        t -= MPD_MAX_POW10;
        _mpd_qpow_uint(&tmp, &sum, mpd_pow10[MPD_MAX_POW10], MPD_POS,
                       &workctx, status);
        _mpd_qpow_uint(result, &tmp, mpd_pow10[t], MPD_POS, &workctx, status);
    }
#endif

    mpd_del(&tmp);
    mpd_del(&sum);
    *status |= (workctx.status&MPD_Errors);
    *status |= (MPD_Inexact|MPD_Rounded);

    return 0;
}

/* exp(a) */
void
mpd_qexp(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
         uint32_t *status)
{
    mpd_context_t workctx;

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        if (mpd_isnegative(a)) {
            _settriple(result, MPD_POS, 0, 0);
        }
        else {
            mpd_setspecial(result, MPD_POS, MPD_INF);
        }
        return;
    }
    if (mpd_iszerocoeff(a)) {
        _settriple(result, MPD_POS, 1, 0);
        return;
    }

    mpd_workcontext(&workctx, ctx);
    workctx.round = MPD_ROUND_HALF_EVEN;

    if (ctx->allcr) {
        MPD_NEW_STATIC(hi, 0,0,0,0);
        MPD_NEW_STATIC(lo, 0,0,0,0);
        MPD_NEW_STATIC(ulp, 0,0,0,0);
        MPD_NEW_STATIC(aa, 0,0,0,0);
        uint32_t loop_protect = 0;
        mpd_ssize_t prec;
        mpd_ssize_t ulpexp;

        if (result == a) {
            if (!mpd_qcopy(&aa, a, status)) {
                mpd_seterror(result, MPD_Malloc_error, status);
                return;
            }
            a = &aa;
        }

        workctx.clamp = 0;
        prec = ctx->prec + 3;
        while (1) {
            uint32_t status_res = 0;
            uint32_t status_hi = 0;
            uint32_t status_lo = 0;
            int shortcut;
            int bounds_eq;
            int subnormal_eq;

            workctx.prec = prec;
            shortcut = _mpd_qexp(result, a, &workctx, &status_res);
            if (mpd_isnan(result)) {
                mpd_seterror(result, status_res, status);
                break;
            }
            if (shortcut || mpd_isinfinite(result) || mpd_iszero(result)) {
                *status |= status_res;
                workctx.prec = ctx->prec;
                workctx.clamp = ctx->clamp;
                _mpd_zeropad(result, &workctx, status);
                mpd_qfinalize(result, &workctx, status);
                break;
            }

            ulpexp = result->exp + result->digits - workctx.prec;
            if (status_res & MPD_Underflow) {
                /* The effective work precision is result->digits. */
                ulpexp = result->exp;
            }
            _ssettriple(&ulp, MPD_POS, 1, ulpexp);

            /*
             * At this point [1]:
             *   1) abs(result - e**x) < 0.5 * 10**(-prec) * e**x
             *   2) result - ulp < e**x < result + ulp
             *   3) result - ulp < result < result + ulp
             *
             * If round(result-ulp)==round(result+ulp), then
             * round(result)==round(e**x). Therefore the result
             * is correctly rounded.
             *
             * [1] If abs(a) <= 9 * 10**(-prec-1), use the absolute
             *     error for a similar argument.
             */
            workctx.prec = ctx->prec;
            mpd_qadd(&hi, result, &ulp, &workctx, &status_hi);
            mpd_qsub(&lo, result, &ulp, &workctx, &status_lo);
            if (mpd_isnan(&hi) || mpd_isnan(&lo)) {
                mpd_seterror(result, status_hi|status_lo, status);
                break;
            }

            subnormal_eq = (status_hi&MPD_Subnormal) == (status_lo&MPD_Subnormal);
            bounds_eq = mpd_qcmp(&hi, &lo, status) == 0;
            if (bounds_eq && ++loop_protect > 5) {
                /* If the bounds are equal, the result is always correctly rounded.

                   Resolving the subnormal status can take more iterations (around
                   three) in extremely rare cases. 'hi' and 'lo' are so close that
                   subnormal/underflow is largely cosmetic, so allow a maximum of
                   five additional iterations. */
                subnormal_eq = 1; /* GCOV_NOT_REACHED */
            }

            if (subnormal_eq && bounds_eq) {
                *status |= status_lo;
                workctx.clamp = ctx->clamp;
                _mpd_zeropad(result, &workctx, status);
                mpd_qfinalize(result, &workctx, status);
                break;
            }

            if (subnormal_eq) {
                prec += MPD_RDIGITS;
            }
            else {
                prec *= 2;
            }
            if (prec > MPD_MAX_PREC) {
                mpd_seterror(result, MPD_Invalid_operation, status);
                break;
            }
        }
        mpd_del(&hi);
        mpd_del(&lo);
        mpd_del(&ulp);
        mpd_del(&aa);
    }
    else {
        _mpd_qexp(result, a, &workctx, status);
        _mpd_zeropad(result, &workctx, status);
        mpd_check_underflow(result, &workctx, status);
        mpd_qfinalize(result, &workctx, status);
    }
}

/* Fused multiply-add: (a * b) + c, with a single final rounding. */
void
mpd_qfma(mpd_t *result, const mpd_t *a, const mpd_t *b, const mpd_t *c,
         const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_t *cc = NULL;

    if (result == c) {
        if ((cc = mpd_qncopy(c)) == NULL) {
            mpd_seterror(result, MPD_Malloc_error, status);
            return;
        }
        c = cc;
    }

    _mpd_qmul(result, a, b, ctx, &workstatus);
    if (!(workstatus&MPD_Invalid_operation)) {
        mpd_qadd(result, result, c, ctx, &workstatus);
    }

    if (cc) mpd_del(cc);
    *status |= workstatus;
}

/*
 * Schedule the optimal precision increase for the Newton iteration.
 *   v := input operand
 *   z_0 := initial approximation
 *   initprec := natural number such that abs(log(v) - z_0) < 10**-initprec
 *   maxprec := target precision
 *
 * For convenience the output klist contains the elements in reverse order:
 *   klist := [k_n-1, ..., k_0], where
 *     1) k_0 <= initprec and
 *     2) abs(log(v) - result) < 10**(-2*k_n-1 + 1) <= 10**-maxprec.
 */
static inline int
ln_schedule_prec(mpd_ssize_t klist[MPD_MAX_PREC_LOG2], mpd_ssize_t maxprec,
                 mpd_ssize_t initprec)
{
    mpd_ssize_t k;
    int i;

    assert(maxprec >= 2 && initprec >= 2);
    if (maxprec <= initprec) return -1;

    i = 0; k = maxprec;
    do {
        k = (k+2) / 2;
        klist[i++] = k;
    } while (k > initprec);

    return i-1;
}

/* The constants have been verified with both decimal.py and mpfr. */
#ifdef CONFIG_64
#if MPD_RDIGITS != 19
  #error "mpdecimal.c: MPD_RDIGITS must be 19."
#endif
static const mpd_uint_t mpd_ln10_data[MPD_MINALLOC_MAX] = {
  6983716328982174407ULL, 9089704281976336583ULL, 1515961135648465461ULL,
  4416816335727555703ULL, 2900988039194170265ULL, 2307925037472986509ULL,
   107598438319191292ULL, 3466624107184669231ULL, 4450099781311469159ULL,
  9807828059751193854ULL, 7713456862091670584ULL, 1492198849978748873ULL,
  6528728696511086257ULL, 2385392051446341972ULL, 8692180205189339507ULL,
  6518769751037497088ULL, 2375253577097505395ULL, 9095610299291824318ULL,
   982748238504564801ULL, 5438635917781170543ULL, 7547331541421808427ULL,
   752371033310119785ULL, 3171643095059950878ULL, 9785265383207606726ULL,
  2932258279850258550ULL, 5497347726624257094ULL, 2976979522110718264ULL,
  9221477656763693866ULL, 1979650047149510504ULL, 6674183485704422507ULL,
  9702766860595249671ULL, 9278096762712757753ULL, 9314848524948644871ULL,
  6826928280848118428ULL,  754403708474699401ULL,  230105703089634572ULL,
  1929203337658714166ULL, 7589402567763113569ULL, 4208241314695689016ULL,
  2922455440575892572ULL, 9356734206705811364ULL, 2684916746550586856ULL,
   644507064800027750ULL, 9476834636167921018ULL, 5659121373450747856ULL,
  2835522011480466371ULL, 6470806855677432162ULL, 7141748003688084012ULL,
  9619404400222105101ULL, 5504893431493939147ULL, 6674744042432743651ULL,
  2287698219886746543ULL, 7773262884616336622ULL, 1985283935053089653ULL,
  4680843799894826233ULL, 8168948290720832555ULL, 8067566662873690987ULL,
  6248633409525465082ULL, 9829834196778404228ULL, 3524802359972050895ULL,
  3327900967572609677ULL,  110148862877297603ULL,  179914546843642076ULL,
  2302585092994045684ULL
};
#else
#if MPD_RDIGITS != 9
  #error "mpdecimal.c: MPD_RDIGITS must be 9."
#endif
static const mpd_uint_t mpd_ln10_data[MPD_MINALLOC_MAX] = {
  401682692UL, 708474699UL, 720754403UL,  30896345UL, 602301057UL, 765871416UL,
  192920333UL, 763113569UL, 589402567UL, 956890167UL,  82413146UL, 589257242UL,
  245544057UL, 811364292UL, 734206705UL, 868569356UL, 167465505UL, 775026849UL,
  706480002UL,  18064450UL, 636167921UL, 569476834UL, 734507478UL, 156591213UL,
  148046637UL, 283552201UL, 677432162UL, 470806855UL, 880840126UL, 417480036UL,
  210510171UL, 940440022UL, 939147961UL, 893431493UL, 436515504UL, 440424327UL,
  654366747UL, 821988674UL, 622228769UL, 884616336UL, 537773262UL, 350530896UL,
  319852839UL, 989482623UL, 468084379UL, 720832555UL, 168948290UL, 736909878UL,
  675666628UL, 546508280UL, 863340952UL, 404228624UL, 834196778UL, 508959829UL,
   23599720UL, 967735248UL,  96757260UL, 603332790UL, 862877297UL, 760110148UL,
  468436420UL, 401799145UL, 299404568UL, 230258509UL
};
#endif
/* _mpd_ln10 is used directly for precisions smaller than MINALLOC_MAX*RDIGITS.
   Otherwise, it serves as the initial approximation for calculating ln(10). */
static const mpd_t _mpd_ln10 = {
  MPD_STATIC|MPD_CONST_DATA, -(MPD_MINALLOC_MAX*MPD_RDIGITS-1),
  MPD_MINALLOC_MAX*MPD_RDIGITS, MPD_MINALLOC_MAX, MPD_MINALLOC_MAX,
  (mpd_uint_t *)mpd_ln10_data
};

/*
 * Set 'result' to log(10).
 *   Ulp error: abs(result - log(10)) < ulp(log(10))
 *   Relative error: abs(result - log(10)) < 5 * 10**-prec * log(10)
 *
 * NOTE: The relative error is not derived from the ulp error, but
 * calculated separately using the fact that 23/10 < log(10) < 24/10.
 */
void
mpd_qln10(mpd_t *result, mpd_ssize_t prec, uint32_t *status)
{
    mpd_context_t varcontext, maxcontext;
    MPD_NEW_STATIC(tmp, 0,0,0,0);
    MPD_NEW_CONST(static10, 0,0,2,1,1,10);
    mpd_ssize_t klist[MPD_MAX_PREC_LOG2];
    mpd_uint_t rnd;
    mpd_ssize_t shift;
    int i;

    assert(prec >= 1);

    shift = MPD_MINALLOC_MAX*MPD_RDIGITS-prec;
    shift = shift < 0 ? 0 : shift;

    rnd = mpd_qshiftr(result, &_mpd_ln10, shift, status);
    if (rnd == MPD_UINT_MAX) {
        mpd_seterror(result, MPD_Malloc_error, status);
        return;
    }
    result->exp = -(result->digits-1);

    mpd_maxcontext(&maxcontext);
    if (prec < MPD_MINALLOC_MAX*MPD_RDIGITS) {
        maxcontext.prec = prec;
        _mpd_apply_round_excess(result, rnd, &maxcontext, status);
        *status |= (MPD_Inexact|MPD_Rounded);
        return;
    }

    mpd_maxcontext(&varcontext);
    varcontext.round = MPD_ROUND_TRUNC;

    i = ln_schedule_prec(klist, prec+2, -result->exp);
    for (; i >= 0; i--) {
        varcontext.prec = 2*klist[i]+3;
        result->flags ^= MPD_NEG;
        _mpd_qexp(&tmp, result, &varcontext, status);
        result->flags ^= MPD_NEG;
        mpd_qmul(&tmp, &static10, &tmp, &varcontext, status);
        mpd_qsub(&tmp, &tmp, &one, &maxcontext, status);
        mpd_qadd(result, result, &tmp, &maxcontext, status);
        if (mpd_isspecial(result)) {
            break;
        }
    }

    mpd_del(&tmp);
    maxcontext.prec = prec;
    mpd_qfinalize(result, &maxcontext, status);
}

/*
 * Initial approximations for the ln() iteration. The values have the
 * following properties (established with both decimal.py and mpfr):
 *
 * Index 0 - 400, logarithms of x in [1.00, 5.00]:
 *   abs(lnapprox[i] * 10**-3 - log((i+100)/100)) < 10**-2
 *   abs(lnapprox[i] * 10**-3 - log((i+1+100)/100)) < 10**-2
 *
 * Index 401 - 899, logarithms of x in (0.500, 0.999]:
 *   abs(-lnapprox[i] * 10**-3 - log((i+100)/1000)) < 10**-2
 *   abs(-lnapprox[i] * 10**-3 - log((i+1+100)/1000)) < 10**-2
 */
static const uint16_t lnapprox[900] = {
  /* index 0 - 400: log((i+100)/100) * 1000 */
  0, 10, 20, 30, 39, 49, 58, 68, 77, 86, 95, 104, 113, 122, 131, 140, 148, 157,
  166, 174, 182, 191, 199, 207, 215, 223, 231, 239, 247, 255, 262, 270, 278,
  285, 293, 300, 308, 315, 322, 329, 336, 344, 351, 358, 365, 372, 378, 385,
  392, 399, 406, 412, 419, 425, 432, 438, 445, 451, 457, 464, 470, 476, 482,
  489, 495, 501, 507, 513, 519, 525, 531, 536, 542, 548, 554, 560, 565, 571,
  577, 582, 588, 593, 599, 604, 610, 615, 621, 626, 631, 637, 642, 647, 652,
  658, 663, 668, 673, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728,
  732, 737, 742, 747, 751, 756, 761, 766, 770, 775, 779, 784, 788, 793, 798,
  802, 806, 811, 815, 820, 824, 829, 833, 837, 842, 846, 850, 854, 859, 863,
  867, 871, 876, 880, 884, 888, 892, 896, 900, 904, 908, 912, 916, 920, 924,
  928, 932, 936, 940, 944, 948, 952, 956, 959, 963, 967, 971, 975, 978, 982,
  986, 990, 993, 997, 1001, 1004, 1008, 1012, 1015, 1019, 1022, 1026, 1030,
  1033, 1037, 1040, 1044, 1047, 1051, 1054, 1058, 1061, 1065, 1068, 1072, 1075,
  1078, 1082, 1085, 1089, 1092, 1095, 1099, 1102, 1105, 1109, 1112, 1115, 1118,
  1122, 1125, 1128, 1131, 1135, 1138, 1141, 1144, 1147, 1151, 1154, 1157, 1160,
  1163, 1166, 1169, 1172, 1176, 1179, 1182, 1185, 1188, 1191, 1194, 1197, 1200,
  1203, 1206, 1209, 1212, 1215, 1218, 1221, 1224, 1227, 1230, 1233, 1235, 1238,
  1241, 1244, 1247, 1250, 1253, 1256, 1258, 1261, 1264, 1267, 1270, 1273, 1275,
  1278, 1281, 1284, 1286, 1289, 1292, 1295, 1297, 1300, 1303, 1306, 1308, 1311,
  1314, 1316, 1319, 1322, 1324, 1327, 1330, 1332, 1335, 1338, 1340, 1343, 1345,
  1348, 1351, 1353, 1356, 1358, 1361, 1364, 1366, 1369, 1371, 1374, 1376, 1379,
  1381, 1384, 1386, 1389, 1391, 1394, 1396, 1399, 1401, 1404, 1406, 1409, 1411,
  1413, 1416, 1418, 1421, 1423, 1426, 1428, 1430, 1433, 1435, 1437, 1440, 1442,
  1445, 1447, 1449, 1452, 1454, 1456, 1459, 1461, 1463, 1466, 1468, 1470, 1472,
  1475, 1477, 1479, 1482, 1484, 1486, 1488, 1491, 1493, 1495, 1497, 1500, 1502,
  1504, 1506, 1509, 1511, 1513, 1515, 1517, 1520, 1522, 1524, 1526, 1528, 1530,
  1533, 1535, 1537, 1539, 1541, 1543, 1545, 1548, 1550, 1552, 1554, 1556, 1558,
  1560, 1562, 1564, 1567, 1569, 1571, 1573, 1575, 1577, 1579, 1581, 1583, 1585,
  1587, 1589, 1591, 1593, 1595, 1597, 1599, 1601, 1603, 1605, 1607, 1609,
  /* index 401 - 899: -log((i+100)/1000) * 1000 */
  691, 689, 687, 685, 683, 681, 679, 677, 675, 673, 671, 669, 668, 666, 664,
  662, 660, 658, 656, 654, 652, 650, 648, 646, 644, 642, 641, 639, 637, 635,
  633, 631, 629, 627, 626, 624, 622, 620, 618, 616, 614, 612, 611, 609, 607,
  605, 603, 602, 600, 598, 596, 594, 592, 591, 589, 587, 585, 583, 582, 580,
  578, 576, 574, 573, 571, 569, 567, 566, 564, 562, 560, 559, 557, 555, 553,
  552, 550, 548, 546, 545, 543, 541, 540, 538, 536, 534, 533, 531, 529, 528,
  526, 524, 523, 521, 519, 518, 516, 514, 512, 511, 509, 508, 506, 504, 502,
  501, 499, 498, 496, 494, 493, 491, 489, 488, 486, 484, 483, 481, 480, 478,
  476, 475, 473, 472, 470, 468, 467, 465, 464, 462, 460, 459, 457, 456, 454,
  453, 451, 449, 448, 446, 445, 443, 442, 440, 438, 437, 435, 434, 432, 431,
  429, 428, 426, 425, 423, 422, 420, 419, 417, 416, 414, 412, 411, 410, 408,
  406, 405, 404, 402, 400, 399, 398, 396, 394, 393, 392, 390, 389, 387, 386,
  384, 383, 381, 380, 378, 377, 375, 374, 372, 371, 370, 368, 367, 365, 364,
  362, 361, 360, 358, 357, 355, 354, 352, 351, 350, 348, 347, 345, 344, 342,
  341, 340, 338, 337, 336, 334, 333, 331, 330, 328, 327, 326, 324, 323, 322,
  320, 319, 318, 316, 315, 313, 312, 311, 309, 308, 306, 305, 304, 302, 301,
  300, 298, 297, 296, 294, 293, 292, 290, 289, 288, 286, 285, 284, 282, 281,
  280, 278, 277, 276, 274, 273, 272, 270, 269, 268, 267, 265, 264, 263, 261,
  260, 259, 258, 256, 255, 254, 252, 251, 250, 248, 247, 246, 245, 243, 242,
  241, 240, 238, 237, 236, 234, 233, 232, 231, 229, 228, 227, 226, 224, 223,
  222, 221, 219, 218, 217, 216, 214, 213, 212, 211, 210, 208, 207, 206, 205,
  203, 202, 201, 200, 198, 197, 196, 195, 194, 192, 191, 190, 189, 188, 186,
  185, 184, 183, 182, 180, 179, 178, 177, 176, 174, 173, 172, 171, 170, 168,
  167, 166, 165, 164, 162, 161, 160, 159, 158, 157, 156, 154, 153, 152, 151,
  150, 148, 147, 146, 145, 144, 143, 142, 140, 139, 138, 137, 136, 135, 134,
  132, 131, 130, 129, 128, 127, 126, 124, 123, 122, 121, 120, 119, 118, 116,
  115, 114, 113, 112, 111, 110, 109, 108, 106, 105, 104, 103, 102, 101, 100,
  99, 98, 97, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 84, 83, 82, 81, 80, 79,
  78, 77, 76, 75, 74, 73, 72, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59,
  58, 57, 56, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39,
  38, 37, 36, 35, 34, 33, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19,
  18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
};

/*
 * Internal ln() function that does not check for specials, zero or one.
 * Relative error: abs(result - log(a)) < 0.1 * 10**-prec * abs(log(a))
 */
static int
_mpd_qln(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
         uint32_t *status)
{
    mpd_context_t varcontext, maxcontext;
    mpd_t *z = result;
    MPD_NEW_STATIC(v,0,0,0,0);
    MPD_NEW_STATIC(vtmp,0,0,0,0);
    MPD_NEW_STATIC(tmp,0,0,0,0);
    mpd_ssize_t klist[MPD_MAX_PREC_LOG2];
    mpd_ssize_t maxprec, shift, t;
    mpd_ssize_t a_digits, a_exp;
    mpd_uint_t dummy, x;
    int ret = 1;
    int i;

    assert(!mpd_isspecial(a) && !mpd_iszerocoeff(a));

    /*
     * We are calculating ln(a) = ln(v * 10^t) = ln(v) + t*ln(10),
     * where 0.5 < v <= 5.
     */
    if (!mpd_qcopy(&v, a, status)) {
        mpd_seterror(result, MPD_Malloc_error, status);
        goto finish;
    }

    /* Initial approximation: we have at least one non-zero digit */
    _mpd_get_msdigits(&dummy, &x, &v, 3);
    if (x < 10) x *= 10;
    if (x < 100) x *= 10;
    x -= 100;

    /* a may equal z */
    a_digits = a->digits;
    a_exp = a->exp;

    mpd_minalloc(z);
    mpd_clear_flags(z);
    z->data[0] = lnapprox[x];
    z->len = 1;
    z->exp = -3;
    mpd_setdigits(z);

    if (x <= 400) {
        /* Reduce the input operand to 1.00 <= v <= 5.00. Let y = x + 100,
         * so 100 <= y <= 500. Since y contains the most significant digits
         * of v, y/100 <= v < (y+1)/100 and abs(z - log(v)) < 10**-2. */
        v.exp = -(a_digits - 1);
        t = a_exp + a_digits - 1;
    }
    else {
        /* Reduce the input operand to 0.500 < v <= 0.999. Let y = x + 100,
         * so 500 < y <= 999. Since y contains the most significant digits
         * of v, y/1000 <= v < (y+1)/1000 and abs(z - log(v)) < 10**-2. */
        v.exp = -a_digits;
        t = a_exp + a_digits;
        mpd_set_negative(z);
    }

    mpd_maxcontext(&maxcontext);
    mpd_maxcontext(&varcontext);
    varcontext.round = MPD_ROUND_TRUNC;

    maxprec = ctx->prec + 2;
    if (t == 0 && (x <= 15 || x >= 800)) {
        /* 0.900 <= v <= 1.15: Estimate the magnitude of the logarithm.
         * If ln(v) will underflow, skip the loop. Otherwise, adjust the
         * precision upwards in order to obtain a sufficient number of
         * significant digits.
         *
         *   Case v > 1:
         *      abs((v-1)/10) < abs((v-1)/v) < abs(ln(v)) < abs(v-1)
         *   Case v < 1:
         *      abs(v-1) < abs(ln(v)) < abs((v-1)/v) < abs((v-1)*10)
         */
        int cmp = _mpd_cmp(&v, &one);

        /* Upper bound (assume v > 1): abs(v-1), unrounded */
        _mpd_qsub(&tmp, &v, &one, &maxcontext, &maxcontext.status);
        if (maxcontext.status & MPD_Errors) {
            mpd_seterror(result, MPD_Malloc_error, status);
            goto finish;
        }

        if (cmp < 0) {
            /* v < 1: abs((v-1)*10) */
            tmp.exp += 1;
        }
        if (mpd_adjexp(&tmp) < mpd_etiny(ctx)) {
            /* The upper bound is less than etiny: Underflow to zero */
            _settriple(result, (cmp<0), 1, mpd_etiny(ctx)-1);
            goto finish;
        }
        /* Lower bound: abs((v-1)/10) or abs(v-1) */
        tmp.exp -= 1;
        if (mpd_adjexp(&tmp) < 0) {
            /* Absolute error of the loop: abs(z - log(v)) < 10**-p. If
             * p = ctx->prec+2-adjexp(lower), then the relative error of
             * the result is (using 10**adjexp(x) <= abs(x)):
             *
             *   abs(z - log(v)) / abs(log(v)) < 10**-p / abs(log(v))
             *                                 <= 10**(-ctx->prec-2)
             */
            maxprec = maxprec - mpd_adjexp(&tmp);
        }
    }

    i = ln_schedule_prec(klist, maxprec, 2);
    for (; i >= 0; i--) {
        varcontext.prec = 2*klist[i]+3;
        z->flags ^= MPD_NEG;
        _mpd_qexp(&tmp, z, &varcontext, status);
        z->flags ^= MPD_NEG;

        if (v.digits > varcontext.prec) {
            shift = v.digits - varcontext.prec;
            mpd_qshiftr(&vtmp, &v, shift, status);
            vtmp.exp += shift;
            mpd_qmul(&tmp, &vtmp, &tmp, &varcontext, status);
        }
        else {
            mpd_qmul(&tmp, &v, &tmp, &varcontext, status);
        }

        mpd_qsub(&tmp, &tmp, &one, &maxcontext, status);
        mpd_qadd(z, z, &tmp, &maxcontext, status);
        if (mpd_isspecial(z)) {
            break;
        }
    }

    /*
     * Case t == 0:
     *    t * log(10) == 0, the result does not change and the analysis
     *    above applies. If v < 0.900 or v > 1.15, the relative error is
     *    less than 10**(-ctx.prec-1).
     * Case t != 0:
     *      z := approx(log(v))
     *      y := approx(log(10))
     *      p := maxprec = ctx->prec + 2
     *   Absolute errors:
     *      1) abs(z - log(v)) < 10**-p
     *      2) abs(y - log(10)) < 10**-p
     *   The multiplication is exact, so:
     *      3) abs(t*y - t*log(10)) < t*10**-p
     *   The sum is exact, so:
     *      4) abs((z + t*y) - (log(v) + t*log(10))) < (abs(t) + 1) * 10**-p
     *   Bounds for log(v) and log(10):
     *      5) -7/10 < log(v) < 17/10
     *      6) 23/10 < log(10) < 24/10
     *   Using 4), 5), 6) and t != 0, the relative error is:
     *
     *      7) relerr < ((abs(t) + 1)*10**-p) / abs(log(v) + t*log(10))
     *                < 0.5 * 10**(-p + 1) = 0.5 * 10**(-ctx->prec-1)
     */
    mpd_qln10(&v, maxprec+1, status);
    mpd_qmul_ssize(&tmp, &v, t, &maxcontext, status);
    mpd_qadd(result, &tmp, z, &maxcontext, status);

    ret = 0;

finish:
    *status |= (MPD_Inexact|MPD_Rounded);
    mpd_del(&v);
    mpd_del(&vtmp);
    mpd_del(&tmp);
    return ret;
}

/* ln(a) */
void
mpd_qln(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
        uint32_t *status)
{
    mpd_context_t workctx;
    mpd_ssize_t adjexp, t;

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        if (mpd_isnegative(a)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        mpd_setspecial(result, MPD_POS, MPD_INF);
        return;
    }
    if (mpd_iszerocoeff(a)) {
        mpd_setspecial(result, MPD_NEG, MPD_INF);
        return;
    }
    if (mpd_isnegative(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (_mpd_cmp(a, &one) == 0) {
        _settriple(result, MPD_POS, 0, 0);
        return;
    }
    /*
     * Check if the result will overflow (0 < x, x != 1):
     *   1) log10(x) < 0 iff adjexp(x) < 0
     *   2) 0 < x /\ x <= y ==> adjexp(x) <= adjexp(y)
     *   3) 0 < x /\ x != 1 ==> 2 * abs(log10(x)) < abs(log(x))
     *   4) adjexp(x) <= log10(x) < adjexp(x) + 1
     *
     * Case adjexp(x) >= 0:
     *   5) 2 * adjexp(x) < abs(log(x))
     *   Case adjexp(x) > 0:
     *     6) adjexp(2 * adjexp(x)) <= adjexp(abs(log(x)))
     *   Case adjexp(x) == 0:
     *     mpd_exp_digits(t)-1 == 0 <= emax (the shortcut is not triggered)
     *
     * Case adjexp(x) < 0:
     *   7) 2 * (-adjexp(x) - 1) < abs(log(x))
     *   Case adjexp(x) < -1:
     *     8) adjexp(2 * (-adjexp(x) - 1)) <= adjexp(abs(log(x)))
     *   Case adjexp(x) == -1:
     *     mpd_exp_digits(t)-1 == 0 <= emax (the shortcut is not triggered)
     */
    adjexp = mpd_adjexp(a);
    t = (adjexp < 0) ? -adjexp-1 : adjexp;
    t *= 2;
    if (mpd_exp_digits(t)-1 > ctx->emax) {
        *status |= MPD_Overflow|MPD_Inexact|MPD_Rounded;
        mpd_setspecial(result, (adjexp<0), MPD_INF);
        return;
    }

    mpd_workcontext(&workctx, ctx);
    workctx.round = MPD_ROUND_HALF_EVEN;

    if (ctx->allcr) {
        MPD_NEW_STATIC(hi, 0,0,0,0);
        MPD_NEW_STATIC(lo, 0,0,0,0);
        MPD_NEW_STATIC(ulp, 0,0,0,0);
        MPD_NEW_STATIC(aa, 0,0,0,0);
        uint32_t loop_protect = 0;
        mpd_ssize_t prec;

        if (result == a) {
            if (!mpd_qcopy(&aa, a, status)) {
                mpd_seterror(result, MPD_Malloc_error, status);
                return;
            }
            a = &aa;
        }

        workctx.clamp = 0;
        prec = ctx->prec + 3;
        while (1) {
            uint32_t status_res = 0;
            uint32_t status_hi = 0;
            uint32_t status_lo = 0;
            int shortcut;
            int bounds_eq;
            int subnormal_eq;

            workctx.prec = prec;
            shortcut = _mpd_qln(result, a, &workctx, &status_res);
            if (mpd_isnan(result)) {
                mpd_seterror(result, status_res, status);
                break;
            }
            if (shortcut || mpd_isinfinite(result) || mpd_iszero(result)) {
                *status |= status_res;
                workctx.prec = ctx->prec;
                workctx.clamp = ctx->clamp;
                mpd_qfinalize(result, &workctx, status);
                break;
            }

            _ssettriple(&ulp, MPD_POS, 1,
                        result->exp + result->digits-workctx.prec);

            workctx.prec = ctx->prec;
            mpd_qadd(&hi, result, &ulp, &workctx, &status_hi);
            mpd_qsub(&lo, result, &ulp, &workctx, &status_lo);
            if (mpd_isnan(&hi) || mpd_isnan(&lo)) {
                mpd_seterror(result, status_hi|status_lo, status);
                break;
            }
            subnormal_eq = (status_hi&MPD_Subnormal) == (status_lo&MPD_Subnormal);
            bounds_eq = mpd_qcmp(&hi, &lo, status) == 0;
            if (bounds_eq && ++loop_protect > 5) {
                /* If the bounds are equal, the result is always correctly rounded.

                   Resolving the subnormal status can take more iterations (around
                   three) in extremely rare cases. 'hi' and 'lo' are so close that
                   subnormal/underflow is largely cosmetic, so allow a maximum of
                   five additional iterations. */
                subnormal_eq = 1; /* GCOV_NOT_REACHED */
            }

            if (subnormal_eq && bounds_eq) {
                *status |= status_lo;
                workctx.clamp = ctx->clamp;
                mpd_qfinalize(result, &workctx, status);
                break;
            }

            if (subnormal_eq) {
                prec += MPD_RDIGITS;
            }
            else {
                prec *= 2;
            }

            if (prec > MPD_MAX_PREC) {
                mpd_seterror(result, MPD_Invalid_operation, status);
                break;
            }
        }
        mpd_del(&hi);
        mpd_del(&lo);
        mpd_del(&ulp);
        mpd_del(&aa);
    }
    else {
        _mpd_qln(result, a, &workctx, status);
        mpd_check_underflow(result, &workctx, status);
        mpd_qfinalize(result, &workctx, status);
    }
}

/*
 * Internal log10() function that does not check for specials, zero or one.
 * Case SKIP_FINALIZE:
 *   Relative error: abs(result - log10(a)) < 0.1 * 10**-prec * abs(log10(a))
 * Case DO_FINALIZE:
 *   Ulp error: abs(result - log10(a)) < ulp(log10(a))
 */
enum {SKIP_FINALIZE, DO_FINALIZE};
static int
_mpd_qlog10(int action, mpd_t *result, const mpd_t *a,
            const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_STATIC(ln10,0,0,0,0);
    int ret;

    mpd_maxcontext(&workctx);
    workctx.prec = ctx->prec + 3;
    /* relative error: 0.1 * 10**(-p-3). The specific underflow shortcut
     * in _mpd_qln() does not change the final result. */
    ret = _mpd_qln(result, a, &workctx, status);
    /* relative error: 5 * 10**(-p-3) */
    mpd_qln10(&ln10, workctx.prec, status);

    if (action == DO_FINALIZE) {
        mpd_workcontext(&workctx, ctx);
        workctx.round = MPD_ROUND_HALF_EVEN;
    }
    /* SKIP_FINALIZE: relative error: 5 * 10**(-p-3) */
    _mpd_qdiv(NO_IDEAL_EXP, result, result, &ln10, &workctx, status);

    mpd_del(&ln10);
    return ret;
}

/* log10(a) */
void
mpd_qlog10(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
           uint32_t *status)
{
    mpd_context_t workctx;
    mpd_ssize_t adjexp, t;

    mpd_workcontext(&workctx, ctx);
    workctx.round = MPD_ROUND_HALF_EVEN;

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        if (mpd_isnegative(a)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        mpd_setspecial(result, MPD_POS, MPD_INF);
        return;
    }
    if (mpd_iszerocoeff(a)) {
        mpd_setspecial(result, MPD_NEG, MPD_INF);
        return;
    }
    if (mpd_isnegative(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mpd_coeff_ispow10(a)) {
        uint8_t sign = 0;
        adjexp = mpd_adjexp(a);
        if (adjexp < 0) {
            sign = 1;
            adjexp = -adjexp;
        }
        _settriple(result, sign, adjexp, 0);
        mpd_qfinalize(result, &workctx, status);
        return;
    }
    /*
     * Check if the result will overflow (0 < x, x != 1):
     *   1) log10(x) < 0 iff adjexp(x) < 0
     *   2) 0 < x /\ x <= y ==> adjexp(x) <= adjexp(y)
     *   3) adjexp(x) <= log10(x) < adjexp(x) + 1
     *
     * Case adjexp(x) >= 0:
     *   4) adjexp(x) <= abs(log10(x))
     *   Case adjexp(x) > 0:
     *     5) adjexp(adjexp(x)) <= adjexp(abs(log10(x)))
     *   Case adjexp(x) == 0:
     *     mpd_exp_digits(t)-1 == 0 <= emax (the shortcut is not triggered)
     *
     * Case adjexp(x) < 0:
     *   6) -adjexp(x) - 1 < abs(log10(x))
     *   Case adjexp(x) < -1:
     *     7) adjexp(-adjexp(x) - 1) <= adjexp(abs(log(x)))
     *   Case adjexp(x) == -1:
     *     mpd_exp_digits(t)-1 == 0 <= emax (the shortcut is not triggered)
     */
    adjexp = mpd_adjexp(a);
    t = (adjexp < 0) ? -adjexp-1 : adjexp;
    if (mpd_exp_digits(t)-1 > ctx->emax) {
        *status |= MPD_Overflow|MPD_Inexact|MPD_Rounded;
        mpd_setspecial(result, (adjexp<0), MPD_INF);
        return;
    }

    if (ctx->allcr) {
        MPD_NEW_STATIC(hi, 0,0,0,0);
        MPD_NEW_STATIC(lo, 0,0,0,0);
        MPD_NEW_STATIC(ulp, 0,0,0,0);
        MPD_NEW_STATIC(aa, 0,0,0,0);
        uint32_t loop_protect = 0;
        mpd_ssize_t prec;

        if (result == a) {
            if (!mpd_qcopy(&aa, a, status)) {
                mpd_seterror(result, MPD_Malloc_error, status);
                return;
            }
            a = &aa;
        }

        workctx.clamp = 0;
        prec = ctx->prec + 3;
        while (1) {
            uint32_t status_res = 0;
            uint32_t status_hi = 0;
            uint32_t status_lo = 0;
            int shortcut;
            int bounds_eq;
            int subnormal_eq;

            workctx.prec = prec;
            shortcut = _mpd_qlog10(SKIP_FINALIZE, result, a, &workctx, &status_res);
            if (mpd_isnan(result)) {
                mpd_seterror(result, status_res, status);
                break;
            }
            if (shortcut || mpd_isinfinite(result) || mpd_iszero(result)) {
                *status |= status_res;
                workctx.prec = ctx->prec;
                workctx.clamp = ctx->clamp;
                mpd_qfinalize(result, &workctx, status);
                break;
            }

            _ssettriple(&ulp, MPD_POS, 1,
                        result->exp + result->digits-workctx.prec);

            workctx.prec = ctx->prec;
            mpd_qadd(&hi, result, &ulp, &workctx, &status_hi);
            mpd_qsub(&lo, result, &ulp, &workctx, &status_lo);
            if (mpd_isnan(&hi) || mpd_isnan(&lo)) {
                mpd_seterror(result, status_hi|status_lo, status);
                break;
            }
            subnormal_eq = (status_hi&MPD_Subnormal) == (status_lo&MPD_Subnormal);
            bounds_eq = mpd_qcmp(&hi, &lo, status) == 0;
            if (bounds_eq && ++loop_protect > 5) {
                /* If the bounds are equal, the result is always correctly rounded.

                   Resolving the subnormal status can take more iterations (around
                   three) in extremely rare cases. 'hi' and 'lo' are so close that
                   subnormal/underflow is largely cosmetic, so allow a maximum of
                   five additional iterations. */
                subnormal_eq = 1; /* GCOV_NOT_REACHED */
            }

            if (subnormal_eq && bounds_eq) {
                *status |= status_lo;
                workctx.clamp = ctx->clamp;
                mpd_qfinalize(result, &workctx, status);
                break;
            }

            if (subnormal_eq) {
                prec += MPD_RDIGITS;
            }
            else {
                prec *= 2;
            }

            if (prec > MPD_MAX_PREC) {
                mpd_seterror(result, MPD_Invalid_operation, status);
                break;
            }
        }
        mpd_del(&hi);
        mpd_del(&lo);
        mpd_del(&ulp);
        mpd_del(&aa);
    }
    else {
        _mpd_qlog10(DO_FINALIZE, result, a, &workctx, status);
        mpd_check_underflow(result, &workctx, status);
    }
}

/*
 * Maximum of the two operands. Attention: If one operand is a quiet NaN and the
 * other is numeric, the numeric operand is returned. This may not be what one
 * expects.
 */
void
mpd_qmax(mpd_t *result, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_isqnan(a) && !mpd_isnan(b)) {
        mpd_qcopy(result, b, status);
    }
    else if (mpd_isqnan(b) && !mpd_isnan(a)) {
        mpd_qcopy(result, a, status);
    }
    else if (mpd_qcheck_nans(result, a, b, ctx, status)) {
        return;
    }
    else {
        c = _mpd_cmp(a, b);
        if (c == 0) {
            c = _mpd_cmp_numequal(a, b);
        }

        if (c < 0) {
            mpd_qcopy(result, b, status);
        }
        else {
            mpd_qcopy(result, a, status);
        }
    }

    mpd_qfinalize(result, ctx, status);
}

/*
 * Maximum magnitude: Same as mpd_max(), but compares the operands with their
 * sign ignored.
 */
void
mpd_qmax_mag(mpd_t *result, const mpd_t *a, const mpd_t *b,
             const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_isqnan(a) && !mpd_isnan(b)) {
        mpd_qcopy(result, b, status);
    }
    else if (mpd_isqnan(b) && !mpd_isnan(a)) {
        mpd_qcopy(result, a, status);
    }
    else if (mpd_qcheck_nans(result, a, b, ctx, status)) {
        return;
    }
    else {
        c = _mpd_cmp_abs(a, b);
        if (c == 0) {
            c = _mpd_cmp_numequal(a, b);
        }

        if (c < 0) {
            mpd_qcopy(result, b, status);
        }
        else {
            mpd_qcopy(result, a, status);
        }
    }

    mpd_qfinalize(result, ctx, status);
}

/*
 * Minimum of the two operands. Attention: If one operand is a quiet NaN and the
 * other is numeric, the numeric operand is returned. This may not be what one
 * expects.
 */
void
mpd_qmin(mpd_t *result, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_isqnan(a) && !mpd_isnan(b)) {
        mpd_qcopy(result, b, status);
    }
    else if (mpd_isqnan(b) && !mpd_isnan(a)) {
        mpd_qcopy(result, a, status);
    }
    else if (mpd_qcheck_nans(result, a, b, ctx, status)) {
        return;
    }
    else {
        c = _mpd_cmp(a, b);
        if (c == 0) {
            c = _mpd_cmp_numequal(a, b);
        }

        if (c < 0) {
            mpd_qcopy(result, a, status);
        }
        else {
            mpd_qcopy(result, b, status);
        }
    }

    mpd_qfinalize(result, ctx, status);
}

/*
 * Minimum magnitude: Same as mpd_min(), but compares the operands with their
 * sign ignored.
 */
void
mpd_qmin_mag(mpd_t *result, const mpd_t *a, const mpd_t *b,
             const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_isqnan(a) && !mpd_isnan(b)) {
        mpd_qcopy(result, b, status);
    }
    else if (mpd_isqnan(b) && !mpd_isnan(a)) {
        mpd_qcopy(result, a, status);
    }
    else if (mpd_qcheck_nans(result, a, b, ctx, status)) {
        return;
    }
    else {
        c = _mpd_cmp_abs(a, b);
        if (c == 0) {
            c = _mpd_cmp_numequal(a, b);
        }

        if (c < 0) {
            mpd_qcopy(result, a, status);
        }
        else {
            mpd_qcopy(result, b, status);
        }
    }

    mpd_qfinalize(result, ctx, status);
}

/* Minimum space needed for the result array in _karatsuba_rec(). */
static inline mpd_size_t
_kmul_resultsize(mpd_size_t la, mpd_size_t lb)
{
    mpd_size_t n, m;

    n = add_size_t(la, lb);
    n = add_size_t(n, 1);

    m = (la+1)/2 + 1;
    m = mul_size_t(m, 3);

    return (m > n) ? m : n;
}

/* Work space needed in _karatsuba_rec(). lim >= 4 */
static inline mpd_size_t
_kmul_worksize(mpd_size_t n, mpd_size_t lim)
{
    mpd_size_t m;

    if (n <= lim) {
        return 0;
    }

    m = (n+1)/2 + 1;

    return add_size_t(mul_size_t(m, 2), _kmul_worksize(m, lim));
}


#define MPD_KARATSUBA_BASECASE 16  /* must be >= 4 */

/*
 * Add the product of a and b to c.
 * c must be _kmul_resultsize(la, lb) in size.
 * w is used as a work array and must be _kmul_worksize(a, lim) in size.
 * Roman E. Maeder, Storage Allocation for the Karatsuba Integer Multiplication
 * Algorithm. In "Design and implementation of symbolic computation systems",
 * Springer, 1993, ISBN 354057235X, 9783540572350.
 */
static void
_karatsuba_rec(mpd_uint_t *c, const mpd_uint_t *a, const mpd_uint_t *b,
               mpd_uint_t *w, mpd_size_t la, mpd_size_t lb)
{
    mpd_size_t m, lt;

    assert(la >= lb && lb > 0);
    assert(la <= MPD_KARATSUBA_BASECASE || w != NULL);

    if (la <= MPD_KARATSUBA_BASECASE) {
        _mpd_basemul(c, a, b, la, lb);
        return;
    }

    m = (la+1)/2;  /* ceil(la/2) */

    /* lb <= m < la */
    if (lb <= m) {

        /* lb can now be larger than la-m */
        if (lb > la-m) {
            lt = lb + lb + 1;       /* space needed for result array */
            mpd_uint_zero(w, lt);   /* clear result array */
            _karatsuba_rec(w, b, a+m, w+lt, lb, la-m); /* b*ah */
        }
        else {
            lt = (la-m) + (la-m) + 1;  /* space needed for result array */
            mpd_uint_zero(w, lt);      /* clear result array */
            _karatsuba_rec(w, a+m, b, w+lt, la-m, lb); /* ah*b */
        }
        _mpd_baseaddto(c+m, w, (la-m)+lb);      /* add ah*b*B**m */

        lt = m + m + 1;         /* space needed for the result array */
        mpd_uint_zero(w, lt);   /* clear result array */
        _karatsuba_rec(w, a, b, w+lt, m, lb);  /* al*b */
        _mpd_baseaddto(c, w, m+lb);    /* add al*b */

        return;
    }

    /* la >= lb > m */
    memcpy(w, a, m * sizeof *w);
    w[m] = 0;
    _mpd_baseaddto(w, a+m, la-m);

    memcpy(w+(m+1), b, m * sizeof *w);
    w[m+1+m] = 0;
    _mpd_baseaddto(w+(m+1), b+m, lb-m);

    _karatsuba_rec(c+m, w, w+(m+1), w+2*(m+1), m+1, m+1);

    lt = (la-m) + (la-m) + 1;
    mpd_uint_zero(w, lt);

    _karatsuba_rec(w, a+m, b+m, w+lt, la-m, lb-m);

    _mpd_baseaddto(c+2*m, w, (la-m) + (lb-m));
    _mpd_basesubfrom(c+m, w, (la-m) + (lb-m));

    lt = m + m + 1;
    mpd_uint_zero(w, lt);

    _karatsuba_rec(w, a, b, w+lt, m, m);
    _mpd_baseaddto(c, w, m+m);
    _mpd_basesubfrom(c+m, w, m+m);

    return;
}

/*
 * Multiply u and v, using Karatsuba multiplication. Returns a pointer
 * to the result or NULL in case of failure (malloc error).
 * Conditions: ulen >= vlen, ulen >= 4
 */
static mpd_uint_t *
_mpd_kmul(const mpd_uint_t *u, const mpd_uint_t *v,
          mpd_size_t ulen, mpd_size_t vlen,
          mpd_size_t *rsize)
{
    mpd_uint_t *result = NULL, *w = NULL;
    mpd_size_t m;

    assert(ulen >= 4);
    assert(ulen >= vlen);

    *rsize = _kmul_resultsize(ulen, vlen);
    if ((result = mpd_calloc(*rsize, sizeof *result)) == NULL) {
        return NULL;
    }

    m = _kmul_worksize(ulen, MPD_KARATSUBA_BASECASE);
    if (m && ((w = mpd_calloc(m, sizeof *w)) == NULL)) {
        mpd_free(result);
        return NULL;
    }

    _karatsuba_rec(result, u, v, w, ulen, vlen);


    if (w) mpd_free(w);
    return result;
}


/*
 * Determine the minimum length for the number theoretic transform. Valid
 * transform lengths are 2**n or 3*2**n, where 2**n <= MPD_MAXTRANSFORM_2N.
 * The function finds the shortest length m such that rsize <= m.
 */
static inline mpd_size_t
_mpd_get_transform_len(mpd_size_t rsize)
{
    mpd_size_t log2rsize;
    mpd_size_t x, step;

    assert(rsize >= 4);
    log2rsize = mpd_bsr(rsize);

    if (rsize <= 1024) {
        /* 2**n is faster in this range. */
        x = ((mpd_size_t)1)<<log2rsize;
        return (rsize == x) ? x : x<<1;
    }
    else if (rsize <= MPD_MAXTRANSFORM_2N) {
        x = ((mpd_size_t)1)<<log2rsize;
        if (rsize == x) return x;
        step = x>>1;
        x += step;
        return (rsize <= x) ? x : x + step;
    }
    else if (rsize <= MPD_MAXTRANSFORM_2N+MPD_MAXTRANSFORM_2N/2) {
        return MPD_MAXTRANSFORM_2N+MPD_MAXTRANSFORM_2N/2;
    }
    else if (rsize <= 3*MPD_MAXTRANSFORM_2N) {
        return 3*MPD_MAXTRANSFORM_2N;
    }
    else {
        return MPD_SIZE_MAX;
    }
}

#ifdef PPRO
#ifndef _MSC_VER
static inline unsigned short
_mpd_get_control87(void)
{
    unsigned short cw;

    __asm__ __volatile__ ("fnstcw %0" : "=m" (cw));
    return cw;
}

static inline void
_mpd_set_control87(unsigned short cw)
{
    __asm__ __volatile__ ("fldcw %0" : : "m" (cw));
}
#endif

static unsigned int
mpd_set_fenv(void)
{
    unsigned int cw;
#ifdef _MSC_VER
    unsigned int flags =
        _EM_INVALID|_EM_DENORMAL|_EM_ZERODIVIDE|_EM_OVERFLOW|
        _EM_UNDERFLOW|_EM_INEXACT|_RC_CHOP|_PC_64;
    unsigned int mask = _MCW_EM|_MCW_RC|_MCW_PC;
    unsigned int dummy;

    __control87_2(0, 0, &cw, NULL);
    __control87_2(flags, mask, &dummy, NULL);
#else
    cw = _mpd_get_control87();
    _mpd_set_control87(cw|0xF3F);
#endif
    return cw;
}

static void
mpd_restore_fenv(unsigned int cw)
{
#ifdef _MSC_VER
    unsigned int mask = _MCW_EM|_MCW_RC|_MCW_PC;
    unsigned int dummy;

    __control87_2(cw, mask, &dummy, NULL);
#else
    _mpd_set_control87((unsigned short)cw);
#endif
}
#endif /* PPRO */

/*
 * Multiply u and v, using the fast number theoretic transform. Returns
 * a pointer to the result or NULL in case of failure (malloc error).
 */
static mpd_uint_t *
_mpd_fntmul(const mpd_uint_t *u, const mpd_uint_t *v,
            mpd_size_t ulen, mpd_size_t vlen,
            mpd_size_t *rsize)
{
    mpd_uint_t *c1 = NULL, *c2 = NULL, *c3 = NULL, *vtmp = NULL;
    mpd_size_t n;

#ifdef PPRO
    unsigned int cw;
    cw = mpd_set_fenv();
#endif

    *rsize = add_size_t(ulen, vlen);
    if ((n = _mpd_get_transform_len(*rsize)) == MPD_SIZE_MAX) {
        goto malloc_error;
    }

    if ((c1 = mpd_calloc(n, sizeof *c1)) == NULL) {
        goto malloc_error;
    }
    if ((c2 = mpd_calloc(n, sizeof *c2)) == NULL) {
        goto malloc_error;
    }
    if ((c3 = mpd_calloc(n, sizeof *c3)) == NULL) {
        goto malloc_error;
    }

    memcpy(c1, u, ulen * (sizeof *c1));
    memcpy(c2, u, ulen * (sizeof *c2));
    memcpy(c3, u, ulen * (sizeof *c3));

    if (u == v) {
        if (!fnt_autoconvolute(c1, n, P1) ||
            !fnt_autoconvolute(c2, n, P2) ||
            !fnt_autoconvolute(c3, n, P3)) {
            goto malloc_error;
        }
    }
    else {
        if ((vtmp = mpd_calloc(n, sizeof *vtmp)) == NULL) {
            goto malloc_error;
        }

        memcpy(vtmp, v, vlen * (sizeof *vtmp));
        if (!fnt_convolute(c1, vtmp, n, P1)) {
            mpd_free(vtmp);
            goto malloc_error;
        }

        memcpy(vtmp, v, vlen * (sizeof *vtmp));
        mpd_uint_zero(vtmp+vlen, n-vlen);
        if (!fnt_convolute(c2, vtmp, n, P2)) {
            mpd_free(vtmp);
            goto malloc_error;
        }

        memcpy(vtmp, v, vlen * (sizeof *vtmp));
        mpd_uint_zero(vtmp+vlen, n-vlen);
        if (!fnt_convolute(c3, vtmp, n, P3)) {
            mpd_free(vtmp);
            goto malloc_error;
        }

        mpd_free(vtmp);
    }

    crt3(c1, c2, c3, *rsize);

out:
#ifdef PPRO
    mpd_restore_fenv(cw);
#endif
    if (c2) mpd_free(c2);
    if (c3) mpd_free(c3);
    return c1;

malloc_error:
    if (c1) mpd_free(c1);
    c1 = NULL;
    goto out;
}


/*
 * Karatsuba multiplication with FNT/basemul as the base case.
 */
static int
_karatsuba_rec_fnt(mpd_uint_t *c, const mpd_uint_t *a, const mpd_uint_t *b,
                   mpd_uint_t *w, mpd_size_t la, mpd_size_t lb)
{
    mpd_size_t m, lt;

    assert(la >= lb && lb > 0);
    assert(la <= 3*(MPD_MAXTRANSFORM_2N/2) || w != NULL);

    if (la <= 3*(MPD_MAXTRANSFORM_2N/2)) {

        if (lb <= 192) {
            _mpd_basemul(c, b, a, lb, la);
        }
        else {
            mpd_uint_t *result;
            mpd_size_t dummy;

            if ((result = _mpd_fntmul(a, b, la, lb, &dummy)) == NULL) {
                return 0;
            }
            memcpy(c, result, (la+lb) * (sizeof *result));
            mpd_free(result);
        }
        return 1;
    }

    m = (la+1)/2;  /* ceil(la/2) */

    /* lb <= m < la */
    if (lb <= m) {

        /* lb can now be larger than la-m */
        if (lb > la-m) {
            lt = lb + lb + 1;       /* space needed for result array */
            mpd_uint_zero(w, lt);   /* clear result array */
            if (!_karatsuba_rec_fnt(w, b, a+m, w+lt, lb, la-m)) { /* b*ah */
                return 0; /* GCOV_UNLIKELY */
            }
        }
        else {
            lt = (la-m) + (la-m) + 1;  /* space needed for result array */
            mpd_uint_zero(w, lt);      /* clear result array */
            if (!_karatsuba_rec_fnt(w, a+m, b, w+lt, la-m, lb)) { /* ah*b */
                return 0; /* GCOV_UNLIKELY */
            }
        }
        _mpd_baseaddto(c+m, w, (la-m)+lb); /* add ah*b*B**m */

        lt = m + m + 1;         /* space needed for the result array */
        mpd_uint_zero(w, lt);   /* clear result array */
        if (!_karatsuba_rec_fnt(w, a, b, w+lt, m, lb)) {  /* al*b */
            return 0; /* GCOV_UNLIKELY */
        }
        _mpd_baseaddto(c, w, m+lb);       /* add al*b */

        return 1;
    }

    /* la >= lb > m */
    memcpy(w, a, m * sizeof *w);
    w[m] = 0;
    _mpd_baseaddto(w, a+m, la-m);

    memcpy(w+(m+1), b, m * sizeof *w);
    w[m+1+m] = 0;
    _mpd_baseaddto(w+(m+1), b+m, lb-m);

    if (!_karatsuba_rec_fnt(c+m, w, w+(m+1), w+2*(m+1), m+1, m+1)) {
        return 0; /* GCOV_UNLIKELY */
    }

    lt = (la-m) + (la-m) + 1;
    mpd_uint_zero(w, lt);

    if (!_karatsuba_rec_fnt(w, a+m, b+m, w+lt, la-m, lb-m)) {
        return 0; /* GCOV_UNLIKELY */
    }

    _mpd_baseaddto(c+2*m, w, (la-m) + (lb-m));
    _mpd_basesubfrom(c+m, w, (la-m) + (lb-m));

    lt = m + m + 1;
    mpd_uint_zero(w, lt);

    if (!_karatsuba_rec_fnt(w, a, b, w+lt, m, m)) {
        return 0; /* GCOV_UNLIKELY */
    }
    _mpd_baseaddto(c, w, m+m);
    _mpd_basesubfrom(c+m, w, m+m);

    return 1;
}

/*
 * Multiply u and v, using Karatsuba multiplication with the FNT as the
 * base case. Returns a pointer to the result or NULL in case of failure
 * (malloc error). Conditions: ulen >= vlen, ulen >= 4.
 */
static mpd_uint_t *
_mpd_kmul_fnt(const mpd_uint_t *u, const mpd_uint_t *v,
              mpd_size_t ulen, mpd_size_t vlen,
              mpd_size_t *rsize)
{
    mpd_uint_t *result = NULL, *w = NULL;
    mpd_size_t m;

    assert(ulen >= 4);
    assert(ulen >= vlen);

    *rsize = _kmul_resultsize(ulen, vlen);
    if ((result = mpd_calloc(*rsize, sizeof *result)) == NULL) {
        return NULL;
    }

    m = _kmul_worksize(ulen, 3*(MPD_MAXTRANSFORM_2N/2));
    if (m && ((w = mpd_calloc(m, sizeof *w)) == NULL)) {
        mpd_free(result); /* GCOV_UNLIKELY */
        return NULL; /* GCOV_UNLIKELY */
    }

    if (!_karatsuba_rec_fnt(result, u, v, w, ulen, vlen)) {
        mpd_free(result);
        result = NULL;
    }


    if (w) mpd_free(w);
    return result;
}


/* Deal with the special cases of multiplying infinities. */
static void
_mpd_qmul_inf(mpd_t *result, const mpd_t *a, const mpd_t *b, uint32_t *status)
{
    if (mpd_isinfinite(a)) {
        if (mpd_iszero(b)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
        }
        else {
            mpd_setspecial(result, mpd_sign(a)^mpd_sign(b), MPD_INF);
        }
        return;
    }
    assert(mpd_isinfinite(b));
    if (mpd_iszero(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
    }
    else {
        mpd_setspecial(result, mpd_sign(a)^mpd_sign(b), MPD_INF);
    }
}

/*
 * Internal function: Multiply a and b. _mpd_qmul deals with specials but
 * does NOT finalize the result. This is for use in mpd_fma().
 */
static inline void
_mpd_qmul(mpd_t *result, const mpd_t *a, const mpd_t *b,
          const mpd_context_t *ctx, uint32_t *status)
{
    const mpd_t *big = a, *small = b;
    mpd_uint_t *rdata = NULL;
    mpd_uint_t rbuf[MPD_MINALLOC_MAX];
    mpd_size_t rsize, i;


    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
        _mpd_qmul_inf(result, a, b, status);
        return;
    }

    if (small->len > big->len) {
        _mpd_ptrswap(&big, &small);
    }

    rsize = big->len + small->len;

    if (big->len == 1) {
        _mpd_singlemul(result->data, big->data[0], small->data[0]);
        goto finish;
    }
    if (rsize <= (mpd_size_t)MPD_MINALLOC_MAX) {
        if (big->len == 2) {
            _mpd_mul_2_le2(rbuf, big->data, small->data, small->len);
        }
        else {
            mpd_uint_zero(rbuf, rsize);
            if (small->len == 1) {
                _mpd_shortmul(rbuf, big->data, big->len, small->data[0]);
            }
            else {
                _mpd_basemul(rbuf, small->data, big->data, small->len, big->len);
            }
        }
        if (!mpd_qresize(result, rsize, status)) {
            return;
        }
        for(i = 0; i < rsize; i++) {
            result->data[i] = rbuf[i];
        }
        goto finish;
    }


    if (small->len <= 256) {
        rdata = mpd_calloc(rsize, sizeof *rdata);
        if (rdata != NULL) {
            if (small->len == 1) {
                _mpd_shortmul(rdata, big->data, big->len, small->data[0]);
            }
            else {
                _mpd_basemul(rdata, small->data, big->data, small->len, big->len);
            }
        }
    }
    else if (rsize <= 1024) {
        rdata = _mpd_kmul(big->data, small->data, big->len, small->len, &rsize);
    }
    else if (rsize <= 3*MPD_MAXTRANSFORM_2N) {
        rdata = _mpd_fntmul(big->data, small->data, big->len, small->len, &rsize);
    }
    else {
        rdata = _mpd_kmul_fnt(big->data, small->data, big->len, small->len, &rsize);
    }

    if (rdata == NULL) {
        mpd_seterror(result, MPD_Malloc_error, status);
        return;
    }

    if (mpd_isdynamic_data(result)) {
        mpd_free(result->data);
    }
    result->data = rdata;
    result->alloc = rsize;
    mpd_set_dynamic_data(result);


finish:
    mpd_set_flags(result, mpd_sign(a)^mpd_sign(b));
    result->exp = big->exp + small->exp;
    result->len = _mpd_real_size(result->data, rsize);
    /* resize to smaller cannot fail */
    mpd_qresize(result, result->len, status);
    mpd_setdigits(result);
}

/* Multiply a and b. */
void
mpd_qmul(mpd_t *result, const mpd_t *a, const mpd_t *b,
         const mpd_context_t *ctx, uint32_t *status)
{
    _mpd_qmul(result, a, b, ctx, status);
    mpd_qfinalize(result, ctx, status);
}

/* Multiply a and b. Set NaN/Invalid_operation if the result is inexact. */
static void
_mpd_qmul_exact(mpd_t *result, const mpd_t *a, const mpd_t *b,
                const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;

    mpd_qmul(result, a, b, ctx, &workstatus);
    *status |= workstatus;
    if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
    }
}

/* Multiply decimal and mpd_ssize_t. */
void
mpd_qmul_ssize(mpd_t *result, const mpd_t *a, mpd_ssize_t b,
               const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_ssize(&bb, b, &maxcontext, status);
    mpd_qmul(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Multiply decimal and mpd_uint_t. */
void
mpd_qmul_uint(mpd_t *result, const mpd_t *a, mpd_uint_t b,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qsset_uint(&bb, b, &maxcontext, status);
    mpd_qmul(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

void
mpd_qmul_i32(mpd_t *result, const mpd_t *a, int32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qmul_ssize(result, a, b, ctx, status);
}

void
mpd_qmul_u32(mpd_t *result, const mpd_t *a, uint32_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qmul_uint(result, a, b, ctx, status);
}

#ifdef CONFIG_64
void
mpd_qmul_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qmul_ssize(result, a, b, ctx, status);
}

void
mpd_qmul_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_qmul_uint(result, a, b, ctx, status);
}
#elif !defined(LEGACY_COMPILER)
/* Multiply decimal and int64_t. */
void
mpd_qmul_i64(mpd_t *result, const mpd_t *a, int64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_i64(&bb, b, &maxcontext, status);
    mpd_qmul(result, a, &bb, ctx, status);
    mpd_del(&bb);
}

/* Multiply decimal and uint64_t. */
void
mpd_qmul_u64(mpd_t *result, const mpd_t *a, uint64_t b,
             const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(bb,0,0,0,0);

    mpd_maxcontext(&maxcontext);
    mpd_qset_u64(&bb, b, &maxcontext, status);
    mpd_qmul(result, a, &bb, ctx, status);
    mpd_del(&bb);
}
#endif

/* Like the minus operator. */
void
mpd_qminus(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
           uint32_t *status)
{
    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
    }

    if (mpd_iszero(a) && ctx->round != MPD_ROUND_FLOOR) {
        mpd_qcopy_abs(result, a, status);
    }
    else {
        mpd_qcopy_negate(result, a, status);
    }

    mpd_qfinalize(result, ctx, status);
}

/* Like the plus operator. */
void
mpd_qplus(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
          uint32_t *status)
{
    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
    }

    if (mpd_iszero(a) && ctx->round != MPD_ROUND_FLOOR) {
        mpd_qcopy_abs(result, a, status);
    }
    else {
        mpd_qcopy(result, a, status);
    }

    mpd_qfinalize(result, ctx, status);
}

/* The largest representable number that is smaller than the operand. */
void
mpd_qnext_minus(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
                uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_CONST(tiny,MPD_POS,mpd_etiny(ctx)-1,1,1,1,1);

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }

        assert(mpd_isinfinite(a));
        if (mpd_isnegative(a)) {
            mpd_qcopy(result, a, status);
            return;
        }
        else {
            mpd_clear_flags(result);
            mpd_qmaxcoeff(result, ctx, status);
            if (mpd_isnan(result)) {
                return;
            }
            result->exp = mpd_etop(ctx);
            return;
        }
    }

    mpd_workcontext(&workctx, ctx);
    workctx.round = MPD_ROUND_FLOOR;

    if (!mpd_qcopy(result, a, status)) {
        return;
    }

    mpd_qfinalize(result, &workctx, &workctx.status);
    if (workctx.status&(MPD_Inexact|MPD_Errors)) {
        *status |= (workctx.status&MPD_Errors);
        return;
    }

    workctx.status = 0;
    mpd_qsub(result, a, &tiny, &workctx, &workctx.status);
    *status |= (workctx.status&MPD_Errors);
}

/* The smallest representable number that is larger than the operand. */
void
mpd_qnext_plus(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
               uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_CONST(tiny,MPD_POS,mpd_etiny(ctx)-1,1,1,1,1);

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }

        assert(mpd_isinfinite(a));
        if (mpd_ispositive(a)) {
            mpd_qcopy(result, a, status);
        }
        else {
            mpd_clear_flags(result);
            mpd_qmaxcoeff(result, ctx, status);
            if (mpd_isnan(result)) {
                return;
            }
            mpd_set_flags(result, MPD_NEG);
            result->exp = mpd_etop(ctx);
        }
        return;
    }

    mpd_workcontext(&workctx, ctx);
    workctx.round = MPD_ROUND_CEILING;

    if (!mpd_qcopy(result, a, status)) {
        return;
    }

    mpd_qfinalize(result, &workctx, &workctx.status);
    if (workctx.status & (MPD_Inexact|MPD_Errors)) {
        *status |= (workctx.status&MPD_Errors);
        return;
    }

    workctx.status = 0;
    mpd_qadd(result, a, &tiny, &workctx, &workctx.status);
    *status |= (workctx.status&MPD_Errors);
}

/*
 * The number closest to the first operand that is in the direction towards
 * the second operand.
 */
void
mpd_qnext_toward(mpd_t *result, const mpd_t *a, const mpd_t *b,
                 const mpd_context_t *ctx, uint32_t *status)
{
    int c;

    if (mpd_qcheck_nans(result, a, b, ctx, status)) {
        return;
    }

    c = _mpd_cmp(a, b);
    if (c == 0) {
        mpd_qcopy_sign(result, a, b, status);
        return;
    }

    if (c < 0) {
        mpd_qnext_plus(result, a, ctx, status);
    }
    else {
        mpd_qnext_minus(result, a, ctx, status);
    }

    if (mpd_isinfinite(result)) {
        *status |= (MPD_Overflow|MPD_Rounded|MPD_Inexact);
    }
    else if (mpd_adjexp(result) < ctx->emin) {
        *status |= (MPD_Underflow|MPD_Subnormal|MPD_Rounded|MPD_Inexact);
        if (mpd_iszero(result)) {
            *status |= MPD_Clamped;
        }
    }
}

/*
 * Internal function: Integer power with mpd_uint_t exponent. The function
 * can fail with MPD_Malloc_error.
 *
 * The error is equal to the error incurred in k-1 multiplications. Assuming
 * the upper bound for the relative error in each operation:
 *
 *   abs(err) = 5 * 10**-prec
 *   result = x**k * (1 + err)**(k-1)
 */
static inline void
_mpd_qpow_uint(mpd_t *result, const mpd_t *base, mpd_uint_t exp,
               uint8_t resultsign, const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_uint_t n;

    if (exp == 0) {
        _settriple(result, resultsign, 1, 0); /* GCOV_NOT_REACHED */
        return; /* GCOV_NOT_REACHED */
    }

    if (!mpd_qcopy(result, base, status)) {
        return;
    }

    n = mpd_bits[mpd_bsr(exp)];
    while (n >>= 1) {
        mpd_qmul(result, result, result, ctx, &workstatus);
        if (exp & n) {
            mpd_qmul(result, result, base, ctx, &workstatus);
        }
        if (mpd_isspecial(result) ||
            (mpd_iszerocoeff(result) && (workstatus & MPD_Clamped))) {
            break;
        }
    }

    *status |= workstatus;
    mpd_set_sign(result, resultsign);
}

/*
 * Internal function: Integer power with mpd_t exponent, tbase and texp
 * are modified!! Function can fail with MPD_Malloc_error.
 *
 * The error is equal to the error incurred in k multiplications. Assuming
 * the upper bound for the relative error in each operation:
 *
 *   abs(err) = 5 * 10**-prec
 *   result = x**k * (1 + err)**k
 */
static inline void
_mpd_qpow_mpd(mpd_t *result, mpd_t *tbase, mpd_t *texp, uint8_t resultsign,
              const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_context_t maxctx;
    MPD_NEW_CONST(two,0,0,1,1,1,2);


    mpd_maxcontext(&maxctx);

    /* resize to smaller cannot fail */
    mpd_qcopy(result, &one, status);

    while (!mpd_iszero(texp)) {
        if (mpd_isodd(texp)) {
            mpd_qmul(result, result, tbase, ctx, &workstatus);
            *status |= workstatus;
            if (mpd_isspecial(result) ||
                (mpd_iszerocoeff(result) && (workstatus & MPD_Clamped))) {
                break;
            }
        }
        mpd_qmul(tbase, tbase, tbase, ctx, &workstatus);
        mpd_qdivint(texp, texp, &two, &maxctx, &workstatus);
        if (mpd_isnan(tbase) || mpd_isnan(texp)) {
            mpd_seterror(result, workstatus&MPD_Errors, status);
            return;
        }
    }
    mpd_set_sign(result, resultsign);
}

/*
 * The power function for integer exponents. Relative error _before_ the
 * final rounding to prec:
 *   abs(result - base**exp) < 0.1 * 10**-prec * abs(base**exp)
 */
static void
_mpd_qpow_int(mpd_t *result, const mpd_t *base, const mpd_t *exp,
              uint8_t resultsign,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_STATIC(tbase,0,0,0,0);
    MPD_NEW_STATIC(texp,0,0,0,0);
    mpd_uint_t n;


    mpd_workcontext(&workctx, ctx);
    workctx.prec += (exp->digits + exp->exp + 2);
    workctx.round = MPD_ROUND_HALF_EVEN;
    workctx.clamp = 0;
    if (mpd_isnegative(exp)) {
        uint32_t workstatus = 0;
        workctx.prec += 1;
        mpd_qdiv(&tbase, &one, base, &workctx, &workstatus);
        *status |= workstatus;
        if (workstatus&MPD_Errors) {
            mpd_setspecial(result, MPD_POS, MPD_NAN);
            goto finish;
        }
    }
    else {
        if (!mpd_qcopy(&tbase, base, status)) {
            mpd_setspecial(result, MPD_POS, MPD_NAN);
            goto finish;
        }
    }

    n = mpd_qabs_uint(exp, &workctx.status);
    if (workctx.status&MPD_Invalid_operation) {
        if (!mpd_qcopy(&texp, exp, status)) {
            mpd_setspecial(result, MPD_POS, MPD_NAN); /* GCOV_UNLIKELY */
            goto finish; /* GCOV_UNLIKELY */
        }
        _mpd_qpow_mpd(result, &tbase, &texp, resultsign, &workctx, status);
    }
    else {
        _mpd_qpow_uint(result, &tbase, n, resultsign, &workctx, status);
    }

    if (mpd_isinfinite(result)) {
        /* for ROUND_DOWN, ROUND_FLOOR, etc. */
        _settriple(result, resultsign, 1, MPD_EXP_INF);
    }

finish:
    mpd_del(&tbase);
    mpd_del(&texp);
    mpd_qfinalize(result, ctx, status);
}

/*
 * If the exponent is infinite and base equals one, the result is one
 * with a coefficient of length prec. Otherwise, result is undefined.
 * Return the value of the comparison against one.
 */
static int
_qcheck_pow_one_inf(mpd_t *result, const mpd_t *base, uint8_t resultsign,
                    const mpd_context_t *ctx, uint32_t *status)
{
    mpd_ssize_t shift;
    int cmp;

    if ((cmp = _mpd_cmp(base, &one)) == 0) {
        shift = ctx->prec-1;
        mpd_qshiftl(result, &one, shift, status);
        result->exp = -shift;
        mpd_set_flags(result, resultsign);
        *status |= (MPD_Inexact|MPD_Rounded);
    }

    return cmp;
}

/*
 * If abs(base) equals one, calculate the correct power of one result.
 * Otherwise, result is undefined. Return the value of the comparison
 * against 1.
 *
 * This is an internal function that does not check for specials.
 */
static int
_qcheck_pow_one(mpd_t *result, const mpd_t *base, const mpd_t *exp,
                uint8_t resultsign,
                const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_ssize_t shift;
    int cmp;

    if ((cmp = _mpd_cmp_abs(base, &one)) == 0) {
        if (_mpd_isint(exp)) {
            if (mpd_isnegative(exp)) {
                _settriple(result, resultsign, 1, 0);
                return 0;
            }
            /* 1.000**3 = 1.000000000 */
            mpd_qmul_ssize(result, exp, -base->exp, ctx, &workstatus);
            if (workstatus&MPD_Errors) {
                *status |= (workstatus&MPD_Errors);
                return 0;
            }
            /* digits-1 after exponentiation */
            shift = mpd_qget_ssize(result, &workstatus);
            /* shift is MPD_SSIZE_MAX if result is too large */
            if (shift > ctx->prec-1) {
                shift = ctx->prec-1;
                *status |= MPD_Rounded;
            }
        }
        else if (mpd_ispositive(base)) {
            shift = ctx->prec-1;
            *status |= (MPD_Inexact|MPD_Rounded);
        }
        else {
            return -2; /* GCOV_NOT_REACHED */
        }
        if (!mpd_qshiftl(result, &one, shift, status)) {
            return 0;
        }
        result->exp = -shift;
        mpd_set_flags(result, resultsign);
    }

    return cmp;
}

/*
 * Detect certain over/underflow of x**y.
 * ACL2 proof: pow-bounds.lisp.
 *
 *   Symbols:
 *
 *     e: EXP_INF or EXP_CLAMP
 *     x: base
 *     y: exponent
 *
 *     omega(e) = log10(abs(e))
 *     zeta(x)  = log10(abs(log10(x)))
 *     theta(y) = log10(abs(y))
 *
 *   Upper and lower bounds:
 *
 *     ub_omega(e) = ceil(log10(abs(e)))
 *     lb_theta(y) = floor(log10(abs(y)))
 *
 *                  | floor(log10(floor(abs(log10(x))))) if x < 1/10 or x >= 10
 *     lb_zeta(x) = | floor(log10(abs(x-1)/10)) if 1/10 <= x < 1
 *                  | floor(log10(abs((x-1)/100))) if 1 < x < 10
 *
 *   ub_omega(e) and lb_theta(y) are obviously upper and lower bounds
 *   for omega(e) and theta(y).
 *
 *   lb_zeta is a lower bound for zeta(x):
 *
 *     x < 1/10 or x >= 10:
 *
 *       abs(log10(x)) >= 1, so the outer log10 is well defined. Since log10
 *       is strictly increasing, the end result is a lower bound.
 *
 *     1/10 <= x < 1:
 *
 *       We use: log10(x) <= (x-1)/log(10)
 *               abs(log10(x)) >= abs(x-1)/log(10)
 *               abs(log10(x)) >= abs(x-1)/10
 *
 *     1 < x < 10:
 *
 *       We use: (x-1)/(x*log(10)) < log10(x)
 *               abs((x-1)/100) < abs(log10(x))
 *
 *       XXX: abs((x-1)/10) would work, need ACL2 proof.
 *
 *
 *   Let (0 < x < 1 and y < 0) or (x > 1 and y > 0).                  (H1)
 *   Let ub_omega(exp_inf) < lb_zeta(x) + lb_theta(y)                 (H2)
 *
 *   Then:
 *       log10(abs(exp_inf)) < log10(abs(log10(x))) + log10(abs(y)).   (1)
 *                   exp_inf < log10(x) * y                            (2)
 *               10**exp_inf < x**y                                    (3)
 *
 *   Let (0 < x < 1 and y > 0) or (x > 1 and y < 0).                  (H3)
 *   Let ub_omega(exp_clamp) < lb_zeta(x) + lb_theta(y)               (H4)
 *
 *   Then:
 *     log10(abs(exp_clamp)) < log10(abs(log10(x))) + log10(abs(y)).   (4)
 *              log10(x) * y < exp_clamp                               (5)
 *                      x**y < 10**exp_clamp                           (6)
 *
 */
static mpd_ssize_t
_lower_bound_zeta(const mpd_t *x, uint32_t *status)
{
    mpd_context_t maxctx;
    MPD_NEW_STATIC(scratch,0,0,0,0);
    mpd_ssize_t t, u;

    t = mpd_adjexp(x);
    if (t > 0) {
        /* x >= 10 -> floor(log10(floor(abs(log10(x))))) */
        return mpd_exp_digits(t) - 1;
    }
    else if (t < -1) {
        /* x < 1/10 -> floor(log10(floor(abs(log10(x))))) */
        return mpd_exp_digits(t+1) - 1;
    }
    else {
        mpd_maxcontext(&maxctx);
        mpd_qsub(&scratch, x, &one, &maxctx, status);
        if (mpd_isspecial(&scratch)) {
            mpd_del(&scratch);
            return MPD_SSIZE_MAX;
        }
        u = mpd_adjexp(&scratch);
        mpd_del(&scratch);

        /* t == -1, 1/10 <= x < 1 -> floor(log10(abs(x-1)/10))
         * t == 0,  1 < x < 10    -> floor(log10(abs(x-1)/100)) */
        return (t == 0) ? u-2 : u-1;
    }
}

/*
 * Detect cases of certain overflow/underflow in the power function.
 * Assumptions: x != 1, y != 0. The proof above is for positive x.
 * If x is negative and y is an odd integer, x**y == -(abs(x)**y),
 * so the analysis does not change.
 */
static int
_qcheck_pow_bounds(mpd_t *result, const mpd_t *x, const mpd_t *y,
                   uint8_t resultsign,
                   const mpd_context_t *ctx, uint32_t *status)
{
    MPD_NEW_SHARED(abs_x, x);
    mpd_ssize_t ub_omega, lb_zeta, lb_theta;
    uint8_t sign;

    mpd_set_positive(&abs_x);

    lb_theta = mpd_adjexp(y);
    lb_zeta = _lower_bound_zeta(&abs_x, status);
    if (lb_zeta == MPD_SSIZE_MAX) {
        mpd_seterror(result, MPD_Malloc_error, status);
        return 1;
    }

    sign = (mpd_adjexp(&abs_x) < 0) ^ mpd_sign(y);
    if (sign == 0) {
        /* (0 < |x| < 1 and y < 0) or (|x| > 1 and y > 0) */
        ub_omega = mpd_exp_digits(ctx->emax);
        if (ub_omega < lb_zeta + lb_theta) {
            _settriple(result, resultsign, 1, MPD_EXP_INF);
            mpd_qfinalize(result, ctx, status);
            return 1;
        }
    }
    else {
        /* (0 < |x| < 1 and y > 0) or (|x| > 1 and y < 0). */
        ub_omega = mpd_exp_digits(mpd_etiny(ctx));
        if (ub_omega < lb_zeta + lb_theta) {
            _settriple(result, resultsign, 1, mpd_etiny(ctx)-1);
            mpd_qfinalize(result, ctx, status);
            return 1;
        }
    }

    return 0;
}

/*
 * TODO: Implement algorithm for computing exact powers from decimal.py.
 * In order to prevent infinite loops, this has to be called before
 * using Ziv's strategy for correct rounding.
 */
/*
static int
_mpd_qpow_exact(mpd_t *result, const mpd_t *base, const mpd_t *exp,
                const mpd_context_t *ctx, uint32_t *status)
{
    return 0;
}
*/

/*
 * The power function for real exponents.
 *   Relative error: abs(result - e**y) < e**y * 1/5 * 10**(-prec - 1)
 */
static void
_mpd_qpow_real(mpd_t *result, const mpd_t *base, const mpd_t *exp,
               const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_STATIC(texp,0,0,0,0);

    if (!mpd_qcopy(&texp, exp, status)) {
        mpd_seterror(result, MPD_Malloc_error, status);
        return;
    }

    mpd_maxcontext(&workctx);
    workctx.prec = (base->digits > ctx->prec) ? base->digits : ctx->prec;
    workctx.prec += (4 + MPD_EXPDIGITS);
    workctx.round = MPD_ROUND_HALF_EVEN;
    workctx.allcr = ctx->allcr;

    /*
     * extra := MPD_EXPDIGITS = MPD_EXP_MAX_T
     * wp := prec + 4 + extra
     * abs(err) < 5 * 10**-wp
     * y := log(base) * exp
     * Calculate:
     *   1)   e**(y * (1 + err)**2) * (1 + err)
     *      = e**y * e**(y * (2*err + err**2)) * (1 + err)
     *        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     * Relative error of the underlined term:
     *   2) abs(e**(y * (2*err + err**2)) - 1)
     * Case abs(y) >= 10**extra:
     *   3) adjexp(y)+1 > log10(abs(y)) >= extra
     *   This triggers the Overflow/Underflow shortcut in _mpd_qexp(),
     *   so no further analysis is necessary.
     * Case abs(y) < 10**extra:
     *   4) abs(y * (2*err + err**2)) < 1/5 * 10**(-prec - 2)
     *   Use (see _mpd_qexp):
     *     5) abs(x) <= 9/10 * 10**-p ==> abs(e**x - 1) < 10**-p
     *   With 2), 4) and 5):
     *     6) abs(e**(y * (2*err + err**2)) - 1) < 10**(-prec - 2)
     *   The complete relative error of 1) is:
     *     7) abs(result - e**y) < e**y * 1/5 * 10**(-prec - 1)
     */
    mpd_qln(result, base, &workctx, &workctx.status);
    mpd_qmul(result, result, &texp, &workctx, &workctx.status);
    mpd_qexp(result, result, &workctx, status);

    mpd_del(&texp);
    *status |= (workctx.status&MPD_Errors);
    *status |= (MPD_Inexact|MPD_Rounded);
}

/* The power function: base**exp */
void
mpd_qpow(mpd_t *result, const mpd_t *base, const mpd_t *exp,
         const mpd_context_t *ctx, uint32_t *status)
{
    uint8_t resultsign = 0;
    int intexp = 0;
    int cmp;

    if (mpd_isspecial(base) || mpd_isspecial(exp)) {
        if (mpd_qcheck_nans(result, base, exp, ctx, status)) {
            return;
        }
    }
    if (mpd_isinteger(exp)) {
        intexp = 1;
        resultsign = mpd_isnegative(base) && mpd_isodd(exp);
    }

    if (mpd_iszero(base)) {
        if (mpd_iszero(exp)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
        }
        else if (mpd_isnegative(exp)) {
            mpd_setspecial(result, resultsign, MPD_INF);
        }
        else {
            _settriple(result, resultsign, 0, 0);
        }
        return;
    }
    if (mpd_isnegative(base)) {
        if (!intexp || mpd_isinfinite(exp)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
    }
    if (mpd_isinfinite(exp)) {
        /* power of one */
        cmp = _qcheck_pow_one_inf(result, base, resultsign, ctx, status);
        if (cmp == 0) {
            return;
        }
        else {
            cmp *= mpd_arith_sign(exp);
            if (cmp < 0) {
                _settriple(result, resultsign, 0, 0);
            }
            else {
                mpd_setspecial(result, resultsign, MPD_INF);
            }
        }
        return;
    }
    if (mpd_isinfinite(base)) {
        if (mpd_iszero(exp)) {
            _settriple(result, resultsign, 1, 0);
        }
        else if (mpd_isnegative(exp)) {
            _settriple(result, resultsign, 0, 0);
        }
        else {
            mpd_setspecial(result, resultsign, MPD_INF);
        }
        return;
    }
    if (mpd_iszero(exp)) {
        _settriple(result, resultsign, 1, 0);
        return;
    }
    if (_qcheck_pow_one(result, base, exp, resultsign, ctx, status) == 0) {
        return;
    }
    if (_qcheck_pow_bounds(result, base, exp, resultsign, ctx, status)) {
        return;
    }

    if (intexp) {
        _mpd_qpow_int(result, base, exp, resultsign, ctx, status);
    }
    else {
        _mpd_qpow_real(result, base, exp, ctx, status);
        if (!mpd_isspecial(result) && _mpd_cmp(result, &one) == 0) {
            mpd_ssize_t shift = ctx->prec-1;
            mpd_qshiftl(result, &one, shift, status);
            result->exp = -shift;
        }
        if (mpd_isinfinite(result)) {
            /* for ROUND_DOWN, ROUND_FLOOR, etc. */
            _settriple(result, MPD_POS, 1, MPD_EXP_INF);
        }
        mpd_qfinalize(result, ctx, status);
    }
}

/*
 * Internal function: Integer powmod with mpd_uint_t exponent, base is modified!
 * Function can fail with MPD_Malloc_error.
 */
static inline void
_mpd_qpowmod_uint(mpd_t *result, mpd_t *base, mpd_uint_t exp,
                  const mpd_t *mod, uint32_t *status)
{
    mpd_context_t maxcontext;

    mpd_maxcontext(&maxcontext);

    /* resize to smaller cannot fail */
    mpd_qcopy(result, &one, status);

    while (exp > 0) {
        if (exp & 1) {
            _mpd_qmul_exact(result, result, base, &maxcontext, status);
            mpd_qrem(result, result, mod, &maxcontext, status);
        }
        _mpd_qmul_exact(base, base, base, &maxcontext, status);
        mpd_qrem(base, base, mod, &maxcontext, status);
        exp >>= 1;
    }
}

/* The powmod function: (base**exp) % mod */
void
mpd_qpowmod(mpd_t *result, const mpd_t *base, const mpd_t *exp,
            const mpd_t *mod,
            const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(tbase,0,0,0,0);
    MPD_NEW_STATIC(texp,0,0,0,0);
    MPD_NEW_STATIC(tmod,0,0,0,0);
    MPD_NEW_STATIC(tmp,0,0,0,0);
    MPD_NEW_CONST(two,0,0,1,1,1,2);
    mpd_ssize_t tbase_exp, texp_exp;
    mpd_ssize_t i;
    mpd_t t;
    mpd_uint_t r;
    uint8_t sign;


    if (mpd_isspecial(base) || mpd_isspecial(exp) || mpd_isspecial(mod)) {
        if (mpd_qcheck_3nans(result, base, exp, mod, ctx, status)) {
            return;
        }
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }


    if (!_mpd_isint(base) || !_mpd_isint(exp) || !_mpd_isint(mod)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mpd_iszerocoeff(mod)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mod->digits+mod->exp > ctx->prec) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    sign = (mpd_isnegative(base)) && (mpd_isodd(exp));
    if (mpd_iszerocoeff(exp)) {
        if (mpd_iszerocoeff(base)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        r = (_mpd_cmp_abs(mod, &one)==0) ? 0 : 1;
        _settriple(result, sign, r, 0);
        return;
    }
    if (mpd_isnegative(exp)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }
    if (mpd_iszerocoeff(base)) {
        _settriple(result, sign, 0, 0);
        return;
    }

    mpd_maxcontext(&maxcontext);

    mpd_qrescale(&tmod, mod, 0, &maxcontext, &maxcontext.status);
    if (maxcontext.status&MPD_Errors) {
        mpd_seterror(result, maxcontext.status&MPD_Errors, status);
        goto out;
    }
    maxcontext.status = 0;
    mpd_set_positive(&tmod);

    mpd_qround_to_int(&tbase, base, &maxcontext, status);
    mpd_set_positive(&tbase);
    tbase_exp = tbase.exp;
    tbase.exp = 0;

    mpd_qround_to_int(&texp, exp, &maxcontext, status);
    texp_exp = texp.exp;
    texp.exp = 0;

    /* base = (base.int % modulo * pow(10, base.exp, modulo)) % modulo */
    mpd_qrem(&tbase, &tbase, &tmod, &maxcontext, status);
    mpd_qshiftl(result, &one, tbase_exp, status);
    mpd_qrem(result, result, &tmod, &maxcontext, status);
    _mpd_qmul_exact(&tbase, &tbase, result, &maxcontext, status);
    mpd_qrem(&tbase, &tbase, &tmod, &maxcontext, status);
    if (mpd_isspecial(&tbase) ||
        mpd_isspecial(&texp) ||
        mpd_isspecial(&tmod)) {
        goto mpd_errors;
    }

    for (i = 0; i < texp_exp; i++) {
        _mpd_qpowmod_uint(&tmp, &tbase, 10, &tmod, status);
        t = tmp;
        tmp = tbase;
        tbase = t;
    }
    if (mpd_isspecial(&tbase)) {
        goto mpd_errors; /* GCOV_UNLIKELY */
    }

    /* resize to smaller cannot fail */
    mpd_qcopy(result, &one, status);
    while (mpd_isfinite(&texp) && !mpd_iszero(&texp)) {
        if (mpd_isodd(&texp)) {
            _mpd_qmul_exact(result, result, &tbase, &maxcontext, status);
            mpd_qrem(result, result, &tmod, &maxcontext, status);
        }
        _mpd_qmul_exact(&tbase, &tbase, &tbase, &maxcontext, status);
        mpd_qrem(&tbase, &tbase, &tmod, &maxcontext, status);
        mpd_qdivint(&texp, &texp, &two, &maxcontext, status);
    }
    if (mpd_isspecial(&texp) || mpd_isspecial(&tbase) ||
        mpd_isspecial(&tmod) || mpd_isspecial(result)) {
        /* MPD_Malloc_error */
        goto mpd_errors;
    }
    else {
        mpd_set_sign(result, sign);
    }

out:
    mpd_del(&tbase);
    mpd_del(&texp);
    mpd_del(&tmod);
    mpd_del(&tmp);
    return;

mpd_errors:
    mpd_setspecial(result, MPD_POS, MPD_NAN);
    goto out;
}

void
mpd_qquantize(mpd_t *result, const mpd_t *a, const mpd_t *b,
              const mpd_context_t *ctx, uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_ssize_t b_exp = b->exp;
    mpd_ssize_t expdiff, shift;
    mpd_uint_t rnd;

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(result, a, b, ctx, status)) {
            return;
        }
        if (mpd_isinfinite(a) && mpd_isinfinite(b)) {
            mpd_qcopy(result, a, status);
            return;
        }
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    if (b->exp > ctx->emax || b->exp < mpd_etiny(ctx)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    if (mpd_iszero(a)) {
        _settriple(result, mpd_sign(a), 0, b->exp);
        mpd_qfinalize(result, ctx, status);
        return;
    }


    expdiff = a->exp - b->exp;
    if (a->digits + expdiff > ctx->prec) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    if (expdiff >= 0) {
        shift = expdiff;
        if (!mpd_qshiftl(result, a, shift, status)) {
            return;
        }
        result->exp = b_exp;
    }
    else {
        /* At this point expdiff < 0 and a->digits+expdiff <= prec,
         * so the shift before an increment will fit in prec. */
        shift = -expdiff;
        rnd = mpd_qshiftr(result, a, shift, status);
        if (rnd == MPD_UINT_MAX) {
            return;
        }
        result->exp = b_exp;
        if (!_mpd_apply_round_fit(result, rnd, ctx, status)) {
            return;
        }
        workstatus |= MPD_Rounded;
        if (rnd) {
            workstatus |= MPD_Inexact;
        }
    }

    if (mpd_adjexp(result) > ctx->emax ||
        mpd_adjexp(result) < mpd_etiny(ctx)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    *status |= workstatus;
    mpd_qfinalize(result, ctx, status);
}

void
mpd_qreduce(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
            uint32_t *status)
{
    mpd_ssize_t shift, maxexp, maxshift;
    uint8_t sign_a = mpd_sign(a);

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        mpd_qcopy(result, a, status);
        return;
    }

    if (!mpd_qcopy(result, a, status)) {
        return;
    }
    mpd_qfinalize(result, ctx, status);
    if (mpd_isspecial(result)) {
        return;
    }
    if (mpd_iszero(result)) {
        _settriple(result, sign_a, 0, 0);
        return;
    }

    shift = mpd_trail_zeros(result);
    maxexp = (ctx->clamp) ? mpd_etop(ctx) : ctx->emax;
    /* After the finalizing above result->exp <= maxexp. */
    maxshift = maxexp - result->exp;
    shift = (shift > maxshift) ? maxshift : shift;

    mpd_qshiftr_inplace(result, shift);
    result->exp += shift;
}

void
mpd_qrem(mpd_t *r, const mpd_t *a, const mpd_t *b, const mpd_context_t *ctx,
         uint32_t *status)
{
    MPD_NEW_STATIC(q,0,0,0,0);

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(r, a, b, ctx, status)) {
            return;
        }
        if (mpd_isinfinite(a)) {
            mpd_seterror(r, MPD_Invalid_operation, status);
            return;
        }
        if (mpd_isinfinite(b)) {
            mpd_qcopy(r, a, status);
            mpd_qfinalize(r, ctx, status);
            return;
        }
        /* debug */
        abort(); /* GCOV_NOT_REACHED */
    }
    if (mpd_iszerocoeff(b)) {
        if (mpd_iszerocoeff(a)) {
            mpd_seterror(r, MPD_Division_undefined, status);
        }
        else {
            mpd_seterror(r, MPD_Invalid_operation, status);
        }
        return;
    }

    _mpd_qdivmod(&q, r, a, b, ctx, status);
    mpd_del(&q);
    mpd_qfinalize(r, ctx, status);
}

void
mpd_qrem_near(mpd_t *r, const mpd_t *a, const mpd_t *b,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_context_t workctx;
    MPD_NEW_STATIC(btmp,0,0,0,0);
    MPD_NEW_STATIC(q,0,0,0,0);
    mpd_ssize_t expdiff, qdigits;
    int cmp, isodd, allnine;

    assert(r != NULL); /* annotation for scan-build */

    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        if (mpd_qcheck_nans(r, a, b, ctx, status)) {
            return;
        }
        if (mpd_isinfinite(a)) {
            mpd_seterror(r, MPD_Invalid_operation, status);
            return;
        }
        if (mpd_isinfinite(b)) {
            mpd_qcopy(r, a, status);
            mpd_qfinalize(r, ctx, status);
            return;
        }
        /* debug */
        abort(); /* GCOV_NOT_REACHED */
    }
    if (mpd_iszerocoeff(b)) {
        if (mpd_iszerocoeff(a)) {
            mpd_seterror(r,  MPD_Division_undefined, status);
        }
        else {
            mpd_seterror(r,  MPD_Invalid_operation, status);
        }
        return;
    }

    if (r == b) {
        if (!mpd_qcopy(&btmp, b, status)) {
            mpd_seterror(r, MPD_Malloc_error, status);
            return;
        }
        b = &btmp;
    }

    _mpd_qdivmod(&q, r, a, b, ctx, status);
    if (mpd_isnan(&q) || mpd_isnan(r)) {
        goto finish;
    }
    if (mpd_iszerocoeff(r)) {
        goto finish;
    }

    expdiff = mpd_adjexp(b) - mpd_adjexp(r);
    if (-1 <= expdiff && expdiff <= 1) {

        allnine = mpd_coeff_isallnine(&q);
        qdigits = q.digits;
        isodd = mpd_isodd(&q);

        mpd_maxcontext(&workctx);
        if (mpd_sign(a) == mpd_sign(b)) {
            /* sign(r) == sign(b) */
            _mpd_qsub(&q, r, b, &workctx, &workctx.status);
        }
        else {
            /* sign(r) != sign(b) */
            _mpd_qadd(&q, r, b, &workctx, &workctx.status);
        }

        if (workctx.status&MPD_Errors) {
            mpd_seterror(r, workctx.status&MPD_Errors, status);
            goto finish;
        }

        cmp = _mpd_cmp_abs(&q, r);
        if (cmp < 0 || (cmp == 0 && isodd)) {
            /* abs(r) > abs(b)/2 or abs(r) == abs(b)/2 and isodd(quotient) */
            if (allnine && qdigits == ctx->prec) {
                /* abs(quotient) + 1 == 10**prec */
                mpd_seterror(r, MPD_Division_impossible, status);
                goto finish;
            }
            mpd_qcopy(r, &q, status);
        }
    }


finish:
    mpd_del(&btmp);
    mpd_del(&q);
    mpd_qfinalize(r, ctx, status);
}

static void
_mpd_qrescale(mpd_t *result, const mpd_t *a, mpd_ssize_t exp,
              const mpd_context_t *ctx, uint32_t *status)
{
    mpd_ssize_t expdiff, shift;
    mpd_uint_t rnd;

    if (mpd_isspecial(a)) {
        mpd_qcopy(result, a, status);
        return;
    }

    if (mpd_iszero(a)) {
        _settriple(result, mpd_sign(a), 0, exp);
        return;
    }

    expdiff = a->exp - exp;
    if (expdiff >= 0) {
        shift = expdiff;
        if (a->digits + shift > MPD_MAX_PREC+1) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        if (!mpd_qshiftl(result, a, shift, status)) {
            return;
        }
        result->exp = exp;
    }
    else {
        shift = -expdiff;
        rnd = mpd_qshiftr(result, a, shift, status);
        if (rnd == MPD_UINT_MAX) {
            return;
        }
        result->exp = exp;
        _mpd_apply_round_excess(result, rnd, ctx, status);
        *status |= MPD_Rounded;
        if (rnd) {
            *status |= MPD_Inexact;
        }
    }

    if (mpd_issubnormal(result, ctx)) {
        *status |= MPD_Subnormal;
    }
}

/*
 * Rescale a number so that it has exponent 'exp'. Does not regard context
 * precision, emax, emin, but uses the rounding mode. Special numbers are
 * quietly copied. Restrictions:
 *
 *     MPD_MIN_ETINY <= exp <= MPD_MAX_EMAX+1
 *     result->digits <= MPD_MAX_PREC+1
 */
void
mpd_qrescale(mpd_t *result, const mpd_t *a, mpd_ssize_t exp,
             const mpd_context_t *ctx, uint32_t *status)
{
    if (exp > MPD_MAX_EMAX+1 || exp < MPD_MIN_ETINY) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    _mpd_qrescale(result, a, exp, ctx, status);
}

/*
 * Same as mpd_qrescale, but with relaxed restrictions. The result of this
 * function should only be used for formatting a number and never as input
 * for other operations.
 *
 *     MPD_MIN_ETINY-MPD_MAX_PREC <= exp <= MPD_MAX_EMAX+1
 *     result->digits <= MPD_MAX_PREC+1
 */
void
mpd_qrescale_fmt(mpd_t *result, const mpd_t *a, mpd_ssize_t exp,
                 const mpd_context_t *ctx, uint32_t *status)
{
    if (exp > MPD_MAX_EMAX+1 || exp < MPD_MIN_ETINY-MPD_MAX_PREC) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    _mpd_qrescale(result, a, exp, ctx, status);
}

/* Round to an integer according to 'action' and ctx->round. */
enum {TO_INT_EXACT, TO_INT_SILENT, TO_INT_TRUNC};
static void
_mpd_qround_to_integral(int action, mpd_t *result, const mpd_t *a,
                        const mpd_context_t *ctx, uint32_t *status)
{
    mpd_uint_t rnd;

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        mpd_qcopy(result, a, status);
        return;
    }
    if (a->exp >= 0) {
        mpd_qcopy(result, a, status);
        return;
    }
    if (mpd_iszerocoeff(a)) {
        _settriple(result, mpd_sign(a), 0, 0);
        return;
    }

    rnd = mpd_qshiftr(result, a, -a->exp, status);
    if (rnd == MPD_UINT_MAX) {
        return;
    }
    result->exp = 0;

    if (action == TO_INT_EXACT || action == TO_INT_SILENT) {
        _mpd_apply_round_excess(result, rnd, ctx, status);
        if (action == TO_INT_EXACT) {
            *status |= MPD_Rounded;
            if (rnd) {
                *status |= MPD_Inexact;
            }
        }
    }
}

void
mpd_qround_to_intx(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
                   uint32_t *status)
{
    (void)_mpd_qround_to_integral(TO_INT_EXACT, result, a, ctx, status);
}

void
mpd_qround_to_int(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
                  uint32_t *status)
{
    (void)_mpd_qround_to_integral(TO_INT_SILENT, result, a, ctx, status);
}

void
mpd_qtrunc(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
           uint32_t *status)
{
    if (mpd_isspecial(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    (void)_mpd_qround_to_integral(TO_INT_TRUNC, result, a, ctx, status);
}

void
mpd_qfloor(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
           uint32_t *status)
{
    mpd_context_t workctx;
    mpd_workcontext(&workctx, ctx);

    if (mpd_isspecial(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    workctx.round = MPD_ROUND_FLOOR;
    (void)_mpd_qround_to_integral(TO_INT_SILENT, result, a,
                                  &workctx, status);
}

void
mpd_qceil(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
          uint32_t *status)
{
    mpd_context_t workctx;
    mpd_workcontext(&workctx, ctx);

    if (mpd_isspecial(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    workctx.round = MPD_ROUND_CEILING;
    (void)_mpd_qround_to_integral(TO_INT_SILENT, result, a,
                                  &workctx, status);
}

int
mpd_same_quantum(const mpd_t *a, const mpd_t *b)
{
    if (mpd_isspecial(a) || mpd_isspecial(b)) {
        return ((mpd_isnan(a) && mpd_isnan(b)) ||
                (mpd_isinfinite(a) && mpd_isinfinite(b)));
    }

    return a->exp == b->exp;
}

/* Schedule the increase in precision for the Newton iteration. */
static inline int
recpr_schedule_prec(mpd_ssize_t klist[MPD_MAX_PREC_LOG2],
                    mpd_ssize_t maxprec, mpd_ssize_t initprec)
{
    mpd_ssize_t k;
    int i;

    assert(maxprec > 0 && initprec > 0);
    if (maxprec <= initprec) return -1;

    i = 0; k = maxprec;
    do {
        k = (k+1) / 2;
        klist[i++] = k;
    } while (k > initprec);

    return i-1;
}

/*
 * Initial approximation for the reciprocal:
 *    k_0 := MPD_RDIGITS-2
 *    z_0 := 10**(-k_0) * floor(10**(2*k_0 + 2) / floor(v * 10**(k_0 + 2)))
 * Absolute error:
 *    |1/v - z_0| < 10**(-k_0)
 * ACL2 proof: maxerror-inverse-approx
 */
static void
_mpd_qreciprocal_approx(mpd_t *z, const mpd_t *v, uint32_t *status)
{
    mpd_uint_t p10data[2] = {0, mpd_pow10[MPD_RDIGITS-2]};
    mpd_uint_t dummy, word;
    int n;

    assert(v->exp == -v->digits);

    _mpd_get_msdigits(&dummy, &word, v, MPD_RDIGITS);
    n = mpd_word_digits(word);
    word *= mpd_pow10[MPD_RDIGITS-n];

    mpd_qresize(z, 2, status);
    (void)_mpd_shortdiv(z->data, p10data, 2, word);

    mpd_clear_flags(z);
    z->exp = -(MPD_RDIGITS-2);
    z->len = (z->data[1] == 0) ? 1 : 2;
    mpd_setdigits(z);
}

/*
 * Reciprocal, calculated with Newton's Method. Assumption: result != a.
 * NOTE: The comments in the function show that certain operations are
 * exact. The proof for the maximum error is too long to fit in here.
 * ACL2 proof: maxerror-inverse-complete
 */
static void
_mpd_qreciprocal(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
                 uint32_t *status)
{
    mpd_context_t varcontext, maxcontext;
    mpd_t *z = result;         /* current approximation */
    mpd_t *v;                  /* a, normalized to a number between 0.1 and 1 */
    MPD_NEW_SHARED(vtmp, a);   /* v shares data with a */
    MPD_NEW_STATIC(s,0,0,0,0); /* temporary variable */
    MPD_NEW_STATIC(t,0,0,0,0); /* temporary variable */
    MPD_NEW_CONST(two,0,0,1,1,1,2); /* const 2 */
    mpd_ssize_t klist[MPD_MAX_PREC_LOG2];
    mpd_ssize_t adj, maxprec, initprec;
    uint8_t sign = mpd_sign(a);
    int i;

    assert(result != a);

    v = &vtmp;
    mpd_clear_flags(v);
    adj = v->digits + v->exp;
    v->exp = -v->digits;

    /* Initial approximation */
    _mpd_qreciprocal_approx(z, v, status);

    mpd_maxcontext(&varcontext);
    mpd_maxcontext(&maxcontext);
    varcontext.round = maxcontext.round = MPD_ROUND_TRUNC;
    varcontext.emax = maxcontext.emax = MPD_MAX_EMAX + 100;
    varcontext.emin = maxcontext.emin = MPD_MIN_EMIN - 100;
    maxcontext.prec = MPD_MAX_PREC + 100;

    maxprec = ctx->prec;
    maxprec += 2;
    initprec = MPD_RDIGITS-3;

    i = recpr_schedule_prec(klist, maxprec, initprec);
    for (; i >= 0; i--) {
         /* Loop invariant: z->digits <= klist[i]+7 */
         /* Let s := z**2, exact result */
        _mpd_qmul_exact(&s, z, z, &maxcontext, status);
        varcontext.prec = 2*klist[i] + 5;
        if (v->digits > varcontext.prec) {
            /* Let t := v, truncated to n >= 2*k+5 fraction digits */
            mpd_qshiftr(&t, v, v->digits-varcontext.prec, status);
            t.exp = -varcontext.prec;
            /* Let t := trunc(v)*s, truncated to n >= 2*k+1 fraction digits */
            mpd_qmul(&t, &t, &s, &varcontext, status);
        }
        else { /* v->digits <= 2*k+5 */
            /* Let t := v*s, truncated to n >= 2*k+1 fraction digits */
            mpd_qmul(&t, v, &s, &varcontext, status);
        }
        /* Let s := 2*z, exact result */
        _mpd_qmul_exact(&s, z, &two, &maxcontext, status);
        /* s.digits < t.digits <= 2*k+5, |adjexp(s)-adjexp(t)| <= 1,
         * so the subtraction generates at most 2*k+6 <= klist[i+1]+7
         * digits. The loop invariant is preserved. */
        _mpd_qsub_exact(z, &s, &t, &maxcontext, status);
    }

    if (!mpd_isspecial(z)) {
        z->exp -= adj;
        mpd_set_flags(z, sign);
    }

    mpd_del(&s);
    mpd_del(&t);
    mpd_qfinalize(z, ctx, status);
}

/*
 * Internal function for large numbers:
 *
 *     q, r = divmod(coeff(a), coeff(b))
 *
 * Strategy: Multiply the dividend by the reciprocal of the divisor. The
 * inexact result is fixed by a small loop, using at most one iteration.
 *
 * ACL2 proofs:
 * ------------
 *    1) q is a natural number.  (ndivmod-quotient-natp)
 *    2) r is a natural number.  (ndivmod-remainder-natp)
 *    3) a = q * b + r           (ndivmod-q*b+r==a)
 *    4) r < b                   (ndivmod-remainder-<-b)
 */
static void
_mpd_base_ndivmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
                  uint32_t *status)
{
    mpd_context_t workctx;
    mpd_t *qq = q, *rr = r;
    mpd_t aa, bb;
    int k;

    _mpd_copy_shared(&aa, a);
    _mpd_copy_shared(&bb, b);

    mpd_set_positive(&aa);
    mpd_set_positive(&bb);
    aa.exp = 0;
    bb.exp = 0;

    if (q == a || q == b) {
        if ((qq = mpd_qnew()) == NULL) {
            *status |= MPD_Malloc_error;
            goto nanresult;
        }
    }
    if (r == a || r == b) {
        if ((rr = mpd_qnew()) == NULL) {
            *status |= MPD_Malloc_error;
            goto nanresult;
        }
    }

    mpd_maxcontext(&workctx);

    /* Let prec := adigits - bdigits + 4 */
    workctx.prec = a->digits - b->digits + 1 + 3;
    if (a->digits > MPD_MAX_PREC || workctx.prec > MPD_MAX_PREC) {
        *status |= MPD_Division_impossible;
        goto nanresult;
    }

    /* Let x := _mpd_qreciprocal(b, prec)
     * Then x is bounded by:
     *    1) 1/b - 10**(-prec - bdigits) < x < 1/b + 10**(-prec - bdigits)
     *    2) 1/b - 10**(-adigits - 4) < x < 1/b + 10**(-adigits - 4)
     */
    _mpd_qreciprocal(rr, &bb, &workctx, &workctx.status);

    /* Get an estimate for the quotient. Let q := a * x
     * Then q is bounded by:
     *    3) a/b - 10**-4 < q < a/b + 10**-4
     */
    _mpd_qmul(qq, &aa, rr, &workctx, &workctx.status);
    /* Truncate q to an integer:
     *    4) a/b - 2 < trunc(q) < a/b + 1
     */
    mpd_qtrunc(qq, qq, &workctx, &workctx.status);

    workctx.prec = aa.digits + 3;
    workctx.emax = MPD_MAX_EMAX + 3;
    workctx.emin = MPD_MIN_EMIN - 3;
    /* Multiply the estimate for q by b:
     *    5) a - 2 * b < trunc(q) * b < a + b
     */
    _mpd_qmul(rr, &bb, qq, &workctx, &workctx.status);
    /* Get the estimate for r such that a = q * b + r. */
    _mpd_qsub_exact(rr, &aa, rr, &workctx, &workctx.status);

    /* Fix the result. At this point -b < r < 2*b, so the correction loop
       takes at most one iteration. */
    for (k = 0;; k++) {
        if (mpd_isspecial(qq) || mpd_isspecial(rr)) {
            *status |= (workctx.status&MPD_Errors);
            goto nanresult;
        }
        if (k > 2) { /* Allow two iterations despite the proof. */
            mpd_err_warn("libmpdec: internal error in "       /* GCOV_NOT_REACHED */
                         "_mpd_base_ndivmod: please report"); /* GCOV_NOT_REACHED */
            *status |= MPD_Invalid_operation;                 /* GCOV_NOT_REACHED */
            goto nanresult;                                   /* GCOV_NOT_REACHED */
        }
        /* r < 0 */
        else if (_mpd_cmp(&zero, rr) == 1) {
            _mpd_qadd_exact(rr, rr, &bb, &workctx, &workctx.status);
            _mpd_qadd_exact(qq, qq, &minus_one, &workctx, &workctx.status);
        }
        /* 0 <= r < b */
        else if (_mpd_cmp(rr, &bb) == -1) {
            break;
        }
        /* r >= b */
        else {
            _mpd_qsub_exact(rr, rr, &bb, &workctx, &workctx.status);
            _mpd_qadd_exact(qq, qq, &one, &workctx, &workctx.status);
        }
    }

    if (qq != q) {
        if (!mpd_qcopy(q, qq, status)) {
            goto nanresult; /* GCOV_UNLIKELY */
        }
        mpd_del(qq);
    }
    if (rr != r) {
        if (!mpd_qcopy(r, rr, status)) {
            goto nanresult; /* GCOV_UNLIKELY */
        }
        mpd_del(rr);
    }

    *status |= (workctx.status&MPD_Errors);
    return;


nanresult:
    if (qq && qq != q) mpd_del(qq);
    if (rr && rr != r) mpd_del(rr);
    mpd_setspecial(q, MPD_POS, MPD_NAN);
    mpd_setspecial(r, MPD_POS, MPD_NAN);
}

/* LIBMPDEC_ONLY */
/*
 * Schedule the optimal precision increase for the Newton iteration.
 *   v := input operand
 *   z_0 := initial approximation
 *   initprec := natural number such that abs(sqrt(v) - z_0) < 10**-initprec
 *   maxprec := target precision
 *
 * For convenience the output klist contains the elements in reverse order:
 *   klist := [k_n-1, ..., k_0], where
 *     1) k_0 <= initprec and
 *     2) abs(sqrt(v) - result) < 10**(-2*k_n-1 + 2) <= 10**-maxprec.
 */
static inline int
invroot_schedule_prec(mpd_ssize_t klist[MPD_MAX_PREC_LOG2],
                      mpd_ssize_t maxprec, mpd_ssize_t initprec)
{
    mpd_ssize_t k;
    int i;

    assert(maxprec >= 3 && initprec >= 3);
    if (maxprec <= initprec) return -1;

    i = 0; k = maxprec;
    do {
        k = (k+3) / 2;
        klist[i++] = k;
    } while (k > initprec);

    return i-1;
}

/*
 * Initial approximation for the inverse square root function.
 *   Input:
 *     v := rational number, with 1 <= v < 100
 *     vhat := floor(v * 10**6)
 *   Output:
 *     z := approximation to 1/sqrt(v), such that abs(z - 1/sqrt(v)) < 10**-3.
 */
static inline void
_invroot_init_approx(mpd_t *z, mpd_uint_t vhat)
{
    mpd_uint_t lo = 1000;
    mpd_uint_t hi = 10000;
    mpd_uint_t a, sq;

    assert(lo*lo <= vhat && vhat < (hi+1)*(hi+1));

    for(;;) {
        a = (lo + hi) / 2;
        sq = a * a;
        if (vhat >= sq) {
            if (vhat < sq + 2*a + 1) {
                break;
            }
            lo = a + 1;
        }
        else {
            hi = a - 1;
        }
    }

    /*
     * After the binary search we have:
     *  1) a**2 <= floor(v * 10**6) < (a + 1)**2
     * This implies:
     *  2) a**2 <= v * 10**6 < (a + 1)**2
     *  3) a <= sqrt(v) * 10**3 < a + 1
     * Since 10**3 <= a:
     *  4) 0 <= 10**prec/a - 1/sqrt(v) < 10**-prec
     * We have:
     *  5) 10**3/a - 10**-3 < floor(10**9/a) * 10**-6 <= 10**3/a
     * Merging 4) and 5):
     *  6) abs(floor(10**9/a) * 10**-6 - 1/sqrt(v)) < 10**-3
     */
    mpd_minalloc(z);
    mpd_clear_flags(z);
    z->data[0] = 1000000000UL / a;
    z->len = 1;
    z->exp = -6;
    mpd_setdigits(z);
}

/*
 * Set 'result' to 1/sqrt(a).
 *   Relative error: abs(result - 1/sqrt(a)) < 10**-prec * 1/sqrt(a)
 */
static void
_mpd_qinvroot(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
              uint32_t *status)
{
    uint32_t workstatus = 0;
    mpd_context_t varcontext, maxcontext;
    mpd_t *z = result;         /* current approximation */
    mpd_t *v;                  /* a, normalized to a number between 1 and 100 */
    MPD_NEW_SHARED(vtmp, a);   /* by default v will share data with a */
    MPD_NEW_STATIC(s,0,0,0,0); /* temporary variable */
    MPD_NEW_STATIC(t,0,0,0,0); /* temporary variable */
    MPD_NEW_CONST(one_half,0,-1,1,1,1,5);
    MPD_NEW_CONST(three,0,0,1,1,1,3);
    mpd_ssize_t klist[MPD_MAX_PREC_LOG2];
    mpd_ssize_t ideal_exp, shift;
    mpd_ssize_t adj, tz;
    mpd_ssize_t maxprec, fracdigits;
    mpd_uint_t vhat, dummy;
    int i, n;


    ideal_exp = -(a->exp - (a->exp & 1)) / 2;

    v = &vtmp;
    if (result == a) {
        if ((v = mpd_qncopy(a)) == NULL) {
            mpd_seterror(result, MPD_Malloc_error, status);
            return;
        }
    }

    /* normalize a to 1 <= v < 100 */
    if ((v->digits+v->exp) & 1) {
        fracdigits = v->digits - 1;
        v->exp = -fracdigits;
        n = (v->digits > 7) ? 7 : (int)v->digits;
        /* Let vhat := floor(v * 10**(2*initprec)) */
        _mpd_get_msdigits(&dummy, &vhat, v, n);
        if (n < 7) {
            vhat *= mpd_pow10[7-n];
        }
    }
    else {
        fracdigits = v->digits - 2;
        v->exp = -fracdigits;
        n = (v->digits > 8) ? 8 : (int)v->digits;
        /* Let vhat := floor(v * 10**(2*initprec)) */
        _mpd_get_msdigits(&dummy, &vhat, v, n);
        if (n < 8) {
            vhat *= mpd_pow10[8-n];
        }
    }
    adj = (a->exp-v->exp) / 2;

    /* initial approximation */
    _invroot_init_approx(z, vhat);

    mpd_maxcontext(&maxcontext);
    mpd_maxcontext(&varcontext);
    varcontext.round = MPD_ROUND_TRUNC;
    maxprec = ctx->prec + 1;

    /* initprec == 3 */
    i = invroot_schedule_prec(klist, maxprec, 3);
    for (; i >= 0; i--) {
        varcontext.prec = 2*klist[i]+2;
        mpd_qmul(&s, z, z, &maxcontext, &workstatus);
        if (v->digits > varcontext.prec) {
            shift = v->digits - varcontext.prec;
            mpd_qshiftr(&t, v, shift, &workstatus);
            t.exp += shift;
            mpd_qmul(&t, &t, &s, &varcontext, &workstatus);
        }
        else {
            mpd_qmul(&t, v, &s, &varcontext, &workstatus);
        }
        mpd_qsub(&t, &three, &t, &maxcontext, &workstatus);
        mpd_qmul(z, z, &t, &varcontext, &workstatus);
        mpd_qmul(z, z, &one_half, &maxcontext, &workstatus);
    }

    z->exp -= adj;

    tz = mpd_trail_zeros(result);
    shift = ideal_exp - result->exp;
    shift = (tz > shift) ? shift : tz;
    if (shift > 0) {
        mpd_qshiftr_inplace(result, shift);
        result->exp += shift;
    }


    mpd_del(&s);
    mpd_del(&t);
    if (v != &vtmp) mpd_del(v);
    *status |= (workstatus&MPD_Errors);
    *status |= (MPD_Rounded|MPD_Inexact);
}

void
mpd_qinvroot(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
             uint32_t *status)
{
    mpd_context_t workctx;

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        if (mpd_isnegative(a)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        /* positive infinity */
        _settriple(result, MPD_POS, 0, mpd_etiny(ctx));
        *status |= MPD_Clamped;
        return;
    }
    if (mpd_iszero(a)) {
        mpd_setspecial(result, mpd_sign(a), MPD_INF);
        *status |= MPD_Division_by_zero;
        return;
    }
    if (mpd_isnegative(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    mpd_workcontext(&workctx, ctx);
    workctx.prec += 2;
    workctx.round = MPD_ROUND_HALF_EVEN;
    _mpd_qinvroot(result, a, &workctx, status);
    mpd_qfinalize(result, ctx, status);
}
/* END LIBMPDEC_ONLY */

/* Algorithm from decimal.py */
static void
_mpd_qsqrt(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
           uint32_t *status)
{
    mpd_context_t maxcontext;
    MPD_NEW_STATIC(c,0,0,0,0);
    MPD_NEW_STATIC(q,0,0,0,0);
    MPD_NEW_STATIC(r,0,0,0,0);
    MPD_NEW_CONST(two,0,0,1,1,1,2);
    mpd_ssize_t prec, ideal_exp;
    mpd_ssize_t l, shift;
    int exact = 0;


    ideal_exp = (a->exp - (a->exp & 1)) / 2;

    if (mpd_isspecial(a)) {
        if (mpd_qcheck_nan(result, a, ctx, status)) {
            return;
        }
        if (mpd_isnegative(a)) {
            mpd_seterror(result, MPD_Invalid_operation, status);
            return;
        }
        mpd_setspecial(result, MPD_POS, MPD_INF);
        return;
    }
    if (mpd_iszero(a)) {
        _settriple(result, mpd_sign(a), 0, ideal_exp);
        mpd_qfinalize(result, ctx, status);
        return;
    }
    if (mpd_isnegative(a)) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    mpd_maxcontext(&maxcontext);
    prec = ctx->prec + 1;

    if (!mpd_qcopy(&c, a, status)) {
        goto malloc_error;
    }
    c.exp = 0;

    if (a->exp & 1) {
        if (!mpd_qshiftl(&c, &c, 1, status)) {
            goto malloc_error;
        }
        l = (a->digits >> 1) + 1;
    }
    else {
        l = (a->digits + 1) >> 1;
    }

    shift = prec - l;
    if (shift >= 0) {
        if (!mpd_qshiftl(&c, &c, 2*shift, status)) {
            goto malloc_error;
        }
        exact = 1;
    }
    else {
        exact = !mpd_qshiftr_inplace(&c, -2*shift);
    }

    ideal_exp -= shift;

    /* find result = floor(sqrt(c)) using Newton's method */
    if (!mpd_qshiftl(result, &one, prec, status)) {
        goto malloc_error;
    }

    while (1) {
        _mpd_qdivmod(&q, &r, &c, result, &maxcontext, &maxcontext.status);
        if (mpd_isspecial(result) || mpd_isspecial(&q)) {
            mpd_seterror(result, maxcontext.status&MPD_Errors, status);
            goto out;
        }
        if (_mpd_cmp(result, &q) <= 0) {
            break;
        }
        _mpd_qadd_exact(result, result, &q, &maxcontext, &maxcontext.status);
        if (mpd_isspecial(result)) {
            mpd_seterror(result, maxcontext.status&MPD_Errors, status);
            goto out;
        }
        _mpd_qdivmod(result, &r, result, &two, &maxcontext, &maxcontext.status);
    }

    if (exact) {
        _mpd_qmul_exact(&r, result, result, &maxcontext, &maxcontext.status);
        if (mpd_isspecial(&r)) {
            mpd_seterror(result, maxcontext.status&MPD_Errors, status);
            goto out;
        }
        exact = (_mpd_cmp(&r, &c) == 0);
    }

    if (exact) {
        if (shift >= 0) {
            mpd_qshiftr_inplace(result, shift);
        }
        else {
            if (!mpd_qshiftl(result, result, -shift, status)) {
                goto malloc_error;
            }
        }
        ideal_exp += shift;
    }
    else {
        int lsd = (int)mpd_lsd(result->data[0]);
        if (lsd == 0 || lsd == 5) {
            result->data[0] += 1;
        }
    }

    result->exp = ideal_exp;


out:
    mpd_del(&c);
    mpd_del(&q);
    mpd_del(&r);
    mpd_workcontext(&maxcontext, ctx);
    maxcontext.round = MPD_ROUND_HALF_EVEN;
    mpd_qfinalize(result, &maxcontext, status);
    return;

malloc_error:
    mpd_seterror(result, MPD_Malloc_error, status);
    goto out;
}

void
mpd_qsqrt(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
          uint32_t *status)
{
    MPD_NEW_STATIC(aa,0,0,0,0);
    uint32_t xstatus = 0;

    if (result == a) {
        if (!mpd_qcopy(&aa, a, status)) {
            mpd_seterror(result, MPD_Malloc_error, status);
            goto out;
        }
        a = &aa;
    }

    _mpd_qsqrt(result, a, ctx, &xstatus);

    if (xstatus & (MPD_Malloc_error|MPD_Division_impossible)) {
        /* The above conditions can occur at very high context precisions
         * if intermediate values get too large. Retry the operation with
         * a lower context precision in case the result is exact.
         *
         * If the result is exact, an upper bound for the number of digits
         * is the number of digits in the input.
         *
         * NOTE: sqrt(40e9) = 2.0e+5 /\ digits(40e9) = digits(2.0e+5) = 2
         */
        uint32_t ystatus = 0;
        mpd_context_t workctx;
        mpd_workcontext(&workctx, ctx);

        workctx.prec = a->digits;
        if (workctx.prec >= ctx->prec) {
            *status |= (xstatus|MPD_Errors);
            goto out; /* No point in repeating this, keep the original error. */
        }

        _mpd_qsqrt(result, a, &workctx, &ystatus);
        if (ystatus != 0) {
            ystatus = *status | ((xstatus|ystatus)&MPD_Errors);
            mpd_seterror(result, ystatus, status);
        }
    }
    else {
        *status |= xstatus;
    }

out:
    mpd_del(&aa);
}


/******************************************************************************/
/*                              Base conversions                              */
/******************************************************************************/

/* Space needed to represent an integer mpd_t in base 'base'. */
size_t
mpd_sizeinbase(const mpd_t *a, uint32_t base)
{
    double x;
    size_t digits;
    double upper_bound;

    assert(mpd_isinteger(a));
    assert(base >= 2);

    if (mpd_iszero(a)) {
        return 1;
    }

    digits = a->digits+a->exp;

#ifdef CONFIG_64
    /* ceil(2711437152599294 / log10(2)) + 4 == 2**53 */
    if (digits > 2711437152599294ULL) {
        return SIZE_MAX;
    }

    upper_bound = (double)((1ULL<<53)-1);
#else
    upper_bound = (double)(SIZE_MAX-1);
#endif

    x = (double)digits / log10(base);
    return (x > upper_bound) ? SIZE_MAX : (size_t)x + 1;
}

/* Space needed to import a base 'base' integer of length 'srclen'. */
static mpd_ssize_t
_mpd_importsize(size_t srclen, uint32_t base)
{
    double x;
    double upper_bound;

    assert(srclen > 0);
    assert(base >= 2);

#if SIZE_MAX == UINT64_MAX
    if (srclen > (1ULL<<53)) {
        return MPD_SSIZE_MAX;
    }

    assert((1ULL<<53) <= MPD_MAXIMPORT);
    upper_bound = (double)((1ULL<<53)-1);
#else
    upper_bound = MPD_MAXIMPORT-1;
#endif

    x = (double)srclen * (log10(base)/MPD_RDIGITS);
    return (x > upper_bound) ? MPD_SSIZE_MAX : (mpd_ssize_t)x + 1;
}

static uint8_t
mpd_resize_u16(uint16_t **w, size_t nmemb)
{
    uint8_t err = 0;
    *w = mpd_realloc(*w, nmemb, sizeof **w, &err);
    return !err;
}

static uint8_t
mpd_resize_u32(uint32_t **w, size_t nmemb)
{
    uint8_t err = 0;
    *w = mpd_realloc(*w, nmemb, sizeof **w, &err);
    return !err;
}

static size_t
_baseconv_to_u16(uint16_t **w, size_t wlen, mpd_uint_t wbase,
                 mpd_uint_t *u, mpd_ssize_t ulen)
{
    size_t n = 0;

    assert(wlen > 0 && ulen > 0);
    assert(wbase <= (1U<<16));

    do {
        if (n >= wlen) {
            if (!mpd_resize_u16(w, n+1)) {
                return SIZE_MAX;
            }
            wlen = n+1;
        }
        (*w)[n++] = (uint16_t)_mpd_shortdiv(u, u, ulen, wbase);
        /* ulen is at least 1. u[ulen-1] can only be zero if ulen == 1. */
        ulen = _mpd_real_size(u, ulen);

    } while (u[ulen-1] != 0);

    return n;
}

static size_t
_coeff_from_u16(mpd_t *w, mpd_ssize_t wlen,
                const mpd_uint_t *u, size_t ulen, uint32_t ubase,
                uint32_t *status)
{
    mpd_ssize_t n = 0;
    mpd_uint_t carry;

    assert(wlen > 0 && ulen > 0);
    assert(ubase <= (1U<<16));

    w->data[n++] = u[--ulen];
    while (--ulen != SIZE_MAX) {
        carry = _mpd_shortmul_c(w->data, w->data, n, ubase);
        if (carry) {
            if (n >= wlen) {
                if (!mpd_qresize(w, n+1, status)) {
                    return SIZE_MAX;
                }
                wlen = n+1;
            }
            w->data[n++] = carry;
        }
        carry = _mpd_shortadd(w->data, n, u[ulen]);
        if (carry) {
            if (n >= wlen) {
                if (!mpd_qresize(w, n+1, status)) {
                    return SIZE_MAX;
                }
                wlen = n+1;
            }
            w->data[n++] = carry;
        }
    }

    return n;
}

/* target base wbase < source base ubase */
static size_t
_baseconv_to_smaller(uint32_t **w, size_t wlen, uint32_t wbase,
                     mpd_uint_t *u, mpd_ssize_t ulen, mpd_uint_t ubase)
{
    size_t n = 0;

    assert(wlen > 0 && ulen > 0);
    assert(wbase < ubase);

    do {
        if (n >= wlen) {
            if (!mpd_resize_u32(w, n+1)) {
                return SIZE_MAX;
            }
            wlen = n+1;
        }
        (*w)[n++] = (uint32_t)_mpd_shortdiv_b(u, u, ulen, wbase, ubase);
        /* ulen is at least 1. u[ulen-1] can only be zero if ulen == 1. */
        ulen = _mpd_real_size(u, ulen);

    } while (u[ulen-1] != 0);

    return n;
}

#ifdef CONFIG_32
/* target base 'wbase' == source base 'ubase' */
static size_t
_copy_equal_base(uint32_t **w, size_t wlen,
                 const uint32_t *u, size_t ulen)
{
    if (wlen < ulen) {
        if (!mpd_resize_u32(w, ulen)) {
            return SIZE_MAX;
        }
    }

    memcpy(*w, u, ulen * (sizeof **w));
    return ulen;
}

/* target base 'wbase' > source base 'ubase' */
static size_t
_baseconv_to_larger(uint32_t **w, size_t wlen, mpd_uint_t wbase,
                    const mpd_uint_t *u, size_t ulen, mpd_uint_t ubase)
{
    size_t n = 0;
    mpd_uint_t carry;

    assert(wlen > 0 && ulen > 0);
    assert(ubase < wbase);

    (*w)[n++] = u[--ulen];
    while (--ulen != SIZE_MAX) {
        carry = _mpd_shortmul_b(*w, *w, n, ubase, wbase);
        if (carry) {
            if (n >= wlen) {
                if (!mpd_resize_u32(w, n+1)) {
                    return SIZE_MAX;
                }
                wlen = n+1;
            }
            (*w)[n++] = carry;
        }
        carry = _mpd_shortadd_b(*w, n, u[ulen], wbase);
        if (carry) {
            if (n >= wlen) {
                if (!mpd_resize_u32(w, n+1)) {
                    return SIZE_MAX;
                }
                wlen = n+1;
            }
            (*w)[n++] = carry;
        }
    }

    return n;
}

/* target base wbase < source base ubase */
static size_t
_coeff_from_larger_base(mpd_t *w, size_t wlen, mpd_uint_t wbase,
                        mpd_uint_t *u, mpd_ssize_t ulen, mpd_uint_t ubase,
                        uint32_t *status)
{
    size_t n = 0;

    assert(wlen > 0 && ulen > 0);
    assert(wbase < ubase);

    do {
        if (n >= wlen) {
            if (!mpd_qresize(w, n+1, status)) {
                return SIZE_MAX;
            }
            wlen = n+1;
        }
        w->data[n++] = (uint32_t)_mpd_shortdiv_b(u, u, ulen, wbase, ubase);
        /* ulen is at least 1. u[ulen-1] can only be zero if ulen == 1. */
        ulen = _mpd_real_size(u, ulen);

    } while (u[ulen-1] != 0);

    return n;
}
#endif

/* target base 'wbase' > source base 'ubase' */
static size_t
_coeff_from_smaller_base(mpd_t *w, mpd_ssize_t wlen, mpd_uint_t wbase,
                         const uint32_t *u, size_t ulen, mpd_uint_t ubase,
                         uint32_t *status)
{
    mpd_ssize_t n = 0;
    mpd_uint_t carry;

    assert(wlen > 0 && ulen > 0);
    assert(wbase > ubase);

    w->data[n++] = u[--ulen];
    while (--ulen != SIZE_MAX) {
        carry = _mpd_shortmul_b(w->data, w->data, n, ubase, wbase);
        if (carry) {
            if (n >= wlen) {
                if (!mpd_qresize(w, n+1, status)) {
                    return SIZE_MAX;
                }
                wlen = n+1;
            }
            w->data[n++] = carry;
        }
        carry = _mpd_shortadd_b(w->data, n, u[ulen], wbase);
        if (carry) {
            if (n >= wlen) {
                if (!mpd_qresize(w, n+1, status)) {
                    return SIZE_MAX;
                }
                wlen = n+1;
            }
            w->data[n++] = carry;
        }
    }

    return n;
}

/*
 * Convert an integer mpd_t to a multiprecision integer with base <= 2**16.
 * The least significant word of the result is (*rdata)[0].
 *
 * If rdata is NULL, space is allocated by the function and rlen is irrelevant.
 * In case of an error any allocated storage is freed and rdata is set back to
 * NULL.
 *
 * If rdata is non-NULL, it MUST be allocated by one of libmpdec's allocation
 * functions and rlen MUST be correct. If necessary, the function will resize
 * rdata. In case of an error the caller must free rdata.
 *
 * Return value: In case of success, the exact length of rdata, SIZE_MAX
 * otherwise.
 */
size_t
mpd_qexport_u16(uint16_t **rdata, size_t rlen, uint32_t rbase,
                const mpd_t *src, uint32_t *status)
{
    MPD_NEW_STATIC(tsrc,0,0,0,0);
    int alloc = 0; /* rdata == NULL */
    size_t n;

    assert(rbase <= (1U<<16));

    if (mpd_isspecial(src) || !_mpd_isint(src)) {
        *status |= MPD_Invalid_operation;
        return SIZE_MAX;
    }

    if (*rdata == NULL) {
        rlen = mpd_sizeinbase(src, rbase);
        if (rlen == SIZE_MAX) {
            *status |= MPD_Invalid_operation;
            return SIZE_MAX;
        }
        *rdata = mpd_alloc(rlen, sizeof **rdata);
        if (*rdata == NULL) {
            goto malloc_error;
        }
        alloc = 1;
    }

    if (mpd_iszero(src)) {
        **rdata = 0;
        return 1;
    }

    if (src->exp >= 0) {
        if (!mpd_qshiftl(&tsrc, src, src->exp, status)) {
            goto malloc_error;
        }
    }
    else {
        if (mpd_qshiftr(&tsrc, src, -src->exp, status) == MPD_UINT_MAX) {
            goto malloc_error;
        }
    }

    n = _baseconv_to_u16(rdata, rlen, rbase, tsrc.data, tsrc.len);
    if (n == SIZE_MAX) {
        goto malloc_error;
    }


out:
    mpd_del(&tsrc);
    return n;

malloc_error:
    if (alloc) {
        mpd_free(*rdata);
        *rdata = NULL;
    }
    n = SIZE_MAX;
    *status |= MPD_Malloc_error;
    goto out;
}

/*
 * Convert an integer mpd_t to a multiprecision integer with base<=UINT32_MAX.
 * The least significant word of the result is (*rdata)[0].
 *
 * If rdata is NULL, space is allocated by the function and rlen is irrelevant.
 * In case of an error any allocated storage is freed and rdata is set back to
 * NULL.
 *
 * If rdata is non-NULL, it MUST be allocated by one of libmpdec's allocation
 * functions and rlen MUST be correct. If necessary, the function will resize
 * rdata. In case of an error the caller must free rdata.
 *
 * Return value: In case of success, the exact length of rdata, SIZE_MAX
 * otherwise.
 */
size_t
mpd_qexport_u32(uint32_t **rdata, size_t rlen, uint32_t rbase,
                const mpd_t *src, uint32_t *status)
{
    MPD_NEW_STATIC(tsrc,0,0,0,0);
    int alloc = 0; /* rdata == NULL */
    size_t n;

    if (mpd_isspecial(src) || !_mpd_isint(src)) {
        *status |= MPD_Invalid_operation;
        return SIZE_MAX;
    }

    if (*rdata == NULL) {
        rlen = mpd_sizeinbase(src, rbase);
        if (rlen == SIZE_MAX) {
            *status |= MPD_Invalid_operation;
            return SIZE_MAX;
        }
        *rdata = mpd_alloc(rlen, sizeof **rdata);
        if (*rdata == NULL) {
            goto malloc_error;
        }
        alloc = 1;
    }

    if (mpd_iszero(src)) {
        **rdata = 0;
        return 1;
    }

    if (src->exp >= 0) {
        if (!mpd_qshiftl(&tsrc, src, src->exp, status)) {
            goto malloc_error;
        }
    }
    else {
        if (mpd_qshiftr(&tsrc, src, -src->exp, status) == MPD_UINT_MAX) {
            goto malloc_error;
        }
    }

#ifdef CONFIG_64
    n = _baseconv_to_smaller(rdata, rlen, rbase,
                             tsrc.data, tsrc.len, MPD_RADIX);
#else
    if (rbase == MPD_RADIX) {
        n = _copy_equal_base(rdata, rlen, tsrc.data, tsrc.len);
    }
    else if (rbase < MPD_RADIX) {
        n = _baseconv_to_smaller(rdata, rlen, rbase,
                                 tsrc.data, tsrc.len, MPD_RADIX);
    }
    else {
        n = _baseconv_to_larger(rdata, rlen, rbase,
                                tsrc.data, tsrc.len, MPD_RADIX);
    }
#endif

    if (n == SIZE_MAX) {
        goto malloc_error;
    }


out:
    mpd_del(&tsrc);
    return n;

malloc_error:
    if (alloc) {
        mpd_free(*rdata);
        *rdata = NULL;
    }
    n = SIZE_MAX;
    *status |= MPD_Malloc_error;
    goto out;
}


/*
 * Converts a multiprecision integer with base <= UINT16_MAX+1 to an mpd_t.
 * The least significant word of the source is srcdata[0].
 */
void
mpd_qimport_u16(mpd_t *result,
                const uint16_t *srcdata, size_t srclen,
                uint8_t srcsign, uint32_t srcbase,
                const mpd_context_t *ctx, uint32_t *status)
{
    mpd_uint_t *usrc; /* uint16_t src copied to an mpd_uint_t array */
    mpd_ssize_t rlen; /* length of the result */
    size_t n;

    assert(srclen > 0);
    assert(srcbase <= (1U<<16));

    rlen = _mpd_importsize(srclen, srcbase);
    if (rlen == MPD_SSIZE_MAX) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    usrc = mpd_alloc((mpd_size_t)srclen, sizeof *usrc);
    if (usrc == NULL) {
        mpd_seterror(result, MPD_Malloc_error, status);
        return;
    }
    for (n = 0; n < srclen; n++) {
        usrc[n] = srcdata[n];
    }

    if (!mpd_qresize(result, rlen, status)) {
        goto finish;
    }

    n = _coeff_from_u16(result, rlen, usrc, srclen, srcbase, status);
    if (n == SIZE_MAX) {
        goto finish;
    }

    mpd_set_flags(result, srcsign);
    result->exp = 0;
    result->len = n;
    mpd_setdigits(result);

    mpd_qresize(result, result->len, status);
    mpd_qfinalize(result, ctx, status);


finish:
    mpd_free(usrc);
}

/*
 * Converts a multiprecision integer with base <= UINT32_MAX to an mpd_t.
 * The least significant word of the source is srcdata[0].
 */
void
mpd_qimport_u32(mpd_t *result,
                const uint32_t *srcdata, size_t srclen,
                uint8_t srcsign, uint32_t srcbase,
                const mpd_context_t *ctx, uint32_t *status)
{
    mpd_ssize_t rlen; /* length of the result */
    size_t n;

    assert(srclen > 0);

    rlen = _mpd_importsize(srclen, srcbase);
    if (rlen == MPD_SSIZE_MAX) {
        mpd_seterror(result, MPD_Invalid_operation, status);
        return;
    }

    if (!mpd_qresize(result, rlen, status)) {
        return;
    }

#ifdef CONFIG_64
    n = _coeff_from_smaller_base(result, rlen, MPD_RADIX,
                                 srcdata, srclen, srcbase,
                                 status);
#else
    if (srcbase == MPD_RADIX) {
        if (!mpd_qresize(result, srclen, status)) {
            return;
        }
        memcpy(result->data, srcdata, srclen * (sizeof *srcdata));
        n = srclen;
    }
    else if (srcbase < MPD_RADIX) {
        n = _coeff_from_smaller_base(result, rlen, MPD_RADIX,
                                     srcdata, srclen, srcbase,
                                     status);
    }
    else {
        mpd_uint_t *usrc = mpd_alloc((mpd_size_t)srclen, sizeof *usrc);
        if (usrc == NULL) {
            mpd_seterror(result, MPD_Malloc_error, status);
            return;
        }
        for (n = 0; n < srclen; n++) {
            usrc[n] = srcdata[n];
        }

        n = _coeff_from_larger_base(result, rlen, MPD_RADIX,
                                    usrc, (mpd_ssize_t)srclen, srcbase,
                                    status);
        mpd_free(usrc);
    }
#endif

    if (n == SIZE_MAX) {
        return;
    }

    mpd_set_flags(result, srcsign);
    result->exp = 0;
    result->len = n;
    mpd_setdigits(result);

    mpd_qresize(result, result->len, status);
    mpd_qfinalize(result, ctx, status);
}


/******************************************************************************/
/*                                From triple                                 */
/******************************************************************************/

#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
static mpd_ssize_t
_set_coeff(uint64_t data[3], uint64_t hi, uint64_t lo)
{
    __uint128_t d = ((__uint128_t)hi << 64) + lo;
    __uint128_t q, r;

    q = d / MPD_RADIX;
    r = d % MPD_RADIX;
    data[0] = (uint64_t)r;
    d = q;

    q = d / MPD_RADIX;
    r = d % MPD_RADIX;
    data[1] = (uint64_t)r;
    d = q;

    q = d / MPD_RADIX;
    r = d % MPD_RADIX;
    data[2] = (uint64_t)r;

    if (q != 0) {
        abort(); /* GCOV_NOT_REACHED */
    }

    return data[2] != 0 ? 3 : (data[1] != 0 ? 2 : 1);
}
#else
static size_t
_uint_from_u16(mpd_uint_t *w, mpd_ssize_t wlen, const uint16_t *u, size_t ulen)
{
    const mpd_uint_t ubase = 1U<<16;
    mpd_ssize_t n = 0;
    mpd_uint_t carry;

    assert(wlen > 0 && ulen > 0);

    w[n++] = u[--ulen];
    while (--ulen != SIZE_MAX) {
        carry = _mpd_shortmul_c(w, w, n, ubase);
        if (carry) {
            if (n >= wlen) {
                abort();  /* GCOV_NOT_REACHED */
            }
            w[n++] = carry;
        }
        carry = _mpd_shortadd(w, n, u[ulen]);
        if (carry) {
            if (n >= wlen) {
                abort();  /* GCOV_NOT_REACHED */
            }
            w[n++] = carry;
        }
    }

    return n;
}

static mpd_ssize_t
_set_coeff(mpd_uint_t *data, mpd_ssize_t len, uint64_t hi, uint64_t lo)
{
    uint16_t u16[8] = {0};

    u16[7] = (uint16_t)((hi & 0xFFFF000000000000ULL) >> 48);
    u16[6] = (uint16_t)((hi & 0x0000FFFF00000000ULL) >> 32);
    u16[5] = (uint16_t)((hi & 0x00000000FFFF0000ULL) >> 16);
    u16[4] = (uint16_t) (hi & 0x000000000000FFFFULL);

    u16[3] = (uint16_t)((lo & 0xFFFF000000000000ULL) >> 48);
    u16[2] = (uint16_t)((lo & 0x0000FFFF00000000ULL) >> 32);
    u16[1] = (uint16_t)((lo & 0x00000000FFFF0000ULL) >> 16);
    u16[0] = (uint16_t) (lo & 0x000000000000FFFFULL);

    return (mpd_ssize_t)_uint_from_u16(data, len, u16, 8);
}
#endif

static int
_set_uint128_coeff_exp(mpd_t *result, uint64_t hi, uint64_t lo, mpd_ssize_t exp)
{
    mpd_uint_t data[5] = {0};
    uint32_t status = 0;
    mpd_ssize_t len;

#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
    len = _set_coeff(data, hi, lo);
#else
    len = _set_coeff(data, 5, hi, lo);
#endif

    if (!mpd_qresize(result, len, &status)) {
        return -1;
    }

    for (mpd_ssize_t i = 0; i < len; i++) {
        result->data[i] = data[i];
    }

    result->exp = exp;
    result->len = len;
    mpd_setdigits(result);

    return 0;
}

int
mpd_from_uint128_triple(mpd_t *result, const mpd_uint128_triple_t *triple, uint32_t *status)
{
    static const mpd_context_t maxcontext = {
     .prec=MPD_MAX_PREC,
     .emax=MPD_MAX_EMAX,
     .emin=MPD_MIN_EMIN,
     .round=MPD_ROUND_HALF_EVEN,
     .traps=MPD_Traps,
     .status=0,
     .newtrap=0,
     .clamp=0,
     .allcr=1,
    };
    const enum mpd_triple_class tag = triple->tag;
    const uint8_t sign = triple->sign;
    const uint64_t hi = triple->hi;
    const uint64_t lo = triple->lo;
    mpd_ssize_t exp;

#ifdef CONFIG_32
    if (triple->exp < MPD_SSIZE_MIN || triple->exp > MPD_SSIZE_MAX) {
        goto conversion_error;
    }
#endif
    exp = (mpd_ssize_t)triple->exp;

    switch (tag) {
    case MPD_TRIPLE_QNAN: case MPD_TRIPLE_SNAN: {
        if (sign > 1 || exp != 0) {
            goto conversion_error;
        }

        const uint8_t flags = tag == MPD_TRIPLE_QNAN ? MPD_NAN : MPD_SNAN;
        mpd_setspecial(result, sign, flags);

        if (hi == 0 && lo == 0) {  /* no payload */
            return 0;
        }

        if (_set_uint128_coeff_exp(result, hi, lo, exp) < 0) {
            goto malloc_error;
        }

        return 0;
    }

    case MPD_TRIPLE_INF: {
        if (sign > 1 || hi != 0 || lo != 0 || exp != 0) {
            goto conversion_error;
        }

        mpd_setspecial(result, sign, MPD_INF);

        return 0;
    }

    case MPD_TRIPLE_NORMAL: {
        if (sign > 1) {
            goto conversion_error;
        }

        const uint8_t flags = sign ? MPD_NEG : MPD_POS;
        mpd_set_flags(result, flags);

        if (exp > MPD_EXP_INF) {
            exp = MPD_EXP_INF;
        }
        if (exp == MPD_SSIZE_MIN) {
            exp = MPD_SSIZE_MIN+1;
        }

        if (_set_uint128_coeff_exp(result, hi, lo, exp) < 0) {
            goto malloc_error;
        }

        uint32_t workstatus = 0;
        mpd_qfinalize(result, &maxcontext, &workstatus);
        if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
            goto conversion_error;
        }

        return 0;
    }

    default:
        goto conversion_error;
    }

conversion_error:
    mpd_seterror(result, MPD_Conversion_syntax, status);
    return -1;

malloc_error:
    mpd_seterror(result, MPD_Malloc_error, status);
    return -1;
}


/******************************************************************************/
/*                                  As triple                                 */
/******************************************************************************/

#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
static void
_get_coeff(uint64_t *hi, uint64_t *lo, const mpd_t *a)
{
    __uint128_t u128 = 0;

    switch (a->len) {
    case 3:
        u128 = a->data[2]; /* fall through */
    case 2:
        u128 = u128 * MPD_RADIX + a->data[1]; /* fall through */
    case 1:
        u128 = u128 * MPD_RADIX + a->data[0];
        break;
    default:
        abort(); /* GCOV_NOT_REACHED */
    }

    *hi = u128 >> 64;
    *lo = (uint64_t)u128;
}
#else
static size_t
_uint_to_u16(uint16_t w[8], mpd_uint_t *u, mpd_ssize_t ulen)
{
    const mpd_uint_t wbase = 1U<<16;
    size_t n = 0;

    assert(ulen > 0);

    do {
        if (n >= 8) {
            abort();  /* GCOV_NOT_REACHED */
        }
        w[n++] = (uint16_t)_mpd_shortdiv(u, u, ulen, wbase);
        /* ulen is at least 1. u[ulen-1] can only be zero if ulen == 1. */
        ulen = _mpd_real_size(u, ulen);

    } while (u[ulen-1] != 0);

    return n;
}

static void
_get_coeff(uint64_t *hi, uint64_t *lo, const mpd_t *a)
{
    uint16_t u16[8] = {0};
    mpd_uint_t data[5] = {0};

    switch (a->len) {
    case 5:
        data[4] = a->data[4]; /* fall through */
    case 4:
        data[3] = a->data[3]; /* fall through */
    case 3:
        data[2] = a->data[2]; /* fall through */
    case 2:
        data[1] = a->data[1]; /* fall through */
    case 1:
        data[0] = a->data[0];
        break;
    default:
        abort();  /* GCOV_NOT_REACHED */
    }

    _uint_to_u16(u16, data, a->len);

    *hi = (uint64_t)u16[7] << 48;
    *hi |= (uint64_t)u16[6] << 32;
    *hi |= (uint64_t)u16[5] << 16;
    *hi |= (uint64_t)u16[4];

    *lo = (uint64_t)u16[3] << 48;
    *lo |= (uint64_t)u16[2] << 32;
    *lo |= (uint64_t)u16[1] << 16;
    *lo |= (uint64_t)u16[0];
}
#endif

static enum mpd_triple_class
_coeff_as_uint128(uint64_t *hi, uint64_t *lo, const mpd_t *a)
{
#ifdef CONFIG_64
    static mpd_uint_t uint128_max_data[3] = { 3374607431768211455ULL, 4028236692093846346ULL, 3ULL };
    static const mpd_t uint128_max = { MPD_STATIC|MPD_CONST_DATA, 0, 39, 3, 3, uint128_max_data };
#else
    static mpd_uint_t uint128_max_data[5] = { 768211455U, 374607431U, 938463463U, 282366920U, 340U };
    static const mpd_t uint128_max = { MPD_STATIC|MPD_CONST_DATA, 0, 39, 5, 5, uint128_max_data };
#endif
    enum mpd_triple_class ret = MPD_TRIPLE_NORMAL;
    uint32_t status = 0;
    mpd_t coeff;

    *hi = *lo = 0ULL;

    if (mpd_isspecial(a)) {
        if (mpd_isinfinite(a)) {
            return MPD_TRIPLE_INF;
        }

        ret = mpd_isqnan(a) ? MPD_TRIPLE_QNAN : MPD_TRIPLE_SNAN;
        if (a->len == 0) { /* no payload */
            return ret;
        }
    }
    else if (mpd_iszero(a)) {
        return ret;
    }

    _mpd_copy_shared(&coeff, a);
    mpd_set_flags(&coeff, 0);
    coeff.exp = 0;

    if (mpd_qcmp(&coeff, &uint128_max, &status) > 0) {
        return MPD_TRIPLE_ERROR;
    }

    _get_coeff(hi, lo, &coeff);
    return ret;
}

mpd_uint128_triple_t
mpd_as_uint128_triple(const mpd_t *a)
{
    mpd_uint128_triple_t triple = { MPD_TRIPLE_ERROR, 0, 0, 0, 0 };

    triple.tag = _coeff_as_uint128(&triple.hi, &triple.lo, a);
    if (triple.tag == MPD_TRIPLE_ERROR) {
        return triple;
    }

    triple.sign = !!mpd_isnegative(a);
    if (triple.tag == MPD_TRIPLE_NORMAL) {
        triple.exp = a->exp;
    }

    return triple;
}
