summaryrefslogtreecommitdiffstats
path: root/Modules/_decimal/libmpdec
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/_decimal/libmpdec')
-rw-r--r--Modules/_decimal/libmpdec/mpdecimal.c185
1 files changed, 140 insertions, 45 deletions
diff --git a/Modules/_decimal/libmpdec/mpdecimal.c b/Modules/_decimal/libmpdec/mpdecimal.c
index 89be535..d6937bd 100644
--- a/Modules/_decimal/libmpdec/mpdecimal.c
+++ b/Modules/_decimal/libmpdec/mpdecimal.c
@@ -105,8 +105,8 @@ 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_qbarrett_divmod(mpd_t *q, mpd_t *r, const mpd_t *a,
- const mpd_t *b, 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, mpd_t *base, mpd_uint_t exp,
uint8_t resultsign, const mpd_context_t *ctx, uint32_t *status);
@@ -3256,6 +3256,20 @@ mpd_qadd(mpd_t *result, const mpd_t *a, const mpd_t *b,
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,
@@ -3273,6 +3287,20 @@ mpd_qsub(mpd_t *result, const mpd_t *a, const mpd_t *b,
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,
@@ -3500,7 +3528,7 @@ _mpd_qdiv(int action, mpd_t *q, const mpd_t *a, const mpd_t *b,
}
else {
MPD_NEW_STATIC(r,0,0,0,0);
- _mpd_qbarrett_divmod(q, &r, a, b, status);
+ _mpd_base_ndivmod(q, &r, a, b, status);
if (mpd_isspecial(q) || mpd_isspecial(&r)) {
mpd_del(&r);
goto finish;
@@ -3652,14 +3680,10 @@ _mpd_qdivmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
}
}
else {
- _mpd_qbarrett_divmod(q, r, a, b, status);
+ _mpd_base_ndivmod(q, r, a, b, status);
if (mpd_isspecial(q) || mpd_isspecial(r)) {
goto nanresult;
}
- if (mpd_isinfinite(q) || q->digits > ctx->prec) {
- *status |= MPD_Division_impossible;
- goto nanresult;
- }
qsize = q->len;
rsize = r->len;
}
@@ -5369,6 +5393,20 @@ mpd_qmul(mpd_t *result, const mpd_t *a, const mpd_t *b,
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,
@@ -6691,11 +6729,18 @@ recpr_schedule_prec(mpd_ssize_t klist[MPD_MAX_PREC_LOG2],
return i-1;
}
-/* Initial approximation for the reciprocal. */
+/*
+ * 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]}; /* 10**(2*MPD_RDIGITS-2) */
+ mpd_uint_t p10data[2] = {0, mpd_pow10[MPD_RDIGITS-2]};
mpd_uint_t dummy, word;
int n;
@@ -6714,7 +6759,12 @@ _mpd_qreciprocal_approx(mpd_t *z, const mpd_t *v, uint32_t *status)
mpd_setdigits(z);
}
-/* Reciprocal, calculated with Newton's Method. Assumption: result != a. */
+/*
+ * 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)
@@ -6738,32 +6788,43 @@ _mpd_qreciprocal(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
adj = v->digits + v->exp;
v->exp = -v->digits;
- /* initial approximation */
+ /* Initial approximation */
_mpd_qreciprocal_approx(z, v, status);
mpd_maxcontext(&varcontext);
mpd_maxcontext(&maxcontext);
- varcontext.round = MPD_ROUND_TRUNC;
- maxcontext.round = MPD_ROUND_TRUNC;
+ 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 = (v->digits > ctx->prec) ? v->digits : ctx->prec;
+ maxprec = ctx->prec;
maxprec += 2;
initprec = MPD_RDIGITS-3;
i = recpr_schedule_prec(klist, maxprec, initprec);
for (; i >= 0; i--) {
- mpd_qmul(&s, z, z, &maxcontext, status);
+ /* 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 {
+ 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);
}
- mpd_qmul(&s, z, &two, &maxcontext, status);
- mpd_qsub(z, &s, &t, &maxcontext, 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)) {
@@ -6777,22 +6838,29 @@ _mpd_qreciprocal(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
}
/*
- * Integer division with remainder of the coefficients: coeff(a) / coeff(b).
- * This function is for large numbers where it is faster to divide by
- * multiplying the dividend by the reciprocal of the divisor.
- * The inexact result is fixed by a small loop, which should not take
- * more than 2 iterations.
+ * 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 2 iterations.
+ *
+ * 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_qbarrett_divmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
- uint32_t *status)
+_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_maxcontext(&workctx);
_mpd_copy_shared(&aa, a);
_mpd_copy_shared(&bb, b);
@@ -6814,41 +6882,68 @@ _mpd_qbarrett_divmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
}
}
- /* maximum length of q + 3 digits */
- workctx.prec = aa.digits - bb.digits + 1 + 3;
- /* we get the reciprocal with precision maxlen(q) + 3 */
+ 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);
- mpd_qmul(qq, &aa, rr, &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;
- /* get the remainder */
- mpd_qmul(rr, &bb, qq, &workctx, &workctx.status);
- mpd_qsub(rr, &aa, rr, &workctx, &workctx.status);
+ 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. Algorithm from: Karl Hasselstrom, Fast Division of Large Integers */
+ /* 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(rr)) {
+ if (mpd_isspecial(qq) || mpd_isspecial(rr)) {
*status |= (workctx.status&MPD_Errors);
goto nanresult;
}
- if (k > 2) {
- mpd_err_warn("libmpdec: internal error in " /* GCOV_NOT_REACHED */
- "_mpd_qbarrett_divmod: please report"); /* GCOV_NOT_REACHED */
- *status |= MPD_Invalid_operation; /* GCOV_NOT_REACHED */
- goto nanresult; /* GCOV_NOT_REACHED */
+ 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(rr, rr, &bb, &workctx, &workctx.status);
- mpd_qadd(qq, qq, &minus_one, &workctx, &workctx.status);
+ _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(rr, rr, &bb, &workctx, &workctx.status);
- mpd_qadd(qq, qq, &one, &workctx, &workctx.status);
+ _mpd_qsub_exact(rr, rr, &bb, &workctx, &workctx.status);
+ _mpd_qadd_exact(qq, qq, &one, &workctx, &workctx.status);
}
}