Raymond Toy pushed to branch rtoy-print-using-ryu at cmucl / cmucl
Commits:
-
f65ff9eb
by Raymond Toy at 2026-05-25T17:34:15-07:00
-
63c190db
by Raymond Toy at 2026-05-25T17:54:42-07:00
-
06ea24af
by Raymond Toy at 2026-05-26T07:22:37-07:00
-
a5b2bbcc
by Raymond Toy at 2026-05-26T07:37:03-07:00
-
af544ca1
by Raymond Toy at 2026-05-26T07:38:14-07:00
3 changed files:
Changes:
| ... | ... | @@ -885,40 +885,57 @@ |
| 885 | 885 | ;;; denormalized or underflows to 0.
|
| 886 | 886 | ;;;
|
| 887 | 887 | (defun scale-float-maybe-underflow (x exp)
|
| 888 | + (declare (type (or single-float double-float) x)
|
|
| 889 | + (fixnum exp))
|
|
| 888 | 890 | (multiple-value-bind (sig old-exp)
|
| 889 | - (integer-decode-float x)
|
|
| 891 | + (integer-decode-float x)
|
|
| 890 | 892 | (let* ((digits (float-digits x))
|
| 893 | + (1+digits (1+ digits))
|
|
| 891 | 894 | (new-exp (+ exp old-exp digits
|
| 892 | 895 | (etypecase x
|
| 893 | 896 | (single-float vm:single-float-bias)
|
| 894 | 897 | (double-float vm:double-float-bias))))
|
| 895 | 898 | (sign (if (minusp (float-sign x)) 1 0)))
|
| 896 | 899 | (cond
|
| 897 | - ((< new-exp
|
|
| 898 | - (etypecase x
|
|
| 899 | - (single-float vm:single-float-normal-exponent-min)
|
|
| 900 | - (double-float vm:double-float-normal-exponent-min)))
|
|
| 901 | - (when (vm:current-float-trap :inexact)
|
|
| 902 | - (error 'floating-point-inexact :operation 'scale-float
|
|
| 903 | - :operands (list x exp)))
|
|
| 904 | - (when (vm:current-float-trap :underflow)
|
|
| 905 | - (error 'floating-point-underflow :operation 'scale-float
|
|
| 906 | - :operands (list x exp)))
|
|
| 907 | - (let ((shift (1- new-exp)))
|
|
| 908 | - ;; Is it necessary to have this IF here? Is there any case
|
|
| 909 | - ;; where (ash sig shift) won't return 0 when
|
|
| 910 | - ;; shift < -(digits-1)?
|
|
| 911 | - (if (< shift (- (1- digits)))
|
|
| 900 | + ((< new-exp
|
|
| 901 | + (etypecase x
|
|
| 902 | + (single-float vm:single-float-normal-exponent-min)
|
|
| 903 | + (double-float vm:double-float-normal-exponent-min)))
|
|
| 904 | + (when (vm:current-float-trap :inexact)
|
|
| 905 | + (error 'floating-point-inexact :operation 'scale-float
|
|
| 906 | + :operands (list x exp)))
|
|
| 907 | + (when (vm:current-float-trap :underflow)
|
|
| 908 | + (error 'floating-point-underflow :operation 'scale-float
|
|
| 909 | + :operands (list x exp)))
|
|
| 910 | + ;; To round correctly, let the hardware multiplier do the
|
|
| 911 | + ;; rounding: build a normal float whose stored exponent is
|
|
| 912 | + ;; bumped up by 1+DIGITS (which puts it safely in the normal
|
|
| 913 | + ;; range), then multiply by 2^-(1+DIGITS). The multiplier is
|
|
| 914 | + ;; an exact power of two, so the multiplication is exact
|
|
| 915 | + ;; apart from the unavoidable rounding step that expresses
|
|
| 916 | + ;; the product as a denormal, which the FPU performs in the
|
|
| 917 | + ;; current rounding mode. If the bumped exponent is zero or
|
|
| 918 | + ;; negative the bumped float would itself be a denormal --
|
|
| 919 | + ;; losing the implicit 1 bit of SIG -- so handle that case
|
|
| 920 | + ;; explicitly by returning signed zero.
|
|
| 921 | + (let ((bumped-exp (+ new-exp 1+digits)))
|
|
| 922 | + (cond
|
|
| 923 | + ((<= bumped-exp 0)
|
|
| 912 | 924 | (etypecase x
|
| 913 | 925 | (single-float (single-from-bits sign 0 0))
|
| 914 | - (double-float (double-from-bits sign 0 0)))
|
|
| 926 | + (double-float (double-from-bits sign 0 0))))
|
|
| 927 | + (t
|
|
| 915 | 928 | (etypecase x
|
| 916 | - (single-float (single-from-bits sign 0 (ash sig shift)))
|
|
| 917 | - (double-float (double-from-bits sign 0 (ash sig shift)))))))
|
|
| 918 | - (t
|
|
| 919 | - (etypecase x
|
|
| 920 | - (single-float (single-from-bits sign new-exp sig))
|
|
| 921 | - (double-float (double-from-bits sign new-exp sig))))))))
|
|
| 929 | + (single-float
|
|
| 930 | + (* (single-from-bits sign bumped-exp sig)
|
|
| 931 | + (scale-float 1f0 (- 1+digits))))
|
|
| 932 | + (double-float
|
|
| 933 | + (* (double-from-bits sign bumped-exp sig)
|
|
| 934 | + (scale-float 1d0 (- 1+digits)))))))))
|
|
| 935 | + (t
|
|
| 936 | + (etypecase x
|
|
| 937 | + (single-float (single-from-bits sign new-exp sig))
|
|
| 938 | + (double-float (double-from-bits sign new-exp sig))))))))
|
|
| 922 | 939 | |
| 923 | 940 | |
| 924 | 941 | ;;; SCALE-FLOAT-MAYBE-OVERFLOW -- Internal
|
| ... | ... | @@ -1150,27 +1167,11 @@ |
| 1150 | 1167 | (format t "2: f0, f1 = ~A~%" f0)
|
| 1151 | 1168 | (format t " scale = ~A~%" scale)
|
| 1152 | 1169 | (format t "scale-float f0 = ~A~%" (scale-float f0 scale)))
|
| 1153 | - (let ((min-exponent
|
|
| 1154 | - ;; Compute the min (unbiased) exponent
|
|
| 1155 | - (ecase format
|
|
| 1156 | - (single-float
|
|
| 1157 | - (- vm:single-float-normal-exponent-min
|
|
| 1158 | - vm:single-float-bias
|
|
| 1159 | - vm:single-float-digits))
|
|
| 1160 | - (double-float
|
|
| 1161 | - (- vm:double-float-normal-exponent-min
|
|
| 1162 | - vm:double-float-bias
|
|
| 1163 | - vm:double-float-digits)))))
|
|
| 1164 | - ;; F0 is always between 0.5 and 1. If
|
|
| 1165 | - ;; SCALE is the min exponent, we have a
|
|
| 1166 | - ;; denormal number just less than the
|
|
| 1167 | - ;; least-positive float. We want to
|
|
| 1168 | - ;; return the least-positive-float so
|
|
| 1169 | - ;; multiply F0 by 2 (without adjusting
|
|
| 1170 | - ;; SCALE) to get the nearest float.
|
|
| 1171 | - (if (= scale min-exponent)
|
|
| 1172 | - (scale-float (* 2 f0) scale)
|
|
| 1173 | - (scale-float f0 scale))))))))
|
|
| 1170 | + ;; SCALE-FLOAT-MAYBE-UNDERFLOW correctly rounds
|
|
| 1171 | + ;; to nearest when constructing a denormal result,
|
|
| 1172 | + ;; so just call SCALE-FLOAT here without any
|
|
| 1173 | + ;; boundary-case fixup.
|
|
| 1174 | + (scale-float f0 scale))))))
|
|
| 1174 | 1175 | (floatit (bits)
|
| 1175 | 1176 | (let ((sign (if plusp 0 1)))
|
| 1176 | 1177 | (case format
|
| ... | ... | @@ -58,6 +58,8 @@ public domain. |
| 58 | 58 | * #463: `double-double-float` is missing comparison operations
|
| 59 | 59 | between `double-double-float` and `double-float`
|
| 60 | 60 | * #474: Add functions to print and parse C-style hex floats.
|
| 61 | + * #504: Do correct rounding in `scale-float-maybe-underflow`.
|
|
| 62 | + This was causing some denormals to be read incorrectly.
|
|
| 61 | 63 | * Other changes:
|
| 62 | 64 | * Improvements to the PCL implementation of CLOS:
|
| 63 | 65 | * Changes to building procedure:
|
| ... | ... | @@ -179,7 +179,9 @@ |
| 179 | 179 | (kernel::float-ratio-float (* 4 expo) 'double-float))
|
| 180 | 180 | (assert-equal least-positive-double-float
|
| 181 | 181 | (kernel::float-ratio-float (* 494/100 expo) 'double-float))
|
| 182 | - (assert-equal least-positive-double-float
|
|
| 182 | + ;; 988/100*10^-324 is very close to 2*least-positive (the exact ratio
|
|
| 183 | + ;; is 1.9997 * least-positive), so it rounds to 2*least-positive.
|
|
| 184 | + (assert-equal (* 2 least-positive-double-float)
|
|
| 183 | 185 | (kernel::float-ratio-float (* 988/100 expo) 'double-float)))))
|
| 184 | 186 |
|
| 185 | 187 | (define-test reader-error.small-single-floats
|
| ... | ... | @@ -678,8 +680,11 @@ |
| 678 | 680 | (frob cdiv.maxima-case
|
| 679 | 681 | #c(5.43d-10 1.13d-100)
|
| 680 | 682 | #c(1.2d-311 5.7d-312)
|
| 681 | - #c(3.691993880674614517999740937026568563794896024143749539711267954d301
|
|
| 682 | - -1.753697093319947872394996242210428954266103103602859195409591583d301)
|
|
| 683 | + ;; Compute the expected value using rational arithmetic after
|
|
| 684 | + ;; converting the complex numbers above to the equivalent
|
|
| 685 | + ;; complex rationals.
|
|
| 686 | + (/ (complex (rational 5.43d-10) (rational 1.13d-100))
|
|
| 687 | + (complex (rational 1.2d-311) (rational 5.7d-312)))
|
|
| 683 | 688 | 52)
|
| 684 | 689 | ;; 12
|
| 685 | 690 | ;;
|
| ... | ... | @@ -766,3 +771,85 @@ |
| 766 | 771 | (coerce y '(complex single-float)))
|
| 767 | 772 | x
|
| 768 | 773 | y)))
|
| 774 | + |
|
| 775 | +(define-test scale-float-underflow-rounding.single
|
|
| 776 | + (:tag :issues)
|
|
| 777 | + ;; SCALE-FLOAT into the denormal range must round to nearest, ties to
|
|
| 778 | + ;; even, instead of truncating the discarded bits. Each (X EXP BITS)
|
|
| 779 | + ;; triple gives a normal X, an exponent EXP to scale by, and the IEEE
|
|
| 780 | + ;; bits of the expected single-float result.
|
|
| 781 | + (ext:with-float-traps-masked (:underflow :inexact)
|
|
| 782 | + (dolist (case (list
|
|
| 783 | + ;; 1.7f0 * 2^-149: between denormals 1 and 2, closer to 2.
|
|
| 784 | + (list 1.7f0 -149 #x00000002)
|
|
| 785 | + ;; 1.5f0 * 2^-149: halfway between denormals 1 and 2;
|
|
| 786 | + ;; ties round to even -> 2.
|
|
| 787 | + (list 1.5f0 -149 #x00000002)
|
|
| 788 | + ;; 1.1f0 * 2^-149: closer to denormal 1.
|
|
| 789 | + (list 1.1f0 -149 #x00000001)
|
|
| 790 | + ;; 1.0001f0 * 2^-150: just above halfway between 0 and
|
|
| 791 | + ;; smallest denormal; rounds up to 1.
|
|
| 792 | + (list 1.0001f0 -150 #x00000001)
|
|
| 793 | + ;; 1.0f0 * 2^-150: exactly halfway between 0 and smallest
|
|
| 794 | + ;; denormal; ties round to even -> 0.
|
|
| 795 | + (list 1.0f0 -150 #x00000000)
|
|
| 796 | + ;; Largest single < 2 scaled by 2^-127: rounding carries
|
|
| 797 | + ;; into the implicit-1 position and produces the smallest
|
|
| 798 | + ;; normal number.
|
|
| 799 | + (list (kernel:make-single-float #x3fffffff)
|
|
| 800 | + -127 #x00800000)))
|
|
| 801 | + (destructuring-bind (x exp bits) case
|
|
| 802 | + (let ((result (scale-float x exp)))
|
|
| 803 | + (assert-equal bits (kernel:single-float-bits result)
|
|
| 804 | + x exp result))))))
|
|
| 805 | + |
|
| 806 | +(define-test scale-float-underflow-rounding.double
|
|
| 807 | + (:tag :issues)
|
|
| 808 | + ;; Like SCALE-FLOAT-UNDERFLOW-ROUNDING.SINGLE but for double-floats.
|
|
| 809 | + ;; Each (X EXP HI LO) gives a normal X, an exponent EXP, and the IEEE
|
|
| 810 | + ;; high and low bits of the expected double-float result.
|
|
| 811 | + (ext:with-float-traps-masked (:underflow :inexact)
|
|
| 812 | + (dolist (case (list
|
|
| 813 | + ;; 1.7d0 * 2^-1074: between denormals 1 and 2, closer to 2.
|
|
| 814 | + (list 1.7d0 -1074 0 2)
|
|
| 815 | + ;; 1.5d0 * 2^-1074: tie, rounds to even -> 2.
|
|
| 816 | + (list 1.5d0 -1074 0 2)
|
|
| 817 | + ;; 1.1d0 * 2^-1074: closer to denormal 1.
|
|
| 818 | + (list 1.1d0 -1074 0 1)
|
|
| 819 | + ;; 1.0001d0 * 2^-1075: just above halfway, rounds up.
|
|
| 820 | + (list 1.0001d0 -1075 0 1)
|
|
| 821 | + ;; 1.0d0 * 2^-1075: tie at the bottom, rounds to even -> 0.
|
|
| 822 | + (list 1.0d0 -1075 0 0)
|
|
| 823 | + ;; Largest double < 2 scaled by 2^-1023: rounding carries
|
|
| 824 | + ;; into the implicit-1 position and produces the smallest
|
|
| 825 | + ;; normal number.
|
|
| 826 | + (list (kernel:make-double-float #x3fffffff #xffffffff)
|
|
| 827 | + -1023 #x00100000 0)))
|
|
| 828 | + (destructuring-bind (x exp hi lo) case
|
|
| 829 | + (let ((result (scale-float x exp)))
|
|
| 830 | + (assert-equal hi (kernel:double-float-high-bits result)
|
|
| 831 | + x exp result)
|
|
| 832 | + (assert-equal lo (kernel:double-float-low-bits result)
|
|
| 833 | + x exp result))))))
|
|
| 834 | + |
|
| 835 | +(define-test scale-float-underflow-rounding.reader-single
|
|
| 836 | + (:tag :issues)
|
|
| 837 | + ;; The reader uses FLOAT-RATIO-FLOAT, which calls SCALE-FLOAT and
|
|
| 838 | + ;; hence SCALE-FLOAT-MAYBE-UNDERFLOW on denormal results. Reading
|
|
| 839 | + ;; small float literals must therefore also round to nearest.
|
|
| 840 | + (ext:with-float-traps-masked (:underflow :inexact)
|
|
| 841 | + ;; 1.1e-44 is closer to 8 * least-positive-single-float than to 7.
|
|
| 842 | + (assert-equal #x00000008
|
|
| 843 | + (kernel:single-float-bits 1.1e-44))
|
|
| 844 | + ;; 1.121e-44 (essentially the IEEE representation of 1.1e-44) reads
|
|
| 845 | + ;; as the same denormal.
|
|
| 846 | + (assert-equal #x00000008
|
|
| 847 | + (kernel:single-float-bits 1.121e-44))))
|
|
| 848 | + |
|
| 849 | +(define-test scale-float-underflow-rounding.reader-double
|
|
| 850 | + (:tag :issues)
|
|
| 851 | + ;; Like the single-float reader test, in the double denormal range.
|
|
| 852 | + ;; 1.1d-322 is closest to denormal #x16 (= 22).
|
|
| 853 | + (ext:with-float-traps-masked (:underflow :inexact)
|
|
| 854 | + (assert-equal 0 (kernel:double-float-high-bits 1.1d-322))
|
|
| 855 | + (assert-equal #x16 (kernel:double-float-low-bits 1.1d-322)))) |