Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3c23a87

Browse files
author
Stefan Krah
committed
The divmod function for large numbers now has an ACL2 proof. Related changes:
1) Rename _mpd_qbarrett_divmod into _mpd_base_ndivmod: The function is only marginally related to either Barrett's algorithm or to the version in Hasselstrom's paper. 2) In places where the proof assumes exact operations, use new versions of add/sub/multiply that set NaN/Invalid_operation if this condition is not met. According to the proof this cannot happen, so this should be regarded as an extra safety net. 3) Raise Division_impossible for operands with a number of digits greater than MPD_MAX_PREC. This facilitates the audit of the function and can practically only occur in the 32-bit version under conditions where a MemoryError is already imminent. 4) Use _mpd_qmul() in places where the result can exceed MPD_MAX_PREC in a well defined manner. 5) Test for mpd_isspecial(qq) in a place where the addition of one can theoretically trigger a Malloc_error. 6) Remove redundant code in _mpd_qdivmod(). 7) Add many comments.
1 parent a2898c1 commit 3c23a87

1 file changed

Lines changed: 140 additions & 45 deletions

File tree

Modules/_decimal/libmpdec/mpdecimal.c

Lines changed: 140 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ static void _mpd_qadd(mpd_t *result, const mpd_t *a, const mpd_t *b,
105105
const mpd_context_t *ctx, uint32_t *status);
106106
static inline void _mpd_qmul(mpd_t *result, const mpd_t *a, const mpd_t *b,
107107
const mpd_context_t *ctx, uint32_t *status);
108-
static void _mpd_qbarrett_divmod(mpd_t *q, mpd_t *r, const mpd_t *a,
109-
const mpd_t *b, uint32_t *status);
108+
static void _mpd_base_ndivmod(mpd_t *q, mpd_t *r, const mpd_t *a,
109+
const mpd_t *b, uint32_t *status);
110110
static inline void _mpd_qpow_uint(mpd_t *result, mpd_t *base, mpd_uint_t exp,
111111
uint8_t resultsign, const mpd_context_t *ctx, uint32_t *status);
112112

@@ -3256,6 +3256,20 @@ mpd_qadd(mpd_t *result, const mpd_t *a, const mpd_t *b,
32563256
mpd_qfinalize(result, ctx, status);
32573257
}
32583258

3259+
/* Add a and b. Set NaN/Invalid_operation if the result is inexact. */
3260+
static void
3261+
_mpd_qadd_exact(mpd_t *result, const mpd_t *a, const mpd_t *b,
3262+
const mpd_context_t *ctx, uint32_t *status)
3263+
{
3264+
uint32_t workstatus = 0;
3265+
3266+
mpd_qadd(result, a, b, ctx, &workstatus);
3267+
*status |= workstatus;
3268+
if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
3269+
mpd_seterror(result, MPD_Invalid_operation, status);
3270+
}
3271+
}
3272+
32593273
/* Subtract b from a. */
32603274
void
32613275
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,
32733287
mpd_qfinalize(result, ctx, status);
32743288
}
32753289

3290+
/* Subtract b from a. Set NaN/Invalid_operation if the result is inexact. */
3291+
static void
3292+
_mpd_qsub_exact(mpd_t *result, const mpd_t *a, const mpd_t *b,
3293+
const mpd_context_t *ctx, uint32_t *status)
3294+
{
3295+
uint32_t workstatus = 0;
3296+
3297+
mpd_qsub(result, a, b, ctx, &workstatus);
3298+
*status |= workstatus;
3299+
if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
3300+
mpd_seterror(result, MPD_Invalid_operation, status);
3301+
}
3302+
}
3303+
32763304
/* Add decimal and mpd_ssize_t. */
32773305
void
32783306
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,
35003528
}
35013529
else {
35023530
MPD_NEW_STATIC(r,0,0,0,0);
3503-
_mpd_qbarrett_divmod(q, &r, a, b, status);
3531+
_mpd_base_ndivmod(q, &r, a, b, status);
35043532
if (mpd_isspecial(q) || mpd_isspecial(&r)) {
35053533
mpd_del(&r);
35063534
goto finish;
@@ -3652,14 +3680,10 @@ _mpd_qdivmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
36523680
}
36533681
}
36543682
else {
3655-
_mpd_qbarrett_divmod(q, r, a, b, status);
3683+
_mpd_base_ndivmod(q, r, a, b, status);
36563684
if (mpd_isspecial(q) || mpd_isspecial(r)) {
36573685
goto nanresult;
36583686
}
3659-
if (mpd_isinfinite(q) || q->digits > ctx->prec) {
3660-
*status |= MPD_Division_impossible;
3661-
goto nanresult;
3662-
}
36633687
qsize = q->len;
36643688
rsize = r->len;
36653689
}
@@ -5369,6 +5393,20 @@ mpd_qmul(mpd_t *result, const mpd_t *a, const mpd_t *b,
53695393
mpd_qfinalize(result, ctx, status);
53705394
}
53715395

5396+
/* Multiply a and b. Set NaN/Invalid_operation if the result is inexact. */
5397+
static void
5398+
_mpd_qmul_exact(mpd_t *result, const mpd_t *a, const mpd_t *b,
5399+
const mpd_context_t *ctx, uint32_t *status)
5400+
{
5401+
uint32_t workstatus = 0;
5402+
5403+
mpd_qmul(result, a, b, ctx, &workstatus);
5404+
*status |= workstatus;
5405+
if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) {
5406+
mpd_seterror(result, MPD_Invalid_operation, status);
5407+
}
5408+
}
5409+
53725410
/* Multiply decimal and mpd_ssize_t. */
53735411
void
53745412
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],
66916729
return i-1;
66926730
}
66936731

6694-
/* Initial approximation for the reciprocal. */
6732+
/*
6733+
* Initial approximation for the reciprocal:
6734+
* k_0 := MPD_RDIGITS-2
6735+
* z_0 := 10**(-k_0) * floor(10**(2*k_0 + 2) / floor(v * 10**(k_0 + 2)))
6736+
* Absolute error:
6737+
* |1/v - z_0| < 10**(-k_0)
6738+
* ACL2 proof: maxerror-inverse-approx
6739+
*/
66956740
static void
66966741
_mpd_qreciprocal_approx(mpd_t *z, const mpd_t *v, uint32_t *status)
66976742
{
6698-
mpd_uint_t p10data[2] = {0, mpd_pow10[MPD_RDIGITS-2]}; /* 10**(2*MPD_RDIGITS-2) */
6743+
mpd_uint_t p10data[2] = {0, mpd_pow10[MPD_RDIGITS-2]};
66996744
mpd_uint_t dummy, word;
67006745
int n;
67016746

@@ -6714,7 +6759,12 @@ _mpd_qreciprocal_approx(mpd_t *z, const mpd_t *v, uint32_t *status)
67146759
mpd_setdigits(z);
67156760
}
67166761

6717-
/* Reciprocal, calculated with Newton's Method. Assumption: result != a. */
6762+
/*
6763+
* Reciprocal, calculated with Newton's Method. Assumption: result != a.
6764+
* NOTE: The comments in the function show that certain operations are
6765+
* exact. The proof for the maximum error is too long to fit in here.
6766+
* ACL2 proof: maxerror-inverse-complete
6767+
*/
67186768
static void
67196769
_mpd_qreciprocal(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
67206770
uint32_t *status)
@@ -6738,32 +6788,43 @@ _mpd_qreciprocal(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
67386788
adj = v->digits + v->exp;
67396789
v->exp = -v->digits;
67406790

6741-
/* initial approximation */
6791+
/* Initial approximation */
67426792
_mpd_qreciprocal_approx(z, v, status);
67436793

67446794
mpd_maxcontext(&varcontext);
67456795
mpd_maxcontext(&maxcontext);
6746-
varcontext.round = MPD_ROUND_TRUNC;
6747-
maxcontext.round = MPD_ROUND_TRUNC;
6796+
varcontext.round = maxcontext.round = MPD_ROUND_TRUNC;
6797+
varcontext.emax = maxcontext.emax = MPD_MAX_EMAX + 100;
6798+
varcontext.emin = maxcontext.emin = MPD_MIN_EMIN - 100;
6799+
maxcontext.prec = MPD_MAX_PREC + 100;
67486800

6749-
maxprec = (v->digits > ctx->prec) ? v->digits : ctx->prec;
6801+
maxprec = ctx->prec;
67506802
maxprec += 2;
67516803
initprec = MPD_RDIGITS-3;
67526804

67536805
i = recpr_schedule_prec(klist, maxprec, initprec);
67546806
for (; i >= 0; i--) {
6755-
mpd_qmul(&s, z, z, &maxcontext, status);
6807+
/* Loop invariant: z->digits <= klist[i]+7 */
6808+
/* Let s := z**2, exact result */
6809+
_mpd_qmul_exact(&s, z, z, &maxcontext, status);
67566810
varcontext.prec = 2*klist[i] + 5;
67576811
if (v->digits > varcontext.prec) {
6812+
/* Let t := v, truncated to n >= 2*k+5 fraction digits */
67586813
mpd_qshiftr(&t, v, v->digits-varcontext.prec, status);
67596814
t.exp = -varcontext.prec;
6815+
/* Let t := trunc(v)*s, truncated to n >= 2*k+1 fraction digits */
67606816
mpd_qmul(&t, &t, &s, &varcontext, status);
67616817
}
6762-
else {
6818+
else { /* v->digits <= 2*k+5 */
6819+
/* Let t := v*s, truncated to n >= 2*k+1 fraction digits */
67636820
mpd_qmul(&t, v, &s, &varcontext, status);
67646821
}
6765-
mpd_qmul(&s, z, &two, &maxcontext, status);
6766-
mpd_qsub(z, &s, &t, &maxcontext, status);
6822+
/* Let s := 2*z, exact result */
6823+
_mpd_qmul_exact(&s, z, &two, &maxcontext, status);
6824+
/* s.digits < t.digits <= 2*k+5, |adjexp(s)-adjexp(t)| <= 1,
6825+
* so the subtraction generates at most 2*k+6 <= klist[i+1]+7
6826+
* digits. The loop invariant is preserved. */
6827+
_mpd_qsub_exact(z, &s, &t, &maxcontext, status);
67676828
}
67686829

67696830
if (!mpd_isspecial(z)) {
@@ -6777,22 +6838,29 @@ _mpd_qreciprocal(mpd_t *result, const mpd_t *a, const mpd_context_t *ctx,
67776838
}
67786839

67796840
/*
6780-
* Integer division with remainder of the coefficients: coeff(a) / coeff(b).
6781-
* This function is for large numbers where it is faster to divide by
6782-
* multiplying the dividend by the reciprocal of the divisor.
6783-
* The inexact result is fixed by a small loop, which should not take
6784-
* more than 2 iterations.
6841+
* Internal function for large numbers:
6842+
*
6843+
* q, r = divmod(coeff(a), coeff(b))
6844+
*
6845+
* Strategy: Multiply the dividend by the reciprocal of the divisor. The
6846+
* inexact result is fixed by a small loop, using at most 2 iterations.
6847+
*
6848+
* ACL2 proofs:
6849+
* ------------
6850+
* 1) q is a natural number. (ndivmod-quotient-natp)
6851+
* 2) r is a natural number. (ndivmod-remainder-natp)
6852+
* 3) a = q * b + r (ndivmod-q*b+r==a)
6853+
* 4) r < b (ndivmod-remainder-<-b)
67856854
*/
67866855
static void
6787-
_mpd_qbarrett_divmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
6788-
uint32_t *status)
6856+
_mpd_base_ndivmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
6857+
uint32_t *status)
67896858
{
67906859
mpd_context_t workctx;
67916860
mpd_t *qq = q, *rr = r;
67926861
mpd_t aa, bb;
67936862
int k;
67946863

6795-
mpd_maxcontext(&workctx);
67966864
_mpd_copy_shared(&aa, a);
67976865
_mpd_copy_shared(&bb, b);
67986866

@@ -6814,41 +6882,68 @@ _mpd_qbarrett_divmod(mpd_t *q, mpd_t *r, const mpd_t *a, const mpd_t *b,
68146882
}
68156883
}
68166884

6817-
/* maximum length of q + 3 digits */
6818-
workctx.prec = aa.digits - bb.digits + 1 + 3;
6819-
/* we get the reciprocal with precision maxlen(q) + 3 */
6885+
mpd_maxcontext(&workctx);
6886+
6887+
/* Let prec := adigits - bdigits + 4 */
6888+
workctx.prec = a->digits - b->digits + 1 + 3;
6889+
if (a->digits > MPD_MAX_PREC || workctx.prec > MPD_MAX_PREC) {
6890+
*status |= MPD_Division_impossible;
6891+
goto nanresult;
6892+
}
6893+
6894+
/* Let x := _mpd_qreciprocal(b, prec)
6895+
* Then x is bounded by:
6896+
* 1) 1/b - 10**(-prec - bdigits) < x < 1/b + 10**(-prec - bdigits)
6897+
* 2) 1/b - 10**(-adigits - 4) < x < 1/b + 10**(-adigits - 4)
6898+
*/
68206899
_mpd_qreciprocal(rr, &bb, &workctx, &workctx.status);
68216900

6822-
mpd_qmul(qq, &aa, rr, &workctx, &workctx.status);
6901+
/* Get an estimate for the quotient. Let q := a * x
6902+
* Then q is bounded by:
6903+
* 3) a/b - 10**-4 < q < a/b + 10**-4
6904+
*/
6905+
_mpd_qmul(qq, &aa, rr, &workctx, &workctx.status);
6906+
/* Truncate q to an integer:
6907+
* 4) a/b - 2 < trunc(q) < a/b + 1
6908+
*/
68236909
mpd_qtrunc(qq, qq, &workctx, &workctx.status);
68246910

68256911
workctx.prec = aa.digits + 3;
6826-
/* get the remainder */
6827-
mpd_qmul(rr, &bb, qq, &workctx, &workctx.status);
6828-
mpd_qsub(rr, &aa, rr, &workctx, &workctx.status);
6912+
workctx.emax = MPD_MAX_EMAX + 3;
6913+
workctx.emin = MPD_MIN_EMIN - 3;
6914+
/* Multiply the estimate for q by b:
6915+
* 5) a - 2 * b < trunc(q) * b < a + b
6916+
*/
6917+
_mpd_qmul(rr, &bb, qq, &workctx, &workctx.status);
6918+
/* Get the estimate for r such that a = q * b + r. */
6919+
_mpd_qsub_exact(rr, &aa, rr, &workctx, &workctx.status);
68296920

6830-
/* Fix the result. Algorithm from: Karl Hasselstrom, Fast Division of Large Integers */
6921+
/* Fix the result. At this point -b < r < 2*b, so the correction loop
6922+
takes at most one iteration. */
68316923
for (k = 0;; k++) {
6832-
if (mpd_isspecial(rr)) {
6924+
if (mpd_isspecial(qq) || mpd_isspecial(rr)) {
68336925
*status |= (workctx.status&MPD_Errors);
68346926
goto nanresult;
68356927
}
6836-
if (k > 2) {
6837-
mpd_err_warn("libmpdec: internal error in " /* GCOV_NOT_REACHED */
6838-
"_mpd_qbarrett_divmod: please report"); /* GCOV_NOT_REACHED */
6839-
*status |= MPD_Invalid_operation; /* GCOV_NOT_REACHED */
6840-
goto nanresult; /* GCOV_NOT_REACHED */
6928+
if (k > 2) { /* Allow two iterations despite the proof. */
6929+
mpd_err_warn("libmpdec: internal error in " /* GCOV_NOT_REACHED */
6930+
"_mpd_base_ndivmod: please report"); /* GCOV_NOT_REACHED */
6931+
*status |= MPD_Invalid_operation; /* GCOV_NOT_REACHED */
6932+
goto nanresult; /* GCOV_NOT_REACHED */
68416933
}
6934+
/* r < 0 */
68426935
else if (_mpd_cmp(&zero, rr) == 1) {
6843-
mpd_qadd(rr, rr, &bb, &workctx, &workctx.status);
6844-
mpd_qadd(qq, qq, &minus_one, &workctx, &workctx.status);
6936+
_mpd_qadd_exact(rr, rr, &bb, &workctx, &workctx.status);
6937+
_mpd_qadd_exact(qq, qq, &minus_one, &workctx, &workctx.status);
68456938
}
6939+
/* 0 <= r < b */
68466940
else if (_mpd_cmp(rr, &bb) == -1) {
68476941
break;
68486942
}
6943+
/* r >= b */
68496944
else {
6850-
mpd_qsub(rr, rr, &bb, &workctx, &workctx.status);
6851-
mpd_qadd(qq, qq, &one, &workctx, &workctx.status);
6945+
_mpd_qsub_exact(rr, rr, &bb, &workctx, &workctx.status);
6946+
_mpd_qadd_exact(qq, qq, &one, &workctx, &workctx.status);
68526947
}
68536948
}
68546949

0 commit comments

Comments
 (0)