diff options
author | Kevin B Kenny <kennykb@acm.org> | 2011-07-02 22:36:45 (GMT) |
---|---|---|
committer | Kevin B Kenny <kennykb@acm.org> | 2011-07-02 22:36:45 (GMT) |
commit | ab7a6af8cae45f25825b7c3ac24edc9af86c9233 (patch) | |
tree | cfb063e941efc61b9d4aaf5f1b223dae502e9b76 /generic | |
parent | 8f8741ac3a12f301cee5f84fd38210ef0527106c (diff) | |
parent | 59f60c354a264bd149bbe3248982f82c6ddd165a (diff) | |
download | tcl-ab7a6af8cae45f25825b7c3ac24edc9af86c9233.zip tcl-ab7a6af8cae45f25825b7c3ac24edc9af86c9233.tar.gz tcl-ab7a6af8cae45f25825b7c3ac24edc9af86c9233.tar.bz2 |
Fix roundoff gaffe in bignum-to-double conversion [Bug 3349507]
Diffstat (limited to 'generic')
-rwxr-xr-x | generic/tclStrToD.c | 58 | ||||
-rw-r--r-- | generic/tclStubInit.c | 1 | ||||
-rw-r--r-- | generic/tclTomMath.decls | 3 | ||||
-rw-r--r-- | generic/tclTomMathDecls.h | 6 |
4 files changed, 58 insertions, 10 deletions
diff --git a/generic/tclStrToD.c b/generic/tclStrToD.c index 16f11d2..15bff3e 100755 --- a/generic/tclStrToD.c +++ b/generic/tclStrToD.c @@ -4563,12 +4563,13 @@ TclBignumToDouble( const mp_int *a) /* Integer to convert. */ { mp_int b; - int bits, shift, i; + int bits, shift, i, lsb; double r; + /* - * Determine how many bits we need, and extract that many from the input. - * Round to nearest unit in the last place. + * We need a 'mantBits'-bit significand. Determine what shift will + * give us that. */ bits = mp_count_bits(a); @@ -4580,17 +4581,54 @@ TclBignumToDouble( return -HUGE_VAL; } } - shift = mantBits + 1 - bits; + shift = mantBits - bits; + + /* + * If shift > 0, shift the significand left by the requisite number of + * bits. If shift == 0, the significand is already exactly 'mantBits' + * in length. If shift < 0, we will need to shift the significand right + * by the requisite number of bits, and round it. If the '1-shift' + * least significant bits are 0, but the 'shift'th bit is nonzero, + * then the significand lies exactly between two values and must be + * 'rounded to even'. + */ + mp_init(&b); - if (shift > 0) { + if (shift == 0) { + mp_copy(a, &b); + } else if (shift > 0) { mp_mul_2d(a, shift, &b); } else if (shift < 0) { - mp_div_2d(a, -shift, &b, NULL); - } else { - mp_copy(a, &b); + lsb = mp_cnt_lsb(a); + if (lsb == -1-shift) { + + /* + * Round to even + */ + + mp_div_2d(a, -shift, &b, NULL); + if (mp_isodd(&b)) { + if (b.sign == MP_ZPOS) { + mp_add_d(&b, 1, &b); + } else { + mp_sub_d(&b, 1, &b); + } + } + } else { + + /* + * Ordinary rounding + */ + + mp_div_2d(a, -1-shift, &b, NULL); + if (b.sign == MP_ZPOS) { + mp_add_d(&b, 1, &b); + } else { + mp_sub_d(&b, 1, &b); + } + mp_div_2d(&b, 1, &b, NULL); + } } - mp_add_d(&b, 1, &b); - mp_div_2d(&b, 1, &b, NULL); /* * Accumulate the result, one mp_digit at a time. diff --git a/generic/tclStubInit.c b/generic/tclStubInit.c index 054ece5..5b47e95 100644 --- a/generic/tclStubInit.c +++ b/generic/tclStubInit.c @@ -464,6 +464,7 @@ const TclTomMathStubs tclTomMathStubs = { TclBN_s_mp_sub, /* 60 */ TclBN_mp_init_set_int, /* 61 */ TclBN_mp_set_int, /* 62 */ + TclBN_mp_cnt_lsb, /* 63 */ }; static const TclStubHooks tclStubHooks = { diff --git a/generic/tclTomMath.decls b/generic/tclTomMath.decls index 5bf338e..29a6a03 100644 --- a/generic/tclTomMath.decls +++ b/generic/tclTomMath.decls @@ -218,3 +218,6 @@ declare 61 { declare 62 { int TclBN_mp_set_int(mp_int* a, unsigned long i) } +declare 63 { + int TclBN_mp_cnt_lsb(const mp_int* a) +} diff --git a/generic/tclTomMathDecls.h b/generic/tclTomMathDecls.h index 7df0d90..feaefb3 100644 --- a/generic/tclTomMathDecls.h +++ b/generic/tclTomMathDecls.h @@ -63,6 +63,7 @@ #define mp_cmp TclBN_mp_cmp #define mp_cmp_d TclBN_mp_cmp_d #define mp_cmp_mag TclBN_mp_cmp_mag +#define mp_cnt_lsb TclBN_mp_cnt_lsb #define mp_copy TclBN_mp_copy #define mp_count_bits TclBN_mp_count_bits #define mp_div TclBN_mp_div @@ -272,6 +273,8 @@ EXTERN int TclBN_s_mp_sub(mp_int *a, mp_int *b, mp_int *c); EXTERN int TclBN_mp_init_set_int(mp_int*a, unsigned long i); /* 62 */ EXTERN int TclBN_mp_set_int(mp_int*a, unsigned long i); +/* 63 */ +EXTERN int TclBN_mp_cnt_lsb(const mp_int*a); typedef struct TclTomMathStubs { int magic; @@ -340,6 +343,7 @@ typedef struct TclTomMathStubs { int (*tclBN_s_mp_sub) (mp_int *a, mp_int *b, mp_int *c); /* 60 */ int (*tclBN_mp_init_set_int) (mp_int*a, unsigned long i); /* 61 */ int (*tclBN_mp_set_int) (mp_int*a, unsigned long i); /* 62 */ + int (*tclBN_mp_cnt_lsb) (const mp_int*a); /* 63 */ } TclTomMathStubs; #ifdef __cplusplus @@ -482,6 +486,8 @@ extern const TclTomMathStubs *tclTomMathStubsPtr; (tclTomMathStubsPtr->tclBN_mp_init_set_int) /* 61 */ #define TclBN_mp_set_int \ (tclTomMathStubsPtr->tclBN_mp_set_int) /* 62 */ +#define TclBN_mp_cnt_lsb \ + (tclTomMathStubsPtr->tclBN_mp_cnt_lsb) /* 63 */ #endif /* defined(USE_TCL_STUBS) */ |