Raymond Toy pushed to branch rtoy-print-using-ryu at cmucl / cmucl
Commits:
-
21d82127
by Raymond Toy at 2026-05-28T17:12:00-07:00
3 changed files:
Changes:
| ... | ... | @@ -358,6 +358,54 @@ |
| 358 | 358 | (declare (dynamic-extent #'write-field))
|
| 359 | 359 | (pad-overflow stream field-len w overflowchar padchar #'write-field))))
|
| 360 | 360 | |
| 361 | +(defun emit-exp-no-fraction (stream mantissa shown-exp int-digits
|
|
| 362 | + is-negative-p at-sign-p
|
|
| 363 | + w e overflowchar padchar exponentchar)
|
|
| 364 | + "Emit \"[sign][INT-DIGITS].[marker][exp-sign][exp]\" with no
|
|
| 365 | + fractional digits, right-justified in width W. MANTISSA is a
|
|
| 366 | + string of significant digits from d2s/d2exp, with or without an
|
|
| 367 | + internal dot; the integer digits are written from it (skipping the
|
|
| 368 | + dot if any), zero-padded to INT-DIGITS."
|
|
| 369 | + (declare (type simple-string mantissa)
|
|
| 370 | + (fixnum shown-exp int-digits)
|
|
| 371 | + (type (or null fixnum) w e))
|
|
| 372 | + (let* ((dotpos (position #\. mantissa))
|
|
| 373 | + (mant-len (length mantissa))
|
|
| 374 | + (sig-digits (mantissa-digit-count mantissa))
|
|
| 375 | + (exp-sign (if (minusp shown-exp) #\- #\+))
|
|
| 376 | + (exp-abs (abs shown-exp))
|
|
| 377 | + (exp-digits (count-decimal-digits exp-abs))
|
|
| 378 | + (exp-width (max (or e 1) exp-digits))
|
|
| 379 | + (exp-marker (or exponentchar #\d))
|
|
| 380 | + (sign-len (if (or is-negative-p at-sign-p) 1 0))
|
|
| 381 | + ;; sign + int + dot + marker + exp-sign + exp-width
|
|
| 382 | + (field-len (+ sign-len int-digits 3 exp-width)))
|
|
| 383 | + (flet ((write-field ()
|
|
| 384 | + (cond (is-negative-p (write-char #\- stream))
|
|
| 385 | + (at-sign-p (write-char #\+ stream)))
|
|
| 386 | + ;; Write up to sig-digits from mantissa (skipping the dot),
|
|
| 387 | + ;; then zero-pad if int-digits exceeds available.
|
|
| 388 | + (let ((available (min int-digits sig-digits)))
|
|
| 389 | + (cond ((null dotpos)
|
|
| 390 | + (write-string mantissa stream :start 0 :end available))
|
|
| 391 | + ((<= available dotpos)
|
|
| 392 | + (write-string mantissa stream :start 0 :end available))
|
|
| 393 | + (t
|
|
| 394 | + (write-string mantissa stream :start 0 :end dotpos)
|
|
| 395 | + (write-string mantissa stream
|
|
| 396 | + :start (1+ dotpos)
|
|
| 397 | + :end (min mant-len (1+ available))))))
|
|
| 398 | + (loop repeat (- int-digits sig-digits)
|
|
| 399 | + do (write-char #\0 stream))
|
|
| 400 | + (write-char #\. stream)
|
|
| 401 | + (write-char exp-marker stream)
|
|
| 402 | + (write-char exp-sign stream)
|
|
| 403 | + (loop repeat (- exp-width exp-digits)
|
|
| 404 | + do (write-char #\0 stream))
|
|
| 405 | + (princ exp-abs stream)))
|
|
| 406 | + (declare (dynamic-extent #'write-field))
|
|
| 407 | + (pad-overflow stream field-len w overflowchar padchar #'write-field))))
|
|
| 408 | + |
|
| 361 | 409 | (defun format-e (value w d e k overflowchar padchar exponentchar at-sign-p)
|
| 362 | 410 | (declare (type (or single-float double-float) value)
|
| 363 | 411 | (fixnum k)
|
| ... | ... | @@ -371,8 +419,22 @@ |
| 371 | 419 | (multiple-value-bind (mantissa exponent)
|
| 372 | 420 | (parsed-exp-form (d2exp abs-value
|
| 373 | 421 | (d2exp-precision d k)))
|
| 374 | - (format-e-string stream mantissa exponent is-negative-p
|
|
| 375 | - w e k overflowchar padchar exponentchar at-sign-p nil)))
|
|
| 422 | + (cond
|
|
| 423 | + ;; CLHS 22.3.3.2: with D specified, exactly d-(k-1)
|
|
| 424 | + ;; fractional digits appear after the dot. When that is 0
|
|
| 425 | + ;; (k >= d+1, plusp k), emit "[int].e[exp]" directly with
|
|
| 426 | + ;; no forced ".0" -- the d2exp result has been rounded to
|
|
| 427 | + ;; exactly the digits we need to show. The k=1, d=0 case
|
|
| 428 | + ;; is excluded: cmucl has always emitted "D.0eN" there and
|
|
| 429 | + ;; existing tests rely on it.
|
|
| 430 | + ((and (>= k 2) (>= k (1+ d)))
|
|
| 431 | + (emit-exp-no-fraction stream mantissa
|
|
| 432 | + (- exponent (1- k))
|
|
| 433 | + k is-negative-p at-sign-p
|
|
| 434 | + w e overflowchar padchar exponentchar))
|
|
| 435 | + (t
|
|
| 436 | + (format-e-string stream mantissa exponent is-negative-p
|
|
| 437 | + w e k overflowchar padchar exponentchar at-sign-p nil)))))
|
|
| 376 | 438 | (t
|
| 377 | 439 | (multiple-value-bind (mantissa exponent)
|
| 378 | 440 | (parsed-exp-form (float-to-string value))
|
| ... | ... | @@ -410,20 +472,10 @@ |
| 410 | 472 | ;; fall through to the old format-e-string path.
|
| 411 | 473 | (multiple-value-bind (one-digit shown-exp)
|
| 412 | 474 | (parsed-exp-form (d2exp abs-value 0))
|
| 413 | - (let* ((exp-sign (if (minusp shown-exp) #\- #\+))
|
|
| 414 | - (exp-abs (abs shown-exp))
|
|
| 415 | - (exp-digits (count-decimal-digits exp-abs))
|
|
| 416 | - (exp-width (max (or e 1) exp-digits))
|
|
| 417 | - (exp-marker (or exponentchar #\d)))
|
|
| 418 | - (cond (is-negative-p (write-char #\- stream))
|
|
| 419 | - (at-sign-p (write-char #\+ stream)))
|
|
| 420 | - (write-string one-digit stream)
|
|
| 421 | - (write-char #\. stream)
|
|
| 422 | - (write-char exp-marker stream)
|
|
| 423 | - (write-char exp-sign stream)
|
|
| 424 | - (loop repeat (- exp-width exp-digits)
|
|
| 425 | - do (write-char #\0 stream))
|
|
| 426 | - (princ exp-abs stream))))
|
|
| 475 | + (emit-exp-no-fraction stream one-digit shown-exp 1
|
|
| 476 | + is-negative-p at-sign-p
|
|
| 477 | + w e overflowchar padchar
|
|
| 478 | + exponentchar)))
|
|
| 427 | 479 | (t
|
| 428 | 480 | ;; k != 1 and no width fits: fall back to the
|
| 429 | 481 | ;; shortest form (may still emit a forced ".0";
|
| ... | ... | @@ -200,12 +200,15 @@ |
| 200 | 200 | |
| 201 | 201 | (define-test format-e.overflow-no-overflowchar
|
| 202 | 202 | (:tag :format-e)
|
| 203 | - ;; w too small AND no overflowchar -- emit the field at natural
|
|
| 204 | - ;; width, exceeding w. No truncation.
|
|
| 205 | - (assert-equal "3.14d+100"
|
|
| 203 | + ;; w too small AND no overflowchar -- emit the no-fractional-digit
|
|
| 204 | + ;; form "d.e+exp" (overflowing W), per CLHS 22.3.3.2 "single zero
|
|
| 205 | + ;; digit ... if the width w permits" (here it does not).
|
|
| 206 | + (assert-equal "3.d+100"
|
|
| 206 | 207 | (lisp::format-e 3.14d100 3 nil nil 1 nil nil #\d nil))
|
| 207 | - ;; Free-format too long for w with no overflowchar.
|
|
| 208 | - (assert-equal "3.14159265358979d+0"
|
|
| 208 | + ;; Free-format too long for w with no overflowchar -- same rule:
|
|
| 209 | + ;; drop to no-fractional-digit form rather than emitting the full
|
|
| 210 | + ;; multi-digit shortest representation.
|
|
| 211 | + (assert-equal "3.d+0"
|
|
| 209 | 212 | (lisp::format-e 3.14159265358979d0 5 nil nil 1 nil nil #\d nil)))
|
| 210 | 213 | |
| 211 | 214 | (define-test format-e.overflow-exponent-too-wide
|
| ... | ... | @@ -232,12 +235,14 @@ |
| 232 | 235 | |
| 233 | 236 | (define-test format-e.overflow-shrink-exhausts
|
| 234 | 237 | (:tag :format-e)
|
| 235 | - ;; w forces shrinking down to d_fit = 0, which still won't fit.
|
|
| 236 | - ;; Should produce overflow fill.
|
|
| 238 | + ;; w forces shrinking down to d_fit = nil (not even d=0 fits).
|
|
| 239 | + ;; With an overflow char, the field is filled.
|
|
| 237 | 240 | (assert-equal "****"
|
| 238 | 241 | (lisp::format-e 3.14159265358979d0 4 nil nil 1 #\* nil #\d nil))
|
| 239 | - ;; Same but no overflowchar -- emit at natural shortest width.
|
|
| 240 | - (assert-equal "3.14159265358979d+0"
|
|
| 242 | + ;; Without overflowchar, emit the no-fractional-digit form
|
|
| 243 | + ;; "d.e+exp" (overflowing W) rather than the full multi-digit
|
|
| 244 | + ;; shortest representation.
|
|
| 245 | + (assert-equal "3.d+0"
|
|
| 241 | 246 | (lisp::format-e 3.14159265358979d0 4 nil nil 1 nil nil #\d nil)))
|
| 242 | 247 | |
| 243 | 248 | (define-test format-e.overflow-with-padchar
|
| ... | ... | @@ -306,6 +311,24 @@ |
| 306 | 311 | (assert-equal "-1.e+0"
|
| 307 | 312 | (lisp::format-e -1.0f0 6 nil nil 1 nil nil #\e nil)))
|
| 308 | 313 | |
| 314 | +(define-test format-e.d-given-k-consumes-fraction
|
|
| 315 | + (:tag :format-e)
|
|
| 316 | + ;; ANSI FORMAT.E.19: with D specified, the number of fractional
|
|
| 317 | + ;; digits is exactly d-(k-1). When k >= d+1 (and k >= 2) that count
|
|
| 318 | + ;; is 0, and the output is "[int].e[exp]" with no forced ".0".
|
|
| 319 | + ;; d=2, k=3, value 0.05 -> "5.00E-2" rounded by d2exp -> "500.e-4"
|
|
| 320 | + ;; after the k-shift consumes the fractional digits.
|
|
| 321 | + (assert-equal "500.e-4"
|
|
| 322 | + (lisp::format-e 0.05f0 nil 2 nil 3 nil nil #\e nil))
|
|
| 323 | + (assert-equal "500.e-4"
|
|
| 324 | + (lisp::format-e 0.05d0 nil 2 nil 3 nil nil #\e nil))
|
|
| 325 | + ;; Negated: leading "-" sign included.
|
|
| 326 | + (assert-equal "-500.e-4"
|
|
| 327 | + (lisp::format-e -0.05d0 nil 2 nil 3 nil nil #\e nil))
|
|
| 328 | + ;; At-sign: explicit "+".
|
|
| 329 | + (assert-equal "+500.e-4"
|
|
| 330 | + (lisp::format-e 0.05d0 nil 2 nil 3 nil nil #\e t)))
|
|
| 331 | + |
|
| 309 | 332 | ;;; ~F tests
|
| 310 | 333 | (define-test format-f.basic
|
| 311 | 334 | (:tag :format-f)
|