Raymond Toy pushed to branch rtoy-print-using-ryu at cmucl / cmucl
Commits:
-
688dc657
by Raymond Toy at 2026-05-25T06:22:05-07:00
6 changed files:
- src/code/format.lisp
- src/code/print.lisp
- src/i18n/locale/cmucl.pot
- src/tools/worldbuild.lisp
- src/tools/worldcom.lisp
- tests/ryu.lisp
Changes:
| ... | ... | @@ -1583,9 +1583,9 @@ |
| 1583 | 1583 | ;; DOUBLE-DOUBLE-FLOAT) always falls through to the B&D path.
|
| 1584 | 1584 | (cond
|
| 1585 | 1585 | ((and lisp::*use-ryu-printer*
|
| 1586 | - (and k (zerop k))
|
|
| 1586 | + (or (null k) (zerop k))
|
|
| 1587 | 1587 | (typep number '(or single-float double-float)))
|
| 1588 | - (format-fixed-ryu stream number w d k ovf pad atsign))
|
|
| 1588 | + (format-fixed-ryu stream number w d (or k 0) ovf pad atsign))
|
|
| 1589 | 1589 | (t
|
| 1590 | 1590 | (format-fixed-aux-bd stream number w d k ovf pad atsign))))
|
| 1591 | 1591 | |
| ... | ... | @@ -1971,7 +1971,7 @@ |
| 1971 | 1971 | (prin1 number stream)
|
| 1972 | 1972 | nil)
|
| 1973 | 1973 | (t
|
| 1974 | - (write-string (lisp::format-g number w d e k ovf pad marker atsign)
|
|
| 1974 | + (write-string (lisp::format-g number w d e (or k 1) ovf pad marker atsign)
|
|
| 1975 | 1975 | stream)))
|
| 1976 | 1976 | (values))
|
| 1977 | 1977 |
| ... | ... | @@ -2001,6 +2001,67 @@ radix-R. If you have a power-list then pass it in as PL." |
| 2001 | 2001 | |
| 2002 | 2002 | |
| 2003 | 2003 | (defun output-float-aux (x stream e-min e-max)
|
| 2004 | + ;; Dispatch to either the Burger and Dybvig implementation or the
|
|
| 2005 | + ;; Ryu-based implementation. The Ryu code only handles single- and
|
|
| 2006 | + ;; double-float values, so any other float type (notably
|
|
| 2007 | + ;; DOUBLE-DOUBLE-FLOAT) always falls through to the B&D path.
|
|
| 2008 | + (cond
|
|
| 2009 | + ((and *use-ryu-printer*
|
|
| 2010 | + (typep x '(or single-float double-float)))
|
|
| 2011 | + (output-float-ryu x stream e-min e-max))
|
|
| 2012 | + (t
|
|
| 2013 | + (output-float-aux-bd x stream e-min e-max))))
|
|
| 2014 | + |
|
| 2015 | +(defun output-float-ryu (x stream e-min e-max)
|
|
| 2016 | + "Ryu-based implementation of free-format float printing. Uses
|
|
| 2017 | + FLOAT-TO-STRING (D2S/F2S) to obtain the shortest round-tripping digit
|
|
| 2018 | + string, then dispatches on the exponent to either free or exponential
|
|
| 2019 | + notation, matching the layout produced by OUTPUT-FLOAT-AUX-BD."
|
|
| 2020 | + (multiple-value-bind (mantissa actual-exp)
|
|
| 2021 | + (parsed-exp-form (float-to-string x))
|
|
| 2022 | + ;; MANTISSA is "d.ddd"; strip the decimal point so STRING is just
|
|
| 2023 | + ;; the digit run, matching the second value of FLONUM-TO-DIGITS.
|
|
| 2024 | + ;; The B&D convention is that the value equals 0.STRING * 10^E,
|
|
| 2025 | + ;; so E = ACTUAL-EXP + 1 (mantissa "d.ddd" has the point one
|
|
| 2026 | + ;; position right of the implicit "0.").
|
|
| 2027 | + (let* ((dot-pos (position #\. mantissa))
|
|
| 2028 | + (string (if dot-pos
|
|
| 2029 | + (concatenate 'string
|
|
| 2030 | + (subseq mantissa 0 dot-pos)
|
|
| 2031 | + (subseq mantissa (1+ dot-pos)))
|
|
| 2032 | + mantissa))
|
|
| 2033 | + (e (1+ actual-exp)))
|
|
| 2034 | + (cond
|
|
| 2035 | + ((< e-min e e-max)
|
|
| 2036 | + ;; free format
|
|
| 2037 | + (cond ((plusp e)
|
|
| 2038 | + (write-string string stream :end (min (length string) e))
|
|
| 2039 | + (dotimes (i (- e (length string)))
|
|
| 2040 | + (write-char #\0 stream))
|
|
| 2041 | + (write-char #\. stream)
|
|
| 2042 | + (write-string string stream :start (min (length string) e))
|
|
| 2043 | + (when (<= (length string) e)
|
|
| 2044 | + (write-char #\0 stream))
|
|
| 2045 | + (print-float-exponent x 0 stream))
|
|
| 2046 | + (t
|
|
| 2047 | + (write-string "0." stream)
|
|
| 2048 | + (dotimes (i (- e))
|
|
| 2049 | + (write-char #\0 stream))
|
|
| 2050 | + (write-string string stream)
|
|
| 2051 | + (print-float-exponent x 0 stream))))
|
|
| 2052 | + (t
|
|
| 2053 | + ;; Exponential format
|
|
| 2054 | + (write-string string stream :end 1)
|
|
| 2055 | + (write-char #\. stream)
|
|
| 2056 | + (write-string string stream :start 1)
|
|
| 2057 | + ;; CLHS 22.1.3.1.3 says at least one digit must be printed
|
|
| 2058 | + ;; after the decimal point.
|
|
| 2059 | + (when (= (length string) 1)
|
|
| 2060 | + (write-char #\0 stream))
|
|
| 2061 | + (print-float-exponent x (1- e) stream))))))
|
|
| 2062 | + |
|
| 2063 | +(defun output-float-aux-bd (x stream e-min e-max)
|
|
| 2064 | + "Burger and Dybvig based implementation of free-format float printing."
|
|
| 2004 | 2065 | (multiple-value-bind (e string)
|
| 2005 | 2066 | (flonum-to-digits x)
|
| 2006 | 2067 | (cond
|
| ... | ... | @@ -156,8 +156,8 @@ |
| 156 | 156 | "target:code/serve-event"
|
| 157 | 157 | "target:code/stream"
|
| 158 | 158 | "target:code/fd-stream"
|
| 159 | - "target:code/print"
|
|
| 160 | 159 | "target:code/ryu-print"
|
| 160 | + "target:code/print"
|
|
| 161 | 161 | "target:code/pprint"
|
| 162 | 162 | "target:code/format"
|
| 163 | 163 | "target:code/package"
|
| ... | ... | @@ -237,8 +237,8 @@ |
| 237 | 237 | (comf "target:code/save")
|
| 238 | 238 | |
| 239 | 239 | (comf "target:code/stream")
|
| 240 | -(comf "target:code/print")
|
|
| 241 | 240 | (comf "target:code/ryu-print")
|
| 241 | +(comf "target:code/print")
|
|
| 242 | 242 | (comf "target:code/pprint")
|
| 243 | 243 | #-no-runtime (comf "target:code/pprint" :byte-compile t)
|
| 244 | 244 | (comf "target:code/pprint-loop")
|
| ... | ... | @@ -986,3 +986,75 @@ |
| 986 | 986 | (assert-true (stringp (format nil "~G" 1/2)))
|
| 987 | 987 | (assert-true (stringp (format nil "~G" 1)))))
|
| 988 | 988 | |
| 989 | +;;; ----------------------------------------------------------------------
|
|
| 990 | +;;; Tests that the float printer used by PRIN1/PRINC/PRINT/WRITE
|
|
| 991 | +;;; (i.e. OUTPUT-FLOAT-AUX) dispatches through Ryu when
|
|
| 992 | +;;; *use-ryu-printer* is set. Both code paths should produce
|
|
| 993 | +;;; round-trippable strings. We don't assert character equality
|
|
| 994 | +;;; between the two paths because the shortest round-tripping digit
|
|
| 995 | +;;; sequence is allowed to differ in rounding of the tie case; we do
|
|
| 996 | +;;; assert each path round-trips.
|
|
| 997 | + |
|
| 998 | +(define-test output-float.dispatch.round-trip
|
|
| 999 | + (:tag :output-float :dispatch)
|
|
| 1000 | + (let ((values '(3.14d0
|
|
| 1001 | + -3.14d0
|
|
| 1002 | + 0.1d0
|
|
| 1003 | + 1.0d100
|
|
| 1004 | + 1.0d-100
|
|
| 1005 | + 1.0d0
|
|
| 1006 | + 3.14f0
|
|
| 1007 | + -3.14f0
|
|
| 1008 | + 0.1f0)))
|
|
| 1009 | + (dolist (v values)
|
|
| 1010 | + (let ((with-ryu
|
|
| 1011 | + (let ((lisp::*use-ryu-printer* t))
|
|
| 1012 | + (prin1-to-string v)))
|
|
| 1013 | + (without-ryu
|
|
| 1014 | + (let ((lisp::*use-ryu-printer* nil))
|
|
| 1015 | + (prin1-to-string v))))
|
|
| 1016 | + (assert-equal v (read-from-string with-ryu) v with-ryu)
|
|
| 1017 | + (assert-equal v (read-from-string without-ryu) v without-ryu)))))
|
|
| 1018 | + |
|
| 1019 | +(define-test output-float.dispatch.princ-matches-prin1
|
|
| 1020 | + (:tag :output-float :dispatch)
|
|
| 1021 | + ;; For floats, PRIN1 and PRINC produce the same string (floats don't
|
|
| 1022 | + ;; have escape characters). This should hold under both code paths.
|
|
| 1023 | + (let ((lisp::*use-ryu-printer* t))
|
|
| 1024 | + (assert-equal (prin1-to-string 3.14d0)
|
|
| 1025 | + (princ-to-string 3.14d0))
|
|
| 1026 | + (assert-equal (prin1-to-string 3.14f0)
|
|
| 1027 | + (princ-to-string 3.14f0))))
|
|
| 1028 | + |
|
| 1029 | +(define-test output-float.dispatch.zero
|
|
| 1030 | + (:tag :output-float :dispatch)
|
|
| 1031 | + ;; Zero is handled by OUTPUT-FLOAT itself (not OUTPUT-FLOAT-AUX), so
|
|
| 1032 | + ;; the dispatch is never reached. Still, make sure both settings
|
|
| 1033 | + ;; produce identical output.
|
|
| 1034 | + (let ((with-ryu
|
|
| 1035 | + (let ((lisp::*use-ryu-printer* t))
|
|
| 1036 | + (prin1-to-string 0.0d0)))
|
|
| 1037 | + (without-ryu
|
|
| 1038 | + (let ((lisp::*use-ryu-printer* nil))
|
|
| 1039 | + (prin1-to-string 0.0d0))))
|
|
| 1040 | + (assert-equal without-ryu with-ryu)))
|
|
| 1041 | + |
|
| 1042 | +(define-test output-float.dispatch.special-values
|
|
| 1043 | + (:tag :output-float :dispatch)
|
|
| 1044 | + ;; Infinity and NaN are handled in OUTPUT-FLOAT itself, before the
|
|
| 1045 | + ;; OUTPUT-FLOAT-AUX dispatch, so the Ryu path is not exercised.
|
|
| 1046 | + ;; Verify no error.
|
|
| 1047 | + (let ((lisp::*use-ryu-printer* t))
|
|
| 1048 | + (assert-true (stringp (prin1-to-string
|
|
| 1049 | + ext:double-float-positive-infinity)))))
|
|
| 1050 | + |
|
| 1051 | +(define-test output-float.dispatch.double-double
|
|
| 1052 | + (:tag :output-float :dispatch)
|
|
| 1053 | + ;; DOUBLE-DOUBLE-FLOAT is handled in OUTPUT-FLOAT itself when the
|
|
| 1054 | + ;; feature is enabled (currently behind #+(and nil double-double),
|
|
| 1055 | + ;; i.e. inactive), so it actually reaches OUTPUT-FLOAT-AUX. Confirm
|
|
| 1056 | + ;; the type-guard in the dispatch wrapper sends it to the B&D path.
|
|
| 1057 | + #+double-double
|
|
| 1058 | + (let ((lisp::*use-ryu-printer* t))
|
|
| 1059 | + (assert-true (stringp (prin1-to-string 1w0)))))
|
|
| 1060 | + |