Raymond Toy pushed to branch rtoy-print-using-ryu at cmucl / cmucl

Commits:

3 changed files:

Changes:

  • src/code/ryu-print.lisp
    ... ... @@ -491,24 +491,79 @@
    491 491
                  (int-len     (max 1 (1+ exponent)))
    
    492 492
                  (shortest-d  (max 0 (- digit-count 1 exponent)))
    
    493 493
                  (sign-len    (if (or is-negative-p at-sign-p) 1 0))
    
    494
    -             (d-fit       (and w (- w sign-len int-len 1))))
    
    494
    +             (d-fit       (and w (- w sign-len int-len 1)))
    
    495
    +	     ;; When the magnitude is < 1 the integer part is a single
    
    496
    +	     ;; "0" that may be dropped (the leading zero is optional),
    
    497
    +	     ;; freeing one more slot for a fractional digit.  Compute
    
    498
    +	     ;; the fractional-digit count that exactly fills W under
    
    499
    +	     ;; that rule.
    
    500
    +	     (frac-fit (and w
    
    501
    +			    (if (plusp (1+ exponent))
    
    502
    +				(- w sign-len int-len 1)	; "[int]."
    
    503
    +				(- w sign-len 1)))))		; ".[frac]"
    
    495 504
     	(cond
    
    496 505
               ((or (null w) (>= d-fit shortest-d))
    
    497
    -	   ;; No width or the shortest form fits within a field width
    
    498
    -	   ;; of W.
    
    506
    +	   ;; No width, or the shortest round-trip form already fits
    
    507
    +	   ;; within a field width of W.  Emit it directly.
    
    499 508
                (emit-shortest stream mantissa exponent is-negative-p w
    
    500 509
                               overflowchar padchar at-sign-p))
    
    501
    -	  (overflowchar
    
    502
    -	   ;; Shortest form does not fit and OVERFLOWCHAR is set; fill
    
    503
    -	   ;; the field with overflow characters.
    
    510
    +	  ((and overflowchar (minusp frac-fit))
    
    511
    +	   ;; Not even the integer part and decimal point fit, and an
    
    512
    +	   ;; overflow character was supplied: fill the field.
    
    504 513
     	   (loop repeat w
    
    505 514
     		 do (write-char overflowchar stream)))
    
    506 515
     	  (t
    
    507
    -	   ;; Shortest form does not fit and no OVERFLOWCHAR; emit the
    
    508
    -	   ;; full shortest form, letting the field expand.  CLHS
    
    509
    -	   ;; 22.3.3.1 requires this.
    
    510
    -	   (emit-shortest stream mantissa exponent is-negative-p w
    
    511
    -			  overflowchar padchar at-sign-p)))))))
    
    516
    +	   ;; The shortest form does not fit in W.  D was not specified,
    
    517
    +	   ;; so round the value to the largest number of fractional
    
    518
    +	   ;; digits FRAC-FIT that fits, then display it.  Per CLHS
    
    519
    +	   ;; 22.3.3.1, if the fraction rounds away to nothing a single
    
    520
    +	   ;; zero digit must still appear after the decimal point.  We
    
    521
    +	   ;; therefore round at FRAC-FIT places (which may be 0) but
    
    522
    +	   ;; always show at least one fractional digit.
    
    523
    +	   (emit-rounded-to-width stream value (max 0 frac-fit)
    
    524
    +				  is-negative-p w
    
    525
    +				  overflowchar padchar at-sign-p)))))))
    
    526
    +
    
    527
    +(defun emit-rounded-to-width (stream value frac is-negative-p w
    
    528
    +			      overflowchar padchar at-sign-p)
    
    529
    +  "Round (the magnitude of) VALUE to FRAC fractional digits with
    
    530
    +   d2fixed and write it right-justified in a field of width W.  At
    
    531
    +   least one fractional digit is always shown: if FRAC is 0 the
    
    532
    +   rounded value is integral and a single \"0\" is appended after the
    
    533
    +   decimal point (CLHS 22.3.3.1).  The optional leading zero before
    
    534
    +   the decimal point is dropped when the field would otherwise
    
    535
    +   overflow W.  Used by the free-format ~F path when the shortest
    
    536
    +   representation does not fit in W."
    
    537
    +  (declare (type (or single-float double-float) value)
    
    538
    +	   (type (integer 0 *) frac))
    
    539
    +  (let* ((rounded   (d2fixed (float (abs value) 1d0) frac))
    
    540
    +	 ;; ROUNDED is "ddd" (frac=0) or "ddd.ddd" (frac>0).  Split it
    
    541
    +	 ;; into integer and fractional digit runs.
    
    542
    +	 (dot        (position #\. rounded))
    
    543
    +	 (int-part   (if dot (subseq rounded 0 dot) rounded))
    
    544
    +	 (frac-part  (if dot (subseq rounded (1+ dot)) ""))
    
    545
    +	 ;; Force at least one fractional digit.
    
    546
    +	 (frac-out   (if (zerop (length frac-part)) "0" frac-part))
    
    547
    +	 (sign-len   (if (or is-negative-p at-sign-p) 1 0))
    
    548
    +	 ;; The leading "0." may be shortened to "." when the magnitude
    
    549
    +	 ;; is < 1 (integer part is a single "0") and nonzero, and the
    
    550
    +	 ;; full field would not fit in W.
    
    551
    +	 (lpoint-droppable
    
    552
    +	   (and (string= int-part "0")
    
    553
    +		(not (zerop value))))
    
    554
    +	 (full-len   (+ sign-len (length int-part) 1 (length frac-out)))
    
    555
    +	 (drop-leading-zero-p
    
    556
    +	   (and lpoint-droppable w (> full-len w)))
    
    557
    +	 (field-len  (if drop-leading-zero-p (1- full-len) full-len)))
    
    558
    +    (flet ((write-field ()
    
    559
    +	     (cond (is-negative-p (write-char #\- stream))
    
    560
    +		   (at-sign-p     (write-char #\+ stream)))
    
    561
    +	     (unless drop-leading-zero-p
    
    562
    +	       (write-string int-part stream))
    
    563
    +	     (write-char #\. stream)
    
    564
    +	     (write-string frac-out stream)))
    
    565
    +      (declare (dynamic-extent #'write-field))
    
    566
    +      (pad-overflow stream field-len w overflowchar padchar #'write-field))))
    
    512 567
     
    
    513 568
     (defun format-f (value w d k overflowchar padchar at-sign-p)
    
    514 569
       (declare (type (or single-float double-float) value)
    

  • src/i18n/locale/cmucl.pot
    No preview for this file type
  • tests/ryu.lisp
    ... ... @@ -367,20 +367,17 @@
    367 367
       (assert-equal "       1.0"
    
    368 368
                     (lisp::format-f  1d0        10 nil 0 nil nil nil)))
    
    369 369
     
    
    370
    -(define-test format-f.d-nil-with-w-no-shrink
    
    370
    +(define-test format-f.d-nil-with-w-shrinks
    
    371 371
       (:tag :format-f)
    
    372
    -  ;; CLHS 22.3.3.1: when d is unspecified, the digit count is set so
    
    373
    -  ;; that the result reads back as an EQUAL float, with no extraneous
    
    374
    -  ;; trailing zeros.  This is the round-trip count.  When the result
    
    375
    -  ;; does not fit in w and no overflowchar is supplied, the field
    
    376
    -  ;; expands -- it does NOT shrink the digits, because doing so would
    
    377
    -  ;; produce a different float on read-back.  (Earlier ryu code did
    
    378
    -  ;; shrink here; that change was needed to pass ANSI FORMAT.F.5.)
    
    379
    -  (assert-equal "1.234567"
    
    372
    +  ;; d=nil with a tight width: CL rounds the value to the largest
    
    373
    +  ;; number of fractional digits that fits in W (matching the B&D
    
    374
    +  ;; reference output).  3.141592653589793 shrinks to fit; 1.234567
    
    375
    +  ;; with w=6 rounds to "1.2346".
    
    376
    +  (assert-equal "1.2346"
    
    380 377
                     (lisp::format-f  1.234567d0   6 nil 0 nil nil nil))
    
    381
    -  (assert-equal "3.141592653589793"
    
    378
    +  (assert-equal "3.14159265"
    
    382 379
                     (lisp::format-f  3.141592653589793d0 10 nil 0 nil nil nil))
    
    383
    -  (assert-equal "3.141592653589793"
    
    380
    +  (assert-equal "3.141593"
    
    384 381
                     (lisp::format-f  3.141592653589793d0  8 nil 0 nil nil nil)))
    
    385 382
     
    
    386 383
     (define-test format-f.d-nil-with-w-overflow
    
    ... ... @@ -755,10 +752,11 @@
    755 752
     
    
    756 753
     (define-test format-f.single-narrow-width-no-overflow-char
    
    757 754
       (:tag :format-f :single-float)
    
    758
    -  ;; CLHS 22.3.3.1: when the shortest form does not fit in the
    
    759
    -  ;; specified width and no overflow character is supplied, the field
    
    760
    -  ;; expands rather than truncating digits.  Round-trip and the
    
    761
    -  ;; "at least one digit after the decimal point" rule must both hold.
    
    755
    +  ;; ~F with unspecified d and a width too small for the shortest form:
    
    756
    +  ;; the value is rounded to the fractional digits that fit, but at
    
    757
    +  ;; least one fractional digit is always shown (CLHS 22.3.3.1).  With
    
    758
    +  ;; no overflow char the field expands when even that minimum
    
    759
    +  ;; doesn't fit.  1.0 -> "1.0".
    
    762 760
       (assert-equal "1.0"
    
    763 761
                     (lisp::format-f 1.0f0 2 nil 0 nil nil nil))
    
    764 762
       (assert-equal "1.0"
    
    ... ... @@ -767,6 +765,21 @@
    767 765
       (assert-equal "1.0"
    
    768 766
                     (lisp::format-f 1.0d0 2 nil 0 nil nil nil)))
    
    769 767
     
    
    768
    +(define-test format-f.d-nil-rounds-to-width-with-forced-zero
    
    769
    +  (:tag :format-f)
    
    770
    +  ;; ANSI FORMAT.F.45/F.47: ~F with unspecified d rounds the value to
    
    771
    +  ;; the fractional digits that fit in W, but always shows at least one
    
    772
    +  ;; fractional digit (a single "0" if the fraction rounds away).  The
    
    773
    +  ;; field expands past W when even that minimum doesn't fit and no
    
    774
    +  ;; overflow char is given.
    
    775
    +  ;; ~2f of 1.1 and 1.9 round to integers, forced ".0", overflow to 3.
    
    776
    +  (assert-equal "1.0" (lisp::format-f 1.1f0 2 nil 0 nil nil nil))
    
    777
    +  (assert-equal "2.0" (lisp::format-f 1.9f0 2 nil 0 nil nil nil))
    
    778
    +  ;; ~3f of 1e-6: rounds to .00 (leading zero dropped to fit width 3).
    
    779
    +  (assert-equal ".00" (lisp::format-f 1.0f-6 3 nil 0 nil nil nil))
    
    780
    +  ;; ~4f of 1e-6: .000.
    
    781
    +  (assert-equal ".000" (lisp::format-f 1.0f-6 4 nil 0 nil nil nil)))
    
    782
    +
    
    770 783
     (define-test format-f.single-narrow-width-with-overflow-char
    
    771 784
       (:tag :format-f :single-float)
    
    772 785
       ;; With overflowchar supplied, the field is filled with the overflow