reader: fix imprecision reading some flonums

Reading `1.0e45` produced a different (and less precise) result than
`1e35`. The problem was in the reader's fast path for simple flonum
conversions, where it converts the mantissa and exponent separately
and then combines them. 10^44 is not represented exactly as a flonum,
so there's imprecision when multiplicy it by 10 versus multiplying
1e45 by 1.

Closes #3548
This commit is contained in:
Matthew Flatt 2020-12-14 12:57:31 -07:00
parent 986c73244e
commit 0561d71e60
4 changed files with 56 additions and 46 deletions

View File

@ -2652,6 +2652,9 @@
(test 10.0 string->number (string-append "1" (make-string 8000 #\0) "/" "1" (make-string 7998 #\0) "#@0")) (test 10.0 string->number (string-append "1" (make-string 8000 #\0) "/" "1" (make-string 7998 #\0) "#@0"))
(test #f zero? (string->number "7.4109846876187e-323")) (test #f zero? (string->number "7.4109846876187e-323"))
;; Regression test to make sure prevision isn't lost by multiplying 10.0 times 1e44:
(test (exact->inexact #e1e45) string->number "1.0e45")
(test #t andmap (lambda (x) (and (>= x 0) (< x 10))) (map random '(10 10 10 10))) (test #t andmap (lambda (x) (and (>= x 0) (< x 10))) (map random '(10 10 10 10)))
(test (void) random-seed 5) (test (void) random-seed 5)
(test (begin (random-seed 23) (list (random 10) (random 20) (random 30))) (test (begin (random-seed 23) (list (random 10) (random 20) (random 30)))

View File

@ -56049,8 +56049,14 @@ static const char *startup_source =
"(let-values()(if(fx= sgn_0 1) 0.0(- 0.0)))" "(let-values()(if(fx= sgn_0 1) 0.0(- 0.0)))"
"(if(if(fixnum? n_0)(if(< n_0(expt 2 50))(> n_0(-(expt 2 50))) #f) #f)" "(if(if(fixnum? n_0)(if(< n_0(expt 2 50))(> n_0(-(expt 2 50))) #f) #f)"
"(let-values()" "(let-values()"
"(let-values(((exp_1)(+ exp_0(* sgn2_0 exp2_0)))" "(let-values(((exp_1)(+ exp_0(* sgn2_0 exp2_0))))"
"((m_0)(fx->fl(if(fx= sgn_0 -1)(fx- 0 n_0) n_0))))" "(if(if(fixnum? exp_1)"
"(if(fx<= radix_0 10)"
"(let-values()(fx<= -15 exp_1 15))"
"(let-values()(fx<= -12 exp_1 12)))"
" #f)"
"(let-values()"
"(let-values(((m_0)(fx->fl(if(fx= sgn_0 -1)(fx- 0 n_0) n_0))))"
"(if(eqv? exp_1 0)" "(if(eqv? exp_1 0)"
"(let-values() m_0)" "(let-values() m_0)"
"(if(not(fixnum? exp_1))" "(if(not(fixnum? exp_1))"
@ -56058,11 +56064,9 @@ static const char *startup_source =
"(let-values()" "(let-values()"
"(let-values(((fradix_0)(if(fx= radix_0 10) 10.0(fx->fl radix_0))))" "(let-values(((fradix_0)(if(fx= radix_0 10) 10.0(fx->fl radix_0))))"
"(if(fx< exp_1 0)" "(if(fx< exp_1 0)"
"(let-values()" "(let-values()(/ m_0(expt fradix_0(fx- 0 exp_1))))"
"(if(fx> exp_1(if(fx<= radix_0 10)(let-values() -300)(let-values() -240)))"
"(/ m_0(expt fradix_0(fx- 0 exp_1)))"
" #f))"
"(let-values()(* m_0(expt fradix_0 exp_1))))))))))" "(let-values()(* m_0(expt fradix_0 exp_1))))))))))"
"(let-values() #f))))"
"(let-values() #f)))))" "(let-values() #f)))))"
"(let-values() #f))))))" "(let-values() #f))))))"
"(define-values" "(define-values"

View File

@ -64606,22 +64606,25 @@
(if (< n_0 1125899906842624) (> n_0 -1125899906842624) #f) (if (< n_0 1125899906842624) (> n_0 -1125899906842624) #f)
#f) #f)
(let ((exp_1 (+ exp_0 (* sgn2_0 exp2_0)))) (let ((exp_1 (+ exp_0 (* sgn2_0 exp2_0))))
(let ((m_0 (if (if (fixnum? exp_1)
(unsafe-fx->fl (if (fx= sgn_0 -1) (fx- 0 n_0) n_0)))) (if (fx<= radix_0 10)
(let ((exp_2 exp_1)) (fx<= -15 exp_1 15)
(if (eqv? exp_2 0) (fx<= -12 exp_1 12))
#f)
(let ((m_0
(unsafe-fx->fl (if (fx= sgn_0 -1) (fx- 0 n_0) n_0))))
(if (eqv? exp_1 0)
m_0 m_0
(if (not (fixnum? exp_2)) (if (not (fixnum? exp_1))
#f #f
(let ((fradix_0 (let ((fradix_0
(if (fx= radix_0 10) (if (fx= radix_0 10)
10.0 10.0
(unsafe-fx->fl radix_0)))) (unsafe-fx->fl radix_0))))
(if (fx< exp_2 0) (if (fx< exp_1 0)
(if (fx> exp_2 (if (fx<= radix_0 10) -300 -240)) (/ m_0 (expt fradix_0 (fx- 0 exp_1)))
(/ m_0 (expt fradix_0 (fx- 0 exp_2))) (* m_0 (expt fradix_0 exp_1)))))))
#f) #f))
(* m_0 (expt fradix_0 exp_2)))))))))
#f))) #f)))
#f)))) #f))))
(define finish.1 (define finish.1

View File

@ -238,28 +238,27 @@
(n . < . (expt 2 50)) (n . < . (expt 2 50))
(n . > . (- (expt 2 50)))) (n . > . (- (expt 2 50))))
;; No loss of precision in mantissa from early flonum conversion ;; No loss of precision in mantissa from early flonum conversion
(let ([exp (+ exp (* sgn2 exp2))] (let ([exp (+ exp (* sgn2 exp2))])
[m (fx->fl (if (fx= sgn -1)
(fx- 0 n)
n))])
(cond (cond
[(eqv? exp 0) m] [(and (fixnum? exp)
[(not (fixnum? exp)) #f] (cond
[else [(radix . fx<= . 10) (fx<= -15 exp 15)]
(define fradix (if (fx= radix 10) [else (fx<= -12 exp 12)]))
10.0 ;; No loss of precision in radix^exponent as a flonum
(fx->fl radix))) (let ([m (fx->fl (if (fx= sgn -1)
(cond (fx- 0 n)
[(exp . fx< . 0) n))])
;; Stay well away from limits on the exponent to make (cond
;; sure there's still no loss of precision. We could [(eqv? exp 0) m]
;; use `(integer-length n)` to improve the bounds, [(not (fixnum? exp)) #f]
;; but this seems good enough for the common case. [else
(and (exp . fx> . (cond (define fradix (if (fx= radix 10)
[(radix . fx<= . 10) -300] 10.0
[else -240])) (fx->fl radix)))
(/ m (expt fradix (fx- 0 exp))))] (cond
[else (* m (expt fradix exp))])]))] [(exp . fx< . 0) (/ m (expt fradix (fx- 0 exp)))]
[else (* m (expt fradix exp))])]))]
[else #f]))]
[else #f])] [else #f])]
[else #f])) [else #f]))
@ -839,7 +838,7 @@
(try "#i+inf.f") (try "#i+inf.f")
(try "-inf.f") (try "-inf.f")
(try "#e+inf.0") (try "#e+inf.0")
(try "-inf.t") (when (extflonum-available?) (try "-inf.t"))
(try "10") (try "10")
(try "10.1") (try "10.1")
(try "1+2i") (try "1+2i")
@ -851,9 +850,9 @@
(try "#e#x+e#s+e") (try "#e#x+e#s+e")
(try "-e#l-e") (try "-e#l-e")
(try "#e#x+e#s+e@-e#l-e") (try "#e#x+e#s+e@-e#l-e")
(try "3.1415926535897932385t0") (when (extflonum-available?) (try "3.1415926535897932385t0"))
(try "+nan.0+1i") (try "+nan.0+1i")
(try "3.0t0") (when (extflonum-available?) (try "3.0t0"))
(try "+i") (try "+i")
(try "-i") (try "-i")
(try "#i3") (try "#i3")
@ -862,7 +861,7 @@
(try "1.2+i") (try "1.2+i")
(try "1/2+3") (try "1/2+3")
(try "1.2+3") (try "1.2+3")
(try "#i1.2t0+3i") (when (extflonum-available?) (try "#i1.2t0+3i"))
(try "#i-0") (try "#i-0")
(try "#i0") (try "#i0")
(try "-0#") (try "-0#")
@ -885,11 +884,12 @@
(try "1@+inf.0") (try "1@+inf.0")
(try "1/1@+inf.0") (try "1/1@+inf.0")
;(try "#d1/0+3.0i") ;(try "#d1/0+3.0i")
(try "3.0t0+1/0i") (when (extflonum-available?)
(try "1/0+3.0t0i") (try "3.0t0+1/0i")
(try "+inf.t0+1/0i") (try "1/0+3.0t0i")
(try "1/0+inf.t0i") (try "+inf.t0+1/0i")
(try "3.#t0") (try "1/0+inf.t0i")
(try "3.#t0"))
(try "-1-2i") (try "-1-2i")
(try "-4.242154731064108e-5-6.865001427422244e-5i") (try "-4.242154731064108e-5-6.865001427422244e-5i")
(try "1e300+1e300i") (try "1e300+1e300i")