diff options
author | Kevin B Kenny <kennykb@acm.org> | 2011-07-02 21:56:33 (GMT) |
---|---|---|
committer | Kevin B Kenny <kennykb@acm.org> | 2011-07-02 21:56:33 (GMT) |
commit | 59f60c354a264bd149bbe3248982f82c6ddd165a (patch) | |
tree | 32125ee4763fe4e82d3a6f93e806e3328c1fade5 /generic/tclStrToD.c | |
parent | ab9198a4a8bfe2f03879c8fc4ae46b5f8eb64d60 (diff) | |
download | tcl-59f60c354a264bd149bbe3248982f82c6ddd165a.zip tcl-59f60c354a264bd149bbe3248982f82c6ddd165a.tar.gz tcl-59f60c354a264bd149bbe3248982f82c6ddd165a.tar.bz2 |
Fix roundoff gaffe in bignum-to-double conversion [Bug 3349507]
Diffstat (limited to 'generic/tclStrToD.c')
-rwxr-xr-x | generic/tclStrToD.c | 58 |
1 files changed, 48 insertions, 10 deletions
diff --git a/generic/tclStrToD.c b/generic/tclStrToD.c index 421657c..7e76727 100755 --- a/generic/tclStrToD.c +++ b/generic/tclStrToD.c @@ -4542,12 +4542,13 @@ TclBignumToDouble( 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); @@ -4559,17 +4560,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. |