ANSI-TEST FORMAT.E.1 fails - FORMAT ~E is inconsistent with PRIN1

Bug #1854151 reported by Michał "phoe" Herda on 2019-11-27
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
SBCL
Undecided
Unassigned

Bug Description

CLHS 22.1.3.1.3 states that FORMAT ~E is equivalent to PRIN1 for (abs x) outside of the range [10^-3,10^7), apart from the sign of the exponent, which is always printed. This is always not the case on SBCL, as there are rounding differences between PRIN1 and FORMAT ~E.

The test body from ANSI-TEST FORMAT.E.1 that triggers this behaviour:
https://gitlab.common-lisp.net/ansi-test/ansi-test/blob/f72a59e1/printer/format/format-e.lsp#L12

Extracted test body to run on SBCL without ANSI-TEST loaded:

(defmacro formatter-call-to-string (fn &body args)
  (let ((stream (gensym "S")))
    `(with-output-to-string (,stream)
       (assert (equal (funcall ,fn ,stream ,@args 'a) '(a))))))

(let ((*print-readably* nil)
      (fn (formatter "~e")))
  (loop
    for i = (random 4)
    for type = (elt #(short-float single-float double-float long-float)
                    i)
    for min-value = (elt (vector least-positive-short-float
                                 least-positive-single-float
                                 least-positive-double-float
                                 least-positive-long-float)
                         i)
    for max-value = (elt (vector most-positive-short-float
                                 most-positive-single-float
                                 most-positive-double-float
                                 most-positive-long-float)
                         i)
    for x = (expt (coerce 10 type)
                  (if (= (random 2) 0)
                      (- -3 (random (- -3 (log min-value 10))))
                      (+ 7 (random (- (log max-value 10) 7)))))
    for s1 = (let ((*read-default-float-format* type)) (format nil "~e" x))
    for s2 = (let* ((*read-default-float-format* type)
                    (s (prin1-to-string x))
                    (exp-pos (1+ (position #\e s :test #'char-equal))))
               (if (> x 1)
                   (concatenate 'string
                                (subseq s 0 exp-pos) "+" (subseq s exp-pos))
                   s))
    for s3 = (let ((*read-default-float-format* type))
               (formatter-call-to-string fn x))
    repeat 1000
    when (and (or (< x 1/1000)
                  (>= x 10000000))
              (or (not (string= s1 s2))
                  (not (string= s1 s3))))
      collect (list x s1 s2 s3)))

Some examples of the inconsistency returned by the above test:

(4.181094272874668d-74 "4.1810942728746675e-74" "4.181094272874668e-74"
                       "4.1810942728746675e-74")
(2.9215631313531054d-93 "2.921563131353106e-93" "2.9215631313531054e-93"
                        "2.921563131353106e-93")
(6.893129496037147d-158 "6.893129496037146e-158" "6.893129496037147e-158"
                        "6.893129496037146e-158")
(4.0289505140665034d-274 "4.028950514066504e-274" "4.0289505140665034e-274"
                         "4.028950514066504e-274")
(4.9146875e-20 "4.9146876e-20" "4.9146875e-20" "4.9146876e-20")

Michał "phoe" Herda (phoe-krk) wrote :

More rationale from CLHS 22.3.3.2:

If all of w, d, and e are omitted, then the effect is to print the value using ordinary free-format exponential-notation output; prin1 uses a similar format for any non-zero number whose magnitude is less than 10^-3 or greater than or equal to 10^7. The only difference is that the ~E directive always prints a plus or minus sign in front of the exponent, while prin1 omits the plus sign if the exponent is non-negative.

Michał "phoe" Herda (phoe-krk) wrote :

Attaching a patch ported from ECL's FORMAT.

To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers