Raymond Toy pushed to branch issue-474-print-parse-hex-floats at cmucl / cmucl
Commits:
-
6af9a683
by Raymond Toy at 2026-02-22T16:52:44-08:00
4 changed files:
Changes:
| 1 | +;;; -*- Log: code.log; Package: Extensions -*-
|
|
| 2 | +;;;
|
|
| 3 | +;;; **********************************************************************
|
|
| 4 | +;;; This code was written as part of the CMU Common Lisp project at
|
|
| 5 | +;;; Carnegie Mellon University, and has been placed in the public domain.
|
|
| 6 | +;;;
|
|
| 7 | +(ext:file-comment
|
|
| 8 | + "$Header: src/code/extensions.lisp $")
|
|
| 9 | +;;;
|
|
| 10 | +;;;
|
|
| 11 | +;;; **********************************************************************
|
|
| 12 | +;;;
|
|
| 13 | +;;; Spice Lisp extensions to the language.
|
|
| 14 | +;;;
|
|
| 15 | +;;; These extensions are compiled natively instead of byte-compiled
|
|
| 16 | +;;; like the code in code/extensions.lisp.
|
|
| 17 | +;;;
|
|
| 18 | +;;; **********************************************************************
|
|
| 19 | +(in-package "EXTENSIONS")
|
|
| 20 | + |
|
| 21 | +(intl:textdomain "cmucl")
|
|
| 22 | + |
|
| 23 | + |
|
| 24 | +;;; C-style hex float printer and parser
|
|
| 25 | +(defun print-hex-single-float (val &optional force-sign)
|
|
| 26 | + (let* ((bits (kernel:single-float-bits val))
|
|
| 27 | + (u-bits (ldb (byte 32 0) bits))
|
|
| 28 | + (sign-bit (ldb (byte 1 31) u-bits))
|
|
| 29 | + (biased-exp (ldb (byte 8 23) u-bits))
|
|
| 30 | + (fraction (ldb (byte 23 0) u-bits))
|
|
| 31 | + (sign-str (cond ((= sign-bit 1) "-") (force-sign "+") (t ""))))
|
|
| 32 | + (cond
|
|
| 33 | + ((= biased-exp 255) (if (zerop fraction) (format nil "~Ainf" sign-str) "nan"))
|
|
| 34 | + ((and (zerop biased-exp) (zerop fraction)) (format nil "~A0x0.000000p+0" sign-str))
|
|
| 35 | + ((zerop biased-exp) (format nil "~A0x0.~6,'0xp-126" sign-str fraction))
|
|
| 36 | + (t (let ((exponent (- biased-exp 127)))
|
|
| 37 | + (format nil "~A0x1.~6,'0xp~:[~;+~]~D" sign-str fraction (not (minusp exponent)) exponent))))))
|
|
| 38 | + |
|
| 39 | +(defun print-hex-double-float (val &optional force-sign)
|
|
| 40 | + (multiple-value-bind (hi lo) (kernel:double-float-bits val)
|
|
| 41 | + (let* ((u-hi (ldb (byte 32 0) hi))
|
|
| 42 | + (sign-bit (ldb (byte 1 31) u-hi))
|
|
| 43 | + (biased-exp (ldb (byte 11 20) u-hi))
|
|
| 44 | + (fraction (logior (ash (ldb (byte 20 0) u-hi) 32) lo))
|
|
| 45 | + (sign-str (cond ((= sign-bit 1) "-") (force-sign "+") (t ""))))
|
|
| 46 | + (cond
|
|
| 47 | + ((= biased-exp #x7FF) (if (zerop fraction) (format nil "~Ainf" sign-str) "nan"))
|
|
| 48 | + ((and (zerop biased-exp) (zerop fraction)) (format nil "~A0x0.0000000000000p+0" sign-str))
|
|
| 49 | + ((zerop biased-exp) (format nil "~A0x0.~13,'0xp-1022" sign-str fraction))
|
|
| 50 | + (t (let ((exponent (- biased-exp 1023)))
|
|
| 51 | + (format nil "~A0x1.~13,'0xp~:[~;+~]~D" sign-str fraction (not (minusp exponent)) exponent)))))))
|
|
| 52 | + |
|
| 53 | +#+nil
|
|
| 54 | +(defun print-hex-single-float (val &optional force-sign)
|
|
| 55 | + "Converts a single-float to a C-style hex string (32-bit)."
|
|
| 56 | + (let* ((bits (kernel:single-float-bits val))
|
|
| 57 | + (u-bits (ldb (byte 32 0) bits))
|
|
| 58 | + (sign-bit (ldb (byte 1 31) u-bits))
|
|
| 59 | + (biased-exp (ldb (byte 8 23) u-bits))
|
|
| 60 | + (fraction (ldb (byte 23 0) u-bits))
|
|
| 61 | + (sign-str (cond ((= sign-bit 1)
|
|
| 62 | + "-")
|
|
| 63 | + (force-sign
|
|
| 64 | + "+")
|
|
| 65 | + (t
|
|
| 66 | + ""))))
|
|
| 67 | + (cond
|
|
| 68 | + ((= biased-exp 255)
|
|
| 69 | + (if (zerop fraction)
|
|
| 70 | + (format nil "~Ainf" sign-str)
|
|
| 71 | + "nan"))
|
|
| 72 | + ((and (zerop biased-exp)
|
|
| 73 | + (zerop fraction))
|
|
| 74 | + (format nil "~A0x0.000000p+0" sign-str))
|
|
| 75 | + ((zerop biased-exp)
|
|
| 76 | + (let ((*print-case* :downcase))
|
|
| 77 | + (format nil "~A0x0.~6,'0xp-126" sign-str fraction)))
|
|
| 78 | + (t
|
|
| 79 | + (let ((*print-case* :downcase)
|
|
| 80 | + (exponent (- biased-exp 127)))
|
|
| 81 | + (format nil "~A0x1.~6,'0xp~:[~;+~]~D"
|
|
| 82 | + sign-str fraction (not (minusp exponent)) exponent))))))
|
|
| 83 | + |
|
| 84 | +#+nil
|
|
| 85 | +(defun print-hex-double-float (val &optional force-sign)
|
|
| 86 | + "Converts a double-float to a C-style hex string (64-bit)."
|
|
| 87 | + (multiple-value-bind (hi lo)
|
|
| 88 | + (kernel:double-float-bits val)
|
|
| 89 | + (let* ((u-hi (ldb (byte 32 0) hi))
|
|
| 90 | + (sign-bit (ldb (byte 1 31) u-hi))
|
|
| 91 | + (biased-exp (ldb (byte 11 20) u-hi))
|
|
| 92 | + (fraction (logior (ash (ldb (byte 20 0) u-hi) 32) lo))
|
|
| 93 | + (sign-str (cond ((= sign-bit 1)
|
|
| 94 | + "-")
|
|
| 95 | + (force-sign "+")
|
|
| 96 | + (t ""))))
|
|
| 97 | + (cond
|
|
| 98 | + ((= biased-exp #x7FF)
|
|
| 99 | + (if (zerop fraction)
|
|
| 100 | + (format nil "~Ainf" sign-str)
|
|
| 101 | + "nan"))
|
|
| 102 | + ((and (zerop biased-exp)
|
|
| 103 | + (zerop fraction))
|
|
| 104 | + (format nil "~A0x0.0000000000000p+0" sign-str))
|
|
| 105 | + ((zerop biased-exp)
|
|
| 106 | + (let ((*print-case* :downcase))
|
|
| 107 | + (format nil "~A0x0.~13,'0xp-1022" sign-str fraction)))
|
|
| 108 | + (t
|
|
| 109 | + (let ((*print-case* :downcase)
|
|
| 110 | + (exponent (- biased-exp 1023)))
|
|
| 111 | + (format nil "~A0x1.~13,'0xp~:[~;+~]~D"
|
|
| 112 | + sign-str fraction (not (minusp exponent)) exponent)))))))
|
|
| 113 | + |
|
| 114 | +;;; PRINT-HEX-FLOAT -- Public
|
|
| 115 | +;;;
|
|
| 116 | +;;; Return a string representing a single and double-floats in C-style
|
|
| 117 | +;;; hex format.
|
|
| 118 | +(defun print-hex-float (float)
|
|
| 119 | + "Convert FLOAT to C-style hex string. Infinities are printed as \"-inf\"
|
|
| 120 | + and \"inf\". NaN is printed as \"nan\"."
|
|
| 121 | + (declare (float float))
|
|
| 122 | + (etypecase float
|
|
| 123 | + (single-float (print-hex-single-float float))
|
|
| 124 | + (double-float (print-hex-double-float float))))
|
|
| 125 | + |
|
| 126 | +;;; FORMAT-HEX-FLOAT -- Public
|
|
| 127 | +;;;
|
|
| 128 | +;;; Function that can be used in a FORMAT ~/
|
|
| 129 | +(defun format-hex-float (stream val &optional colon-p at-p &rest params)
|
|
| 130 | + "Format ~/ directive supporting @ (sign) modifier for single/double floats."
|
|
| 131 | + (declare (ignore colon-p params))
|
|
| 132 | + (write-string
|
|
| 133 | + (typecase val
|
|
| 134 | + (single-float (print-hex-single-float val at-p))
|
|
| 135 | + (double-float (print-hex-double-float val at-p))
|
|
| 136 | + (t (format nil "~A" val)))
|
|
| 137 | + stream))
|
|
| 138 | + |
|
| 139 | +;;; PARSE-HEX-FLOAT -- Public
|
|
| 140 | +;;;
|
|
| 141 | +;;; Parse a C-style float hex strings. Always returns a double-float.
|
|
| 142 | +;;; Error-checking is enabled for malformed strings.
|
|
| 143 | +(define-condition hex-parse-error (error)
|
|
| 144 | + ((text :initarg :text :reader hex-parse-error-text)
|
|
| 145 | + (message :initarg :message :reader hex-parse-error-message))
|
|
| 146 | + (:report (lambda (c s)
|
|
| 147 | + (format s "Hex float parse error in ~S: ~A"
|
|
| 148 | + (hex-parse-error-text c) (hex-parse-error-message c)))))
|
|
| 149 | + |
|
| 150 | +#+nil
|
|
| 151 | +(defun parse-hex-float (str)
|
|
| 152 | + "Parses hex strings by converting the significand to a float, then scaling."
|
|
| 153 | + (let* ((str (string-trim '(#\Space #\Tab #\Newline #\Return) (string-downcase str)))
|
|
| 154 | + (len (length str)))
|
|
| 155 | + (when (zerop len) (error 'hex-parse-error :text str :message "Empty string"))
|
|
| 156 | + (let* ((has-sign (or (char= (char str 0) #\-) (char= (char str 0) #\+)))
|
|
| 157 | + (sign (if (and has-sign (char= (char str 0) #\-)) -1 1))
|
|
| 158 | + (start (if has-sign 1 0)))
|
|
| 159 | + (cond
|
|
| 160 | + ((string= str "inf" :start1 start)
|
|
| 161 | + (if (= sign 1) double-float-positive-infinity double-float-negative-infinity))
|
|
| 162 | + ((string= str "nan" :start1 start) :nan)
|
|
| 163 | + (t
|
|
| 164 | + (unless (and (<= (+ start 2) len) (string= str "0x" :start1 start :end1 (+ start 2)))
|
|
| 165 | + (error 'hex-parse-error :text str :message "Missing '0x' prefix"))
|
|
| 166 | + (let ((p-pos (position #\p str :start start)))
|
|
| 167 | + (unless p-pos (error 'hex-parse-error :text str :message "Missing exponent 'p'"))
|
|
| 168 | +
|
|
| 169 | + ;; Check for internal whitespace
|
|
| 170 | + (loop for i from start below len
|
|
| 171 | + when (member (char str i) '(#\Space #\Tab #\Newline #\Return))
|
|
| 172 | + do (error 'hex-parse-error :text str :message "Internal whitespace detected"))
|
|
| 173 | + |
|
| 174 | + (let* ((sig-start (+ start 2))
|
|
| 175 | + (dot-pos (position #\. str :start sig-start :end p-pos))
|
|
| 176 | + (exp-start (1+ p-pos)))
|
|
| 177 | +
|
|
| 178 | + (handler-case
|
|
| 179 | + (let* ((frac-hex-len (if dot-pos (- p-pos (1+ dot-pos)) 0))
|
|
| 180 | + ;; 1. Combine leading and trailing into one large integer
|
|
| 181 | + (significand-int
|
|
| 182 | + (if (null dot-pos)
|
|
| 183 | + (parse-integer str :start sig-start :end p-pos :radix 16)
|
|
| 184 | + (let ((leading (if (= sig-start dot-pos) 0
|
|
| 185 | + (parse-integer str :start sig-start :end dot-pos :radix 16)))
|
|
| 186 | + (trailing (if (= (1+ dot-pos) p-pos) 0
|
|
| 187 | + (parse-integer str :start (1+ dot-pos) :end p-pos :radix 16))))
|
|
| 188 | + (+ (ash leading (* 4 frac-hex-len)) trailing))))
|
|
| 189 | + ;; 2. Parse decimal exponent
|
|
| 190 | + (raw-exponent (parse-integer str :start exp-start :end len))
|
|
| 191 | + ;; 3. Handle the "cliff" logic for 0x0. vs 0x1.
|
|
| 192 | + (starts-with-zero (char= (char str sig-start) #\0))
|
|
| 193 | + (actual-exponent (if (and starts-with-zero (not (zerop significand-int)))
|
|
| 194 | + -1022
|
|
| 195 | + raw-exponent)))
|
|
| 196 | +
|
|
| 197 | + ;; 4. Convert integer to float and scale by (exponent - fractional bits)
|
|
| 198 | + ;; scale-float is bit-exact for binary scaling.
|
|
| 199 | + (* sign (scale-float (float significand-int 1.0d0)
|
|
| 200 | + (- actual-exponent (* 4 frac-hex-len)))))
|
|
| 201 | + (error (c) (error 'hex-parse-error :text str :message (format nil "~A" c)))))))))))
|
|
| 202 | + |
|
| 203 | +(defun parse-hex-float (str)
|
|
| 204 | + "Parses C-style hex strings via an exact rational. Strictly validates digit presence."
|
|
| 205 | + (let* ((str (string-trim '(#\Space #\Tab #\Newline #\Return) (string-downcase str)))
|
|
| 206 | + (len (length str)))
|
|
| 207 | + (when (zerop len) (error 'hex-parse-error :text str :message "Empty string"))
|
|
| 208 | + (let* ((has-sign (or (char= (char str 0) #\-) (char= (char str 0) #\+)))
|
|
| 209 | + (sign (if (and has-sign (char= (char str 0) #\-)) -1 1))
|
|
| 210 | + (start (if has-sign 1 0)))
|
|
| 211 | + (cond
|
|
| 212 | + ((string= str "inf" :start1 start)
|
|
| 213 | + (if (= sign 1) double-float-positive-infinity double-float-negative-infinity))
|
|
| 214 | + ((string= str "nan" :start1 start) :nan)
|
|
| 215 | + (t
|
|
| 216 | + (unless (and (<= (+ start 2) len) (string= str "0x" :start1 start :end1 (+ start 2)))
|
|
| 217 | + (error 'hex-parse-error :text str :message "Missing '0x' prefix"))
|
|
| 218 | + (let ((p-pos (position #\p str :start start)))
|
|
| 219 | + (unless p-pos (error 'hex-parse-error :text str :message "Missing exponent 'p'"))
|
|
| 220 | +
|
|
| 221 | + (loop for i from start below len
|
|
| 222 | + when (member (char str i) '(#\Space #\Tab #\Newline #\Return))
|
|
| 223 | + do (error 'hex-parse-error :text str :message "Internal whitespace detected"))
|
|
| 224 | + |
|
| 225 | + (let* ((sig-start (+ start 2))
|
|
| 226 | + (dot-pos (position #\. str :start sig-start :end p-pos))
|
|
| 227 | + (exp-start (1+ p-pos))
|
|
| 228 | + ;; Strict Validation: Ensure there is at least one digit in the significand
|
|
| 229 | + (has-leading (and (not (eql sig-start dot-pos)) (not (eql sig-start p-pos))))
|
|
| 230 | + (has-trailing (and dot-pos (not (eql (1+ dot-pos) p-pos)))))
|
|
| 231 | +
|
|
| 232 | + (unless (or has-leading has-trailing)
|
|
| 233 | + (error 'hex-parse-error :text str :message "No hex digits in significand"))
|
|
| 234 | +
|
|
| 235 | + (handler-case
|
|
| 236 | + (let* ((frac-hex-len (if dot-pos (- p-pos (1+ dot-pos)) 0))
|
|
| 237 | + (significand-int
|
|
| 238 | + (if (null dot-pos)
|
|
| 239 | + (parse-integer str :start sig-start :end p-pos :radix 16)
|
|
| 240 | + (let ((leading (if (not has-leading) 0
|
|
| 241 | + (parse-integer str :start sig-start :end dot-pos :radix 16)))
|
|
| 242 | + (trailing (if (not has-trailing) 0
|
|
| 243 | + (parse-integer str :start (1+ dot-pos) :end p-pos :radix 16))))
|
|
| 244 | + (+ (ash leading (* 4 frac-hex-len)) trailing))))
|
|
| 245 | + (raw-exponent (parse-integer str :start exp-start :end len))
|
|
| 246 | + ;; significand * 2^(exp - 4*frac_len)
|
|
| 247 | + (rational-val (* significand-int
|
|
| 248 | + (expt 2 (- raw-exponent (* 4 frac-hex-len))))))
|
|
| 249 | + (* sign (float rational-val 1.0d0)))
|
|
| 250 | + (error (c) (error 'hex-parse-error :text str :message (format nil "~A" c))))))))))) |
| ... | ... | @@ -725,304 +725,6 @@ |
| 725 | 725 | (when ,dirname
|
| 726 | 726 | (delete-directory ,dirname :recursive t))))))
|
| 727 | 727 | |
| 728 | -;;; C-style hex float printer and parser
|
|
| 729 | -(defun print-hex-single-float (val &optional force-sign)
|
|
| 730 | - (let* ((bits (kernel:single-float-bits val))
|
|
| 731 | - (u-bits (ldb (byte 32 0) bits))
|
|
| 732 | - (sign-bit (ldb (byte 1 31) u-bits))
|
|
| 733 | - (biased-exp (ldb (byte 8 23) u-bits))
|
|
| 734 | - (fraction (ldb (byte 23 0) u-bits))
|
|
| 735 | - (sign-str (cond ((= sign-bit 1) "-") (force-sign "+") (t ""))))
|
|
| 736 | - (cond
|
|
| 737 | - ((= biased-exp 255) (if (zerop fraction) (format nil "~Ainf" sign-str) "nan"))
|
|
| 738 | - ((and (zerop biased-exp) (zerop fraction)) (format nil "~A0x0.000000p+0" sign-str))
|
|
| 739 | - ((zerop biased-exp) (format nil "~A0x0.~6,'0xp-126" sign-str fraction))
|
|
| 740 | - (t (let ((exponent (- biased-exp 127)))
|
|
| 741 | - (format nil "~A0x1.~6,'0xp~:[~;+~]~D" sign-str fraction (not (minusp exponent)) exponent))))))
|
|
| 742 | - |
|
| 743 | -(defun print-hex-double-float (val &optional force-sign)
|
|
| 744 | - (multiple-value-bind (hi lo) (kernel:double-float-bits val)
|
|
| 745 | - (let* ((u-hi (ldb (byte 32 0) hi))
|
|
| 746 | - (sign-bit (ldb (byte 1 31) u-hi))
|
|
| 747 | - (biased-exp (ldb (byte 11 20) u-hi))
|
|
| 748 | - (fraction (logior (ash (ldb (byte 20 0) u-hi) 32) lo))
|
|
| 749 | - (sign-str (cond ((= sign-bit 1) "-") (force-sign "+") (t ""))))
|
|
| 750 | - (cond
|
|
| 751 | - ((= biased-exp #x7FF) (if (zerop fraction) (format nil "~Ainf" sign-str) "nan"))
|
|
| 752 | - ((and (zerop biased-exp) (zerop fraction)) (format nil "~A0x0.0000000000000p+0" sign-str))
|
|
| 753 | - ((zerop biased-exp) (format nil "~A0x0.~13,'0xp-1022" sign-str fraction))
|
|
| 754 | - (t (let ((exponent (- biased-exp 1023)))
|
|
| 755 | - (format nil "~A0x1.~13,'0xp~:[~;+~]~D" sign-str fraction (not (minusp exponent)) exponent)))))))
|
|
| 756 | - |
|
| 757 | -#+nil
|
|
| 758 | -(defun print-hex-single-float (val &optional force-sign)
|
|
| 759 | - "Converts a single-float to a C-style hex string (32-bit)."
|
|
| 760 | - (let* ((bits (kernel:single-float-bits val))
|
|
| 761 | - (u-bits (ldb (byte 32 0) bits))
|
|
| 762 | - (sign-bit (ldb (byte 1 31) u-bits))
|
|
| 763 | - (biased-exp (ldb (byte 8 23) u-bits))
|
|
| 764 | - (fraction (ldb (byte 23 0) u-bits))
|
|
| 765 | - (sign-str (cond ((= sign-bit 1)
|
|
| 766 | - "-")
|
|
| 767 | - (force-sign
|
|
| 768 | - "+")
|
|
| 769 | - (t
|
|
| 770 | - ""))))
|
|
| 771 | - (cond
|
|
| 772 | - ((= biased-exp 255)
|
|
| 773 | - (if (zerop fraction)
|
|
| 774 | - (format nil "~Ainf" sign-str)
|
|
| 775 | - "nan"))
|
|
| 776 | - ((and (zerop biased-exp)
|
|
| 777 | - (zerop fraction))
|
|
| 778 | - (format nil "~A0x0.000000p+0" sign-str))
|
|
| 779 | - ((zerop biased-exp)
|
|
| 780 | - (let ((*print-case* :downcase))
|
|
| 781 | - (format nil "~A0x0.~6,'0xp-126" sign-str fraction)))
|
|
| 782 | - (t
|
|
| 783 | - (let ((*print-case* :downcase)
|
|
| 784 | - (exponent (- biased-exp 127)))
|
|
| 785 | - (format nil "~A0x1.~6,'0xp~:[~;+~]~D"
|
|
| 786 | - sign-str fraction (not (minusp exponent)) exponent))))))
|
|
| 787 | - |
|
| 788 | -#+nil
|
|
| 789 | -(defun print-hex-double-float (val &optional force-sign)
|
|
| 790 | - "Converts a double-float to a C-style hex string (64-bit)."
|
|
| 791 | - (multiple-value-bind (hi lo)
|
|
| 792 | - (kernel:double-float-bits val)
|
|
| 793 | - (let* ((u-hi (ldb (byte 32 0) hi))
|
|
| 794 | - (sign-bit (ldb (byte 1 31) u-hi))
|
|
| 795 | - (biased-exp (ldb (byte 11 20) u-hi))
|
|
| 796 | - (fraction (logior (ash (ldb (byte 20 0) u-hi) 32) lo))
|
|
| 797 | - (sign-str (cond ((= sign-bit 1)
|
|
| 798 | - "-")
|
|
| 799 | - (force-sign "+")
|
|
| 800 | - (t ""))))
|
|
| 801 | - (cond
|
|
| 802 | - ((= biased-exp #x7FF)
|
|
| 803 | - (if (zerop fraction)
|
|
| 804 | - (format nil "~Ainf" sign-str)
|
|
| 805 | - "nan"))
|
|
| 806 | - ((and (zerop biased-exp)
|
|
| 807 | - (zerop fraction))
|
|
| 808 | - (format nil "~A0x0.0000000000000p+0" sign-str))
|
|
| 809 | - ((zerop biased-exp)
|
|
| 810 | - (let ((*print-case* :downcase))
|
|
| 811 | - (format nil "~A0x0.~13,'0xp-1022" sign-str fraction)))
|
|
| 812 | - (t
|
|
| 813 | - (let ((*print-case* :downcase)
|
|
| 814 | - (exponent (- biased-exp 1023)))
|
|
| 815 | - (format nil "~A0x1.~13,'0xp~:[~;+~]~D"
|
|
| 816 | - sign-str fraction (not (minusp exponent)) exponent)))))))
|
|
| 817 | - |
|
| 818 | -;;; PRINT-HEX-FLOAT -- Public
|
|
| 819 | -;;;
|
|
| 820 | -;;; Return a string representing a single and double-floats in C-style
|
|
| 821 | -;;; hex format.
|
|
| 822 | -(defun print-hex-float (float)
|
|
| 823 | - "Convert FLOAT to C-style hex string. Infinities are printed as \"-inf\"
|
|
| 824 | - and \"inf\". NaN is printed as \"nan\"."
|
|
| 825 | - (declare (float float))
|
|
| 826 | - (etypecase float
|
|
| 827 | - (single-float (print-hex-single-float float))
|
|
| 828 | - (double-float (print-hex-double-float float))))
|
|
| 829 | - |
|
| 830 | -;;; FORMAT-HEX-FLOAT -- Public
|
|
| 831 | -;;;
|
|
| 832 | -;;; Function that can be used in a FORMAT ~/
|
|
| 833 | -(defun format-hex-float (stream val &optional colon-p at-p &rest params)
|
|
| 834 | - "Format ~/ directive supporting @ (sign) modifier for single/double floats."
|
|
| 835 | - (declare (ignore colon-p params))
|
|
| 836 | - (write-string
|
|
| 837 | - (typecase val
|
|
| 838 | - (single-float (print-hex-single-float val at-p))
|
|
| 839 | - (double-float (print-hex-double-float val at-p))
|
|
| 840 | - (t (format nil "~A" val)))
|
|
| 841 | - stream))
|
|
| 842 | - |
|
| 843 | -;;; PARSE-HEX-FLOAT -- Public
|
|
| 844 | -;;;
|
|
| 845 | -;;; Parse a C-style float hex strings. Always returns a double-float.
|
|
| 846 | -;;; Error-checking is enabled for malformed strings.
|
|
| 847 | -(define-condition hex-parse-error (error)
|
|
| 848 | - ((text :initarg :text :reader hex-parse-error-text)
|
|
| 849 | - (message :initarg :message :reader hex-parse-error-message))
|
|
| 850 | - (:report (lambda (c s)
|
|
| 851 | - (format s "Hex float parse error in ~S: ~A"
|
|
| 852 | - (hex-parse-error-text c) (hex-parse-error-message c)))))
|
|
| 853 | - |
|
| 854 | -(defun parse-hex-float (str)
|
|
| 855 | - "Parses C-style hex strings by converting to an exact rational, then to double-float."
|
|
| 856 | - (let* ((str (string-trim '(#\Space #\Tab #\Newline #\Return) (string-downcase str)))
|
|
| 857 | - (len (length str)))
|
|
| 858 | - (when (zerop len) (error 'hex-parse-error :text str :message "Empty string"))
|
|
| 859 | - (let* ((has-sign (or (char= (char str 0) #\-) (char= (char str 0) #\+)))
|
|
| 860 | - (sign (if (and has-sign (char= (char str 0) #\-)) -1 1))
|
|
| 861 | - (start (if has-sign 1 0)))
|
|
| 862 | - (cond
|
|
| 863 | - ((string= str "inf" :start1 start)
|
|
| 864 | - (if (= sign 1) double-float-positive-infinity double-float-negative-infinity))
|
|
| 865 | - ((string= str "nan" :start1 start) :nan)
|
|
| 866 | - (t
|
|
| 867 | - (unless (and (<= (+ start 2) len) (string= str "0x" :start1 start :end1 (+ start 2)))
|
|
| 868 | - (error 'hex-parse-error :text str :message "Missing '0x' prefix"))
|
|
| 869 | - (let ((p-pos (position #\p str :start start)))
|
|
| 870 | - (unless p-pos (error 'hex-parse-error :text str :message "Missing exponent 'p'"))
|
|
| 871 | -
|
|
| 872 | - (loop for i from start below len
|
|
| 873 | - when (member (char str i) '(#\Space #\Tab #\Newline #\Return))
|
|
| 874 | - do (error 'hex-parse-error :text str :message "Internal whitespace detected"))
|
|
| 875 | - |
|
| 876 | - (let* ((sig-start (+ start 2))
|
|
| 877 | - (dot-pos (position #\. str :start sig-start :end p-pos))
|
|
| 878 | - (exp-start (1+ p-pos)))
|
|
| 879 | - (when (or (= sig-start p-pos)
|
|
| 880 | - (and dot-pos (= (1+ sig-start) p-pos) (= sig-start dot-pos)))
|
|
| 881 | - (error 'hex-parse-error :text str :message "No hex digits in significand"))
|
|
| 882 | -
|
|
| 883 | - (handler-case
|
|
| 884 | - (let* ((frac-hex-len (if dot-pos (- p-pos (1+ dot-pos)) 0))
|
|
| 885 | - ;; 1. Parse significand as one large integer
|
|
| 886 | - (significand-int
|
|
| 887 | - (if (null dot-pos)
|
|
| 888 | - (parse-integer str :start sig-start :end p-pos :radix 16)
|
|
| 889 | - (let ((leading (if (= sig-start dot-pos) 0
|
|
| 890 | - (parse-integer str :start sig-start :end dot-pos :radix 16)))
|
|
| 891 | - (trailing (if (= (1+ dot-pos) p-pos) 0
|
|
| 892 | - (parse-integer str :start (1+ dot-pos) :end p-pos :radix 16))))
|
|
| 893 | - (+ (ash leading (* 4 frac-hex-len)) trailing))))
|
|
| 894 | - ;; 2. Parse exponent
|
|
| 895 | - (raw-exponent (parse-integer str :start exp-start :end len))
|
|
| 896 | - ;; 3. Build exact rational: significand / 16^frac-len * 2^exponent
|
|
| 897 | - (rational-val (* significand-int
|
|
| 898 | - (expt 2 (- raw-exponent (* 4 frac-hex-len))))))
|
|
| 899 | - ;; 4. Coerce to double-float
|
|
| 900 | - (* sign (float rational-val 1.0d0)))
|
|
| 901 | - (error (c) (error 'hex-parse-error :text str :message (format nil "~A" c)))))))))))
|
|
| 902 | - |
|
| 903 | -#+nil
|
|
| 904 | -(defun parse-hex-float (str)
|
|
| 905 | - "Parses C-style hex strings into double-floats using robust integer scaling."
|
|
| 906 | - (let* ((str (string-trim '(#\Space #\Tab #\Newline #\Return) (string-downcase str)))
|
|
| 907 | - (len (length str)))
|
|
| 908 | - (when (zerop len) (error 'hex-parse-error :text str :message "Empty string"))
|
|
| 909 | - (let* ((has-sign (or (char= (char str 0) #\-) (char= (char str 0) #\+)))
|
|
| 910 | - (sign (if (and has-sign (char= (char str 0) #\-)) -1 1))
|
|
| 911 | - (start (if has-sign 1 0)))
|
|
| 912 | - (cond
|
|
| 913 | - ((string= str "inf" :start1 start)
|
|
| 914 | - (if (= sign 1) double-float-positive-infinity double-float-negative-infinity))
|
|
| 915 | - ((string= str "nan" :start1 start) :nan)
|
|
| 916 | - (t
|
|
| 917 | - (unless (and (<= (+ start 2) len) (string= str "0x" :start1 start :end1 (+ start 2)))
|
|
| 918 | - (error 'hex-parse-error :text str :message "Missing '0x' prefix"))
|
|
| 919 | - (let ((p-pos (position #\p str :start start)))
|
|
| 920 | - (unless p-pos (error 'hex-parse-error :text str :message "Missing exponent 'p'"))
|
|
| 921 | -
|
|
| 922 | - (loop for i from start below len
|
|
| 923 | - when (member (char str i) '(#\Space #\Tab #\Newline #\Return))
|
|
| 924 | - do (error 'hex-parse-error :text str :message "Internal whitespace detected"))
|
|
| 925 | - |
|
| 926 | - (let* ((sig-start (+ start 2))
|
|
| 927 | - (dot-pos (position #\. str :start sig-start :end p-pos))
|
|
| 928 | - (exp-start (1+ p-pos)))
|
|
| 929 | - (when (or (= sig-start p-pos)
|
|
| 930 | - (and dot-pos (= (1+ sig-start) p-pos) (= sig-start dot-pos)))
|
|
| 931 | - (error 'hex-parse-error :text str :message "No hex digits in significand"))
|
|
| 932 | -
|
|
| 933 | - (handler-case
|
|
| 934 | - (let* ((frac-hex-len (if dot-pos (- p-pos (1+ dot-pos)) 0))
|
|
| 935 | - (significand-int
|
|
| 936 | - (if (null dot-pos)
|
|
| 937 | - (parse-integer str :start sig-start :end p-pos :radix 16)
|
|
| 938 | - (let ((leading (if (= sig-start dot-pos) 0
|
|
| 939 | - (parse-integer str :start sig-start :end dot-pos :radix 16)))
|
|
| 940 | - (trailing (if (= (1+ dot-pos) p-pos) 0
|
|
| 941 | - (parse-integer str :start (1+ dot-pos) :end p-pos :radix 16))))
|
|
| 942 | - (+ (ash leading (* 4 frac-hex-len)) trailing))))
|
|
| 943 | - (raw-exponent (parse-integer str :start exp-start :end len))
|
|
| 944 | - ;; A leading zero digit in hex-float notation denotes a subnormal-style format.
|
|
| 945 | - (starts-with-zero (char= (char str sig-start) #\0)))
|
|
| 946 | -
|
|
| 947 | - (let ((val (if starts-with-zero
|
|
| 948 | - ;; Subnormal path: Fixed binary floor of -1022.
|
|
| 949 | - ;; Shifted by (4 * frac-hex-len) to align integer bits.
|
|
| 950 | - (scale-float (float significand-int 1.0d0)
|
|
| 951 | - (- -1022 (* 4 frac-hex-len)))
|
|
| 952 | - ;; Normalized path: Use provided exponent,
|
|
| 953 | - ;; adjusted for the integer shift.
|
|
| 954 | - (scale-float (float significand-int 1.0d0)
|
|
| 955 | - (- raw-exponent (* 4 frac-hex-len))))))
|
|
| 956 | - (* sign val)))
|
|
| 957 | - (error (c) (error 'hex-parse-error :text str :message (format nil "~A" c)))))))))))
|
|
| 958 | - |
|
| 959 | - |
|
| 960 | - |
|
| 961 | - |
|
| 962 | - |
|
| 963 | -#+nil
|
|
| 964 | -(defun parse-hex-float (str)
|
|
| 965 | - "Parses C-style hex strings into double-floats. \"inf\" returns
|
|
| 966 | - DOUBLE-FLOAT-POSITIVE-INFINITY and \"-inf\" returns
|
|
| 967 | - DOUBLE-FLOAT-NEGATIVE-INFINITY. \"nan\" returns :NAN."
|
|
| 968 | - (let* ((str (string-trim '(#\Space #\Tab #\Newline #\Return) (string-downcase str)))
|
|
| 969 | - (len (length str)))
|
|
| 970 | - (when (zerop len)
|
|
| 971 | - (error 'hex-parse-error :text str :message "Empty string"))
|
|
| 972 | - (let* ((has-sign (or (char= (char str 0) #\-)
|
|
| 973 | - (char= (char str 0) #\+)))
|
|
| 974 | - (sign (if (and has-sign (char= (char str 0) #\-))
|
|
| 975 | - -1 1))
|
|
| 976 | - (start (if has-sign 1 0)))
|
|
| 977 | - (cond
|
|
| 978 | - ((string= str "inf" :start1 start)
|
|
| 979 | - (if (= sign 1)
|
|
| 980 | - double-float-positive-infinity
|
|
| 981 | - double-float-negative-infinity))
|
|
| 982 | - ((string= str "nan" :start1 start)
|
|
| 983 | - :nan)
|
|
| 984 | - (t
|
|
| 985 | - (unless (and (<= (+ start 2) len)
|
|
| 986 | - (string= str "0x" :start1 start :end1 (+ start 2)))
|
|
| 987 | - (error 'hex-parse-error :text str :message "Missing '0x' prefix"))
|
|
| 988 | - (let ((p-pos (position #\p str :start start)))
|
|
| 989 | - (unless p-pos
|
|
| 990 | - (error 'hex-parse-error :text str :message "Missing exponent 'p'"))
|
|
| 991 | - (unless (loop for i from start below len
|
|
| 992 | - never (member (char str i)
|
|
| 993 | - '(#\Space #\Tab #\Newline #\Return)))
|
|
| 994 | - (error 'hex-parse-error :text str :message "Internal whitespace detected"))
|
|
| 995 | - (let* ((sig-start (+ start 2))
|
|
| 996 | - (dot-pos (position #\. str :start sig-start :end p-pos))
|
|
| 997 | - (exp-start (1+ p-pos)))
|
|
| 998 | - (when (or (= sig-start p-pos)
|
|
| 999 | - (and dot-pos
|
|
| 1000 | - (= (1+ sig-start) p-pos)
|
|
| 1001 | - (= sig-start dot-pos)))
|
|
| 1002 | - (error 'hex-parse-error :text str :message "No hex digits in significand"))
|
|
| 1003 | - (handler-case
|
|
| 1004 | - (let* ((frac-hex-len (if dot-pos
|
|
| 1005 | - (- p-pos (1+ dot-pos))
|
|
| 1006 | - 0))
|
|
| 1007 | - (significand-int
|
|
| 1008 | - (if (null dot-pos)
|
|
| 1009 | - (parse-integer str :start sig-start :end p-pos :radix 16)
|
|
| 1010 | - (let ((leading (if (= sig-start dot-pos)
|
|
| 1011 | - 0
|
|
| 1012 | - (parse-integer str :start sig-start :end dot-pos :radix 16)))
|
|
| 1013 | - (trailing (if (= (1+ dot-pos) p-pos)
|
|
| 1014 | - 0
|
|
| 1015 | - (parse-integer str :start (1+ dot-pos) :end p-pos :radix 16))))
|
|
| 1016 | - (+ (ash leading (* 4 frac-hex-len))
|
|
| 1017 | - trailing))))
|
|
| 1018 | - (raw-exponent (parse-integer str :start exp-start :end len))
|
|
| 1019 | - ;; Scale: each fractional nibble reduces binary exponent by 4
|
|
| 1020 | - (val (scale-float (float significand-int 1.0d0)
|
|
| 1021 | - (- raw-exponent
|
|
| 1022 | - (* 4 frac-hex-len)))))
|
|
| 1023 | - (* sign val))
|
|
| 1024 | - (error (c)
|
|
| 1025 | - (error 'hex-parse-error :text str :message (format nil "~A" c)))))))))))
|
|
| 1026 | 728 | |
| 1027 | 729 | |
| 1028 | 730 |
| ... | ... | @@ -221,6 +221,7 @@ |
| 221 | 221 | (comf "target:code/misc")
|
| 222 | 222 | (comf "target:code/misc-doc")
|
| 223 | 223 | (comf "target:code/extensions" :byte-compile t)
|
| 224 | +(comf "target:code/ext-code")
|
|
| 224 | 225 | (comf "target:code/commandline")
|
| 225 | 226 | (comf "target:code/env-access")
|
| 226 | 227 |
| ... | ... | @@ -44,6 +44,7 @@ |
| 44 | 44 | |
| 45 | 45 | |
| 46 | 46 | (maybe-byte-load "target:code/extensions")
|
| 47 | +(maybe-byte-load "target:code/ext-code")
|
|
| 47 | 48 | (maybe-byte-load "target:code/defmacro")
|
| 48 | 49 | (maybe-byte-load "target:code/sysmacs")
|
| 49 | 50 |