summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
authorKevin B Kenny <kennykb@acm.org>2011-07-02 21:56:33 (GMT)
committerKevin B Kenny <kennykb@acm.org>2011-07-02 21:56:33 (GMT)
commit59f60c354a264bd149bbe3248982f82c6ddd165a (patch)
tree32125ee4763fe4e82d3a6f93e806e3328c1fade5 /generic
parentab9198a4a8bfe2f03879c8fc4ae46b5f8eb64d60 (diff)
downloadtcl-59f60c354a264bd149bbe3248982f82c6ddd165a.zip
tcl-59f60c354a264bd149bbe3248982f82c6ddd165a.tar.gz
tcl-59f60c354a264bd149bbe3248982f82c6ddd165a.tar.bz2
Fix roundoff gaffe in bignum-to-double conversion [Bug 3349507]
Diffstat (limited to 'generic')
-rwxr-xr-xgeneric/tclStrToD.c58
-rw-r--r--generic/tclStubInit.c1
-rw-r--r--generic/tclTomMath.decls3
-rw-r--r--generic/tclTomMathDecls.h11
4 files changed, 63 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.
diff --git a/generic/tclStubInit.c b/generic/tclStubInit.c
index bc29ee6..d8a300c 100644
--- a/generic/tclStubInit.c
+++ b/generic/tclStubInit.c
@@ -481,6 +481,7 @@ TclTomMathStubs tclTomMathStubs = {
TclBN_s_mp_sub, /* 60 */
TclBN_mp_init_set_int, /* 61 */
TclBN_mp_set_int, /* 62 */
+ TclBN_mp_cnt_lsb, /* 63 */
};
static TclStubHooks tclStubHooks = {
diff --git a/generic/tclTomMath.decls b/generic/tclTomMath.decls
index ca883f6..191312f 100644
--- a/generic/tclTomMath.decls
+++ b/generic/tclTomMath.decls
@@ -217,3 +217,6 @@ declare 61 {
declare 62 {
int TclBN_mp_set_int(mp_int* a, unsigned long i)
}
+declare 63 {
+ int TclBN_mp_cnt_lsb(mp_int* a)
+}
diff --git a/generic/tclTomMathDecls.h b/generic/tclTomMathDecls.h
index f072311..4d5515b 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
@@ -461,6 +462,11 @@ 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);
#endif
+#ifndef TclBN_mp_cnt_lsb_TCL_DECLARED
+#define TclBN_mp_cnt_lsb_TCL_DECLARED
+/* 63 */
+EXTERN int TclBN_mp_cnt_lsb(mp_int*a);
+#endif
typedef struct TclTomMathStubs {
int magic;
@@ -529,6 +535,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) (mp_int*a); /* 63 */
} TclTomMathStubs;
#ifdef __cplusplus
@@ -797,6 +804,10 @@ extern TclTomMathStubs *tclTomMathStubsPtr;
#define TclBN_mp_set_int \
(tclTomMathStubsPtr->tclBN_mp_set_int) /* 62 */
#endif
+#ifndef TclBN_mp_cnt_lsb
+#define TclBN_mp_cnt_lsb \
+ (tclTomMathStubsPtr->tclBN_mp_cnt_lsb) /* 63 */
+#endif
#endif /* defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) */