Reading floats with large exponents no longer takes too much time.
authorStas Boukarev <stassats@gmail.com>
Fri, 31 Aug 2012 21:38:13 +0000 (01:38 +0400)
committerStas Boukarev <stassats@gmail.com>
Fri, 31 Aug 2012 21:38:13 +0000 (01:38 +0400)
Reading 1.0s1000000000000000 will attempt to construct a very large
bignum, which takes a considerable amount of time just to report in
the end that it cannot be represented as a float. Truncate the
exponent to manageable size before raising it.

Fixes lp#309070. Thanks to Paul Khuong for the help.

NEWS
src/code/reader.lisp
tests/reader.pure.lisp

diff --git a/NEWS b/NEWS
index 647df01..47be00e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,8 @@ changes relative to sbcl-1.0.58:
     source annotation of DISASSEMBLE output. Defaults to T.
   * optimization: CL:SORT and CL:STABLE-SORT of lists are faster and use fewer
     comparisons, particularly on almost-sorted inputs.
+  * bug fix: Reading floats with large exponents no longer takes too much time
+    before reporting that the exponent is too large.
   * documentation: a section on random number generation has been added to the
     manual. (lp#656839)
 
index 5d85c73..08a5286 100644 (file)
@@ -1409,6 +1409,24 @@ extended <package-name>::<form-in-package> syntax."
                                  (the index (* num base))))))))
        (setq number (+ num (* number base-power)))))))
 
+(defun truncate-exponent (exponent number divisor)
+  "Truncate exponent if it's too large for a float"
+  ;; Work with base-2 logarithms to avoid conversions to floats,
+  ;; and convert to base-10 conservatively at the end.
+  ;; Use the least positive float, because denormalized exponent
+  ;; can be larger than normalized.
+  (let* ((max-exponent (- (nth-value
+                           1
+                           (decode-float least-positive-long-float))))
+         (number-magnitude (integer-length number))
+         (divisor-magnitude (1- (integer-length divisor)))
+         (magnitude (- number-magnitude divisor-magnitude)))
+    (if (minusp exponent)
+        (max exponent (ceiling (- (+ max-exponent magnitude))
+                               (floor (log 10 2))))
+        (min exponent (floor (- max-exponent magnitude)
+                             (floor (log 10 2)))))))
+
 (defun make-float (stream)
   ;; Assume that the contents of *read-buffer* are a legal float, with nothing
   ;; else after it.
@@ -1469,6 +1487,7 @@ extended <package-name>::<form-in-package> syntax."
                                   (#\F 'single-float)
                                   (#\D 'double-float)
                                   (#\L 'long-float)))
+                  (exponent (truncate-exponent exponent number divisor))
                   (result (make-float-aux (* (expt 10 exponent) number)
                                           divisor float-format stream)))
              (return-from make-float
@@ -1481,7 +1500,8 @@ extended <package-name>::<form-in-package> syntax."
     (type-error (c)
       (error 'reader-impossible-number-error
              :error c :stream stream
-             :format-control "failed to build float"))))
+             :format-control "failed to build float from ~a"
+             :format-arguments (list (read-buffer-to-string))))))
 
 (defun make-ratio (stream)
   ;; Assume *READ-BUFFER* contains a legal ratio. Build the number from
index 7d29b18..04d9dda 100644 (file)
                   (read-from-string "cl::'foo")
                 (package-lock-violation ()
                   :violated!)))))
+
+(with-test (:name :bug-309070)
+  (with-timeout 10
+    (assert (raises-error? (read-from-string "10e10000000000000000000")
+                           sb-kernel:reader-impossible-number-error))))