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 Integrate Ryu printer into ~F and ~G. Same idea as for ~E. Dispatch to Ryu if the number is a single or double-float and if *use-ryu-printer* is T. Fixed an issue with ~F and ~G where the default value for k is NIL. This is different for ~E where the default is k=1. Updated output-float-aux (print and friends) to use Ryu too. Let's compile ryu-print before print. Tests added for format using ryu. - - - - - 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: ===================================== src/code/format.lisp ===================================== @@ -1583,9 +1583,9 @@ ;; DOUBLE-DOUBLE-FLOAT) always falls through to the B&D path. (cond ((and lisp::*use-ryu-printer* - (and k (zerop k)) + (or (null k) (zerop k)) (typep number '(or single-float double-float))) - (format-fixed-ryu stream number w d k ovf pad atsign)) + (format-fixed-ryu stream number w d (or k 0) ovf pad atsign)) (t (format-fixed-aux-bd stream number w d k ovf pad atsign)))) @@ -1971,7 +1971,7 @@ (prin1 number stream) nil) (t - (write-string (lisp::format-g number w d e k ovf pad marker atsign) + (write-string (lisp::format-g number w d e (or k 1) ovf pad marker atsign) stream))) (values)) ===================================== src/code/print.lisp ===================================== @@ -2001,6 +2001,67 @@ radix-R. If you have a power-list then pass it in as PL." (defun output-float-aux (x stream e-min e-max) + ;; Dispatch to either the Burger and Dybvig implementation or the + ;; Ryu-based implementation. The Ryu code only handles single- and + ;; double-float values, so any other float type (notably + ;; DOUBLE-DOUBLE-FLOAT) always falls through to the B&D path. + (cond + ((and *use-ryu-printer* + (typep x '(or single-float double-float))) + (output-float-ryu x stream e-min e-max)) + (t + (output-float-aux-bd x stream e-min e-max)))) + +(defun output-float-ryu (x stream e-min e-max) + "Ryu-based implementation of free-format float printing. Uses + FLOAT-TO-STRING (D2S/F2S) to obtain the shortest round-tripping digit + string, then dispatches on the exponent to either free or exponential + notation, matching the layout produced by OUTPUT-FLOAT-AUX-BD." + (multiple-value-bind (mantissa actual-exp) + (parsed-exp-form (float-to-string x)) + ;; MANTISSA is "d.ddd"; strip the decimal point so STRING is just + ;; the digit run, matching the second value of FLONUM-TO-DIGITS. + ;; The B&D convention is that the value equals 0.STRING * 10^E, + ;; so E = ACTUAL-EXP + 1 (mantissa "d.ddd" has the point one + ;; position right of the implicit "0."). + (let* ((dot-pos (position #\. mantissa)) + (string (if dot-pos + (concatenate 'string + (subseq mantissa 0 dot-pos) + (subseq mantissa (1+ dot-pos))) + mantissa)) + (e (1+ actual-exp))) + (cond + ((< e-min e e-max) + ;; free format + (cond ((plusp e) + (write-string string stream :end (min (length string) e)) + (dotimes (i (- e (length string))) + (write-char #\0 stream)) + (write-char #\. stream) + (write-string string stream :start (min (length string) e)) + (when (<= (length string) e) + (write-char #\0 stream)) + (print-float-exponent x 0 stream)) + (t + (write-string "0." stream) + (dotimes (i (- e)) + (write-char #\0 stream)) + (write-string string stream) + (print-float-exponent x 0 stream)))) + (t + ;; Exponential format + (write-string string stream :end 1) + (write-char #\. stream) + (write-string string stream :start 1) + ;; CLHS 22.1.3.1.3 says at least one digit must be printed + ;; after the decimal point. + (when (= (length string) 1) + (write-char #\0 stream)) + (print-float-exponent x (1- e) stream)))))) + +(defun output-float-aux-bd (x stream e-min e-max) + "Burger and Dybvig based implementation of free-format float printing." (multiple-value-bind (e string) (flonum-to-digits x) (cond ===================================== src/i18n/locale/cmucl.pot ===================================== @@ -7412,6 +7412,71 @@ msgstr "" msgid "Trying to output binary data to a text stream." msgstr "" +#: src/code/ryu-print.lisp +msgid "" +"When non-NIL, format directives ~E, ~F, ~G and the float printer used\n" +" by PRINC/PRIN1/PRINT/WRITE route through the Ryu-based formatter\n" +" (FORMAT-E, FORMAT-F, FORMAT-G, and the shortest-form D2S/F2S\n" +" routines). When NIL, the original Burger and Dybvig based code is\n" +" used. Some cases (notably ~F with non-zero scale factor K, and\n" +" printing of DOUBLE-DOUBLE-FLOAT values) always fall back to the\n" +" Burger and Dybvig code regardless of this variable." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "Maximum precision (fractional digits for d2fixed." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "Buffer size for d2fixed." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "Lisp interface to Ryu d2fixed routine (specically d2fixed_buffered)" +msgstr "" + +#: src/code/ryu-print.lisp +msgid "Lisp interface to Ryu d2exp (specifically d2exp-buffered)." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "Lisp interface to Ryu d2s (specifically d2s_buffered" +msgstr "" + +#: src/code/ryu-print.lisp +msgid "" +"Convert F, a single-float or double-float, to a string of the shortest\n" +" form." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "" +"Number of decimal digits in N. N is the absolute value of a\n" +" double-float's exponent, so 0 <= n <= 324." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "" +"Write MANTISSA-TEXT to STREAM, shifting the decimal point so that\n" +" the new dot lands K digits in from the start of the digit string.\n" +" MANTISSA-TEXT is 'D' or 'D.DDDD' from d2s/d2exp.\n" +" DROP-LEADING-ZERO-P controls whether the leading '0' before the dot\n" +" is emitted when the result starts with '0.'. K=0 puts the dot\n" +" before all digits; K=1 leaves it at the original position." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "" +"Apply width/pad/overflow rules. FIELD-WRITER is a thunk of zero\n" +" arguments that writes the field characters to STREAM." +msgstr "" + +#: src/code/ryu-print.lisp +msgid "" +"Returns two values for the float VALUE: T if the value is negative and\n" +" the double-float absolute value of VALUE." +msgstr "" + #: src/code/print.lisp msgid "" "If true, all objects will printed readably. If readably printing is\n" @@ -7711,6 +7776,18 @@ msgstr "" msgid "Print out a double-double to a string" msgstr "" +#: src/code/print.lisp +msgid "" +"Ryu-based implementation of free-format float printing. Uses\n" +" FLOAT-TO-STRING (D2S/F2S) to obtain the shortest round-tripping digit\n" +" string, then dispatches on the exponent to either free or exponential\n" +" notation, matching the layout produced by OUTPUT-FLOAT-AUX-BD." +msgstr "" + +#: src/code/print.lisp +msgid "Burger and Dybvig based implementation of free-format float printing." +msgstr "" + #: src/code/print.lisp msgid "Weak Pointer: " msgstr "" @@ -7787,71 +7864,6 @@ msgstr "" msgid "Cannot find ~S, so unicode support is not available" msgstr "" -#: src/code/ryu-print.lisp -msgid "" -"When non-NIL, format directives ~E, ~F, ~G and the float printer used\n" -" by PRINC/PRIN1/PRINT/WRITE route through the Ryu-based formatter\n" -" (FORMAT-E, FORMAT-F, FORMAT-G, and the shortest-form D2S/F2S\n" -" routines). When NIL, the original Burger and Dybvig based code is\n" -" used. Some cases (notably ~F with non-zero scale factor K, and\n" -" printing of DOUBLE-DOUBLE-FLOAT values) always fall back to the\n" -" Burger and Dybvig code regardless of this variable." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "Maximum precision (fractional digits for d2fixed." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "Buffer size for d2fixed." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "Lisp interface to Ryu d2fixed routine (specically d2fixed_buffered)" -msgstr "" - -#: src/code/ryu-print.lisp -msgid "Lisp interface to Ryu d2exp (specifically d2exp-buffered)." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "Lisp interface to Ryu d2s (specifically d2s_buffered" -msgstr "" - -#: src/code/ryu-print.lisp -msgid "" -"Convert F, a single-float or double-float, to a string of the shortest\n" -" form." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "" -"Number of decimal digits in N. N is the absolute value of a\n" -" double-float's exponent, so 0 <= n <= 324." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "" -"Write MANTISSA-TEXT to STREAM, shifting the decimal point so that\n" -" the new dot lands K digits in from the start of the digit string.\n" -" MANTISSA-TEXT is 'D' or 'D.DDDD' from d2s/d2exp.\n" -" DROP-LEADING-ZERO-P controls whether the leading '0' before the dot\n" -" is emitted when the result starts with '0.'. K=0 puts the dot\n" -" before all digits; K=1 leaves it at the original position." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "" -"Apply width/pad/overflow rules. FIELD-WRITER is a thunk of zero\n" -" arguments that writes the field characters to STREAM." -msgstr "" - -#: src/code/ryu-print.lisp -msgid "" -"Returns two values for the float VALUE: T if the value is negative and\n" -" the double-float absolute value of VALUE." -msgstr "" - #: src/code/pprint.lisp msgid "" "Insert an annotation into the pretty-printing stream STREAM.\n" ===================================== src/tools/worldbuild.lisp ===================================== @@ -156,8 +156,8 @@ "target:code/serve-event" "target:code/stream" "target:code/fd-stream" - "target:code/print" "target:code/ryu-print" + "target:code/print" "target:code/pprint" "target:code/format" "target:code/package" ===================================== src/tools/worldcom.lisp ===================================== @@ -237,8 +237,8 @@ (comf "target:code/save") (comf "target:code/stream") -(comf "target:code/print") (comf "target:code/ryu-print") +(comf "target:code/print") (comf "target:code/pprint") #-no-runtime (comf "target:code/pprint" :byte-compile t) (comf "target:code/pprint-loop") ===================================== tests/ryu.lisp ===================================== @@ -986,3 +986,75 @@ (assert-true (stringp (format nil "~G" 1/2))) (assert-true (stringp (format nil "~G" 1))))) +;;; ---------------------------------------------------------------------- +;;; Tests that the float printer used by PRIN1/PRINC/PRINT/WRITE +;;; (i.e. OUTPUT-FLOAT-AUX) dispatches through Ryu when +;;; *use-ryu-printer* is set. Both code paths should produce +;;; round-trippable strings. We don't assert character equality +;;; between the two paths because the shortest round-tripping digit +;;; sequence is allowed to differ in rounding of the tie case; we do +;;; assert each path round-trips. + +(define-test output-float.dispatch.round-trip + (:tag :output-float :dispatch) + (let ((values '(3.14d0 + -3.14d0 + 0.1d0 + 1.0d100 + 1.0d-100 + 1.0d0 + 3.14f0 + -3.14f0 + 0.1f0))) + (dolist (v values) + (let ((with-ryu + (let ((lisp::*use-ryu-printer* t)) + (prin1-to-string v))) + (without-ryu + (let ((lisp::*use-ryu-printer* nil)) + (prin1-to-string v)))) + (assert-equal v (read-from-string with-ryu) v with-ryu) + (assert-equal v (read-from-string without-ryu) v without-ryu))))) + +(define-test output-float.dispatch.princ-matches-prin1 + (:tag :output-float :dispatch) + ;; For floats, PRIN1 and PRINC produce the same string (floats don't + ;; have escape characters). This should hold under both code paths. + (let ((lisp::*use-ryu-printer* t)) + (assert-equal (prin1-to-string 3.14d0) + (princ-to-string 3.14d0)) + (assert-equal (prin1-to-string 3.14f0) + (princ-to-string 3.14f0)))) + +(define-test output-float.dispatch.zero + (:tag :output-float :dispatch) + ;; Zero is handled by OUTPUT-FLOAT itself (not OUTPUT-FLOAT-AUX), so + ;; the dispatch is never reached. Still, make sure both settings + ;; produce identical output. + (let ((with-ryu + (let ((lisp::*use-ryu-printer* t)) + (prin1-to-string 0.0d0))) + (without-ryu + (let ((lisp::*use-ryu-printer* nil)) + (prin1-to-string 0.0d0)))) + (assert-equal without-ryu with-ryu))) + +(define-test output-float.dispatch.special-values + (:tag :output-float :dispatch) + ;; Infinity and NaN are handled in OUTPUT-FLOAT itself, before the + ;; OUTPUT-FLOAT-AUX dispatch, so the Ryu path is not exercised. + ;; Verify no error. + (let ((lisp::*use-ryu-printer* t)) + (assert-true (stringp (prin1-to-string + ext:double-float-positive-infinity))))) + +(define-test output-float.dispatch.double-double + (:tag :output-float :dispatch) + ;; DOUBLE-DOUBLE-FLOAT is handled in OUTPUT-FLOAT itself when the + ;; feature is enabled (currently behind #+(and nil double-double), + ;; i.e. inactive), so it actually reaches OUTPUT-FLOAT-AUX. Confirm + ;; the type-guard in the dispatch wrapper sends it to the B&D path. + #+double-double + (let ((lisp::*use-ryu-printer* t)) + (assert-true (stringp (prin1-to-string 1w0))))) + View it on GitLab: https://gitlab.common-lisp.net/cmucl/cmucl/-/commit/688dc657b478b490021177d9... -- View it on GitLab: https://gitlab.common-lisp.net/cmucl/cmucl/-/commit/688dc657b478b490021177d9... You're receiving this email because of your account on gitlab.common-lisp.net. Manage all notifications: https://gitlab.common-lisp.net/-/profile/notifications | Help: https://gitlab.common-lisp.net/help