| ... |
... |
@@ -21,100 +21,69 @@ |
|
21
|
21
|
(intl:textdomain "cmucl")
|
|
22
|
22
|
|
|
23
|
23
|
|
|
24
|
|
-;;; C-style hex float printer and parser
|
|
25
|
|
-(defun print-hex-single-float (val)
|
|
26
|
|
- "Prints a single-float in C-style hex format."
|
|
27
|
|
- (cond ((float-nan-p val)
|
|
28
|
|
- "nan")
|
|
29
|
|
- ((float-infinity-p val)
|
|
30
|
|
- (if (plusp val) "inf" "-inf"))
|
|
31
|
|
- ((zerop val)
|
|
32
|
|
- (if (eql val -0.0f0)
|
|
33
|
|
- "-0x0.0p+0f" "0x0.0p+0f"))
|
|
34
|
|
- (t
|
|
35
|
|
- (let* ((bits (ldb (byte 32 0) (kernel:single-float-bits val)))
|
|
36
|
|
- (sign (ldb (byte 1 31) bits))
|
|
37
|
|
- (exp-bits (ldb (byte 8 23) bits))
|
|
38
|
|
- (mantissa (ldb (byte 23 0) bits))
|
|
39
|
|
- ;; Print lower-case hex digits.
|
|
40
|
|
- (*print-case* :downcase))
|
|
41
|
|
- (if (zerop exp-bits)
|
|
42
|
|
- ;; Subnormal: Leading digit 0, exponent fixed at -126
|
|
43
|
|
- (format nil "~A0x0.~6,'0Xp-126f"
|
|
44
|
|
- (if (= sign 1) "-" "")
|
|
45
|
|
- (ash mantissa 1)) ; Align 23 bits to 24 bits (6 hex digits)
|
|
46
|
|
- ;; Normalized: Leading digit 1, exponent bias 127
|
|
47
|
|
- (format nil "~A0x1.~6,'0Xp~Af"
|
|
48
|
|
- (if (= sign 1) "-" "")
|
|
49
|
|
- (ash mantissa 1) ; Align 23 bits to 24 bits (6 hex digits)
|
|
50
|
|
- (- exp-bits 127)))))))
|
|
51
|
|
-
|
|
52
|
|
-(defun print-hex-double-float (val)
|
|
53
|
|
- "Prints a double-float in C-style hex format."
|
|
54
|
|
- (cond ((float-nan-p val)
|
|
55
|
|
- "nan")
|
|
56
|
|
- ((float-infinity-p val)
|
|
57
|
|
- (if (plusp val) "inf" "-inf"))
|
|
58
|
|
- ((zerop val)
|
|
59
|
|
- (if (eql val -0.0d0)
|
|
60
|
|
- "-0x0.0p+0" "0x0.0p+0"))
|
|
61
|
|
- (t
|
|
62
|
|
- (multiple-value-bind (hi-bits lo-bits)
|
|
63
|
|
- (kernel:double-float-bits val)
|
|
64
|
|
- (let* ((hi (ldb (byte 32 0) hi-bits))
|
|
65
|
|
- (lo (ldb (byte 32 0) lo-bits))
|
|
66
|
|
- (sign (ldb (byte 1 31) hi))
|
|
67
|
|
- (exp-bits (ldb (byte 11 20) hi))
|
|
68
|
|
- ;; Combine 20 bits from high word and 32 bits from low word
|
|
69
|
|
- (mantissa (logior (ash (ldb (byte 20 0) hi) 32)
|
|
70
|
|
- lo))
|
|
71
|
|
- ;; Print lower-case hex digits.
|
|
72
|
|
- (*print-case* :downcase))
|
|
73
|
|
- (if (zerop exp-bits)
|
|
74
|
|
- ;; Subnormal: Leading digit 0, exponent fixed at -1022
|
|
75
|
|
- (format nil "~A0x0.~13,'0Xp-1022"
|
|
76
|
|
- (if (= sign 1) "-" "")
|
|
77
|
|
- mantissa)
|
|
78
|
|
- ;; Normalized: Leading digit 1, exponent bias 1023
|
|
79
|
|
- (format nil "~A0x1.~13,'0Xp~A"
|
|
80
|
|
- (if (= sign 1) "-" "")
|
|
81
|
|
- mantissa ; 52 bits fits 13 hex digits perfectly
|
|
82
|
|
- (- exp-bits 1023))))))))
|
|
|
24
|
+;;;; C-style hex float printer and parser
|
|
83
|
25
|
|
|
84
|
|
-;;; PRINT-HEX-FLOAT -- Public
|
|
|
26
|
+;;; FLOAT-TO-HEX-STRING -- Public
|
|
85
|
27
|
;;;
|
|
86
|
28
|
;;; Return a string representing a single and double-floats in C-style
|
|
87
|
29
|
;;; hex format.
|
|
88
|
|
-(defun print-hex-float (float)
|
|
89
|
|
- "Convert FLOAT to C-style hex string. Infinities are printed as \"-inf\"
|
|
90
|
|
- and \"inf\". NaN is printed as \"nan\"."
|
|
|
30
|
+(defun float-to-hex-string (val &optional at-p)
|
|
|
31
|
+ "Prints a single or double float in bit-perfect C-style hex.
|
|
|
32
|
+ If AT-P is true, prepends '+' for non-negative finite values."
|
|
|
33
|
+ (cond ((ext:float-nan-p val) "nan")
|
|
|
34
|
+ ((ext:float-infinity-p val)
|
|
|
35
|
+ (if (plusp val) (if at-p "+inf" "inf") "-inf"))
|
|
|
36
|
+ (t
|
|
|
37
|
+ (multiple-value-bind (sign exp-bits mantissa bias precision suffix)
|
|
|
38
|
+ (typecase val
|
|
|
39
|
+ (single-float
|
|
|
40
|
+ (let ((bits (ldb (byte 32 0) (kernel:single-float-bits val))))
|
|
|
41
|
+ (values (ldb (byte 1 31) bits)
|
|
|
42
|
+ (ldb (byte 8 23) bits)
|
|
|
43
|
+ (ash (ldb (byte 23 0) bits) 1) ; Align 23 to 6 hex digits
|
|
|
44
|
+ 127 6 "f")))
|
|
|
45
|
+ (double-float
|
|
|
46
|
+ (multiple-value-bind (hi lo) (kernel:double-float-bits val)
|
|
|
47
|
+ (values (ldb (byte 1 31) hi)
|
|
|
48
|
+ (ldb (byte 11 20) hi)
|
|
|
49
|
+ (logior (ash (ldb (byte 20 0) hi) 32) (ldb (byte 32 0) lo))
|
|
|
50
|
+ 1023 13 "")))
|
|
|
51
|
+ (t (error "Unsupported float type: ~S" (type-of val))))
|
|
|
52
|
+
|
|
|
53
|
+ (let ((sign-str (cond ((= sign 1) "-")
|
|
|
54
|
+ (at-p "+")
|
|
|
55
|
+ (t ""))))
|
|
|
56
|
+ (if (and (zerop exp-bits) (zerop mantissa))
|
|
|
57
|
+ (format nil "~A0x0.0p+0~A" sign-str suffix)
|
|
|
58
|
+ (format nil "~A0x~A.~V,'0Xp~A~A"
|
|
|
59
|
+ sign-str
|
|
|
60
|
+ (if (zerop exp-bits) "0" "1")
|
|
|
61
|
+ precision
|
|
|
62
|
+ mantissa
|
|
|
63
|
+ (if (zerop exp-bits) (1+ (- bias)) (- exp-bits bias))
|
|
|
64
|
+ suffix)))))))
|
|
|
65
|
+
|
|
|
66
|
+;;; WRITE-HEX-FLOAT -- Public
|
|
|
67
|
+;;;
|
|
|
68
|
+;;; Writes a float number in C-style hex format to the given stream.
|
|
|
69
|
+(defun write-hex-float (float &optional (stream *standard-output*))
|
|
|
70
|
+ "Convert FLOAT to C-style hex string and write it to STREAM.
|
|
|
71
|
+ Infinities are printed as \"-inf\" and \"inf\". NaN is printed as
|
|
|
72
|
+ \"nan\"."
|
|
91
|
73
|
(declare (float float))
|
|
92
|
|
- (etypecase float
|
|
93
|
|
- (single-float (print-hex-single-float float))
|
|
94
|
|
- (double-float (print-hex-double-float float))))
|
|
|
74
|
+ (write-string (float-to-hex-string float)
|
|
|
75
|
+ stream))
|
|
95
|
76
|
|
|
96
|
77
|
;;; FORMAT-HEX-FLOAT -- Public
|
|
97
|
78
|
;;;
|
|
98
|
79
|
;;; Function that can be used in a FORMAT ~/
|
|
99
|
80
|
(defun format-hex-float (stream arg colon-p at-sign-p &optional width)
|
|
100
|
81
|
"Formatter for ~/ext:format-hex-float/.
|
|
101
|
|
- @ forces sign (+/-). Colon modifier is ignored as per request."
|
|
|
82
|
+ Uses AT-SIGN-P (@) to force the sign. COLON-P (:) is currently ignored."
|
|
102
|
83
|
(declare (ignore width colon-p))
|
|
103
|
|
- (let ((str (if (typep arg 'single-float)
|
|
104
|
|
- (print-hex-single-float arg)
|
|
105
|
|
- (print-hex-double-float arg))))
|
|
106
|
|
- ;; Prepend '+' if @ is used and number isn't negative or special
|
|
107
|
|
- (when (and at-sign-p
|
|
108
|
|
- (not (ext:float-nan-p arg))
|
|
109
|
|
- (not (ext:float-infinity-p arg))
|
|
110
|
|
- (not (char= (char str 0) #\-)))
|
|
111
|
|
- (write-char #\+ stream))
|
|
112
|
|
- (write-string str stream)))
|
|
|
84
|
+ (write-string (float-to-hex-string arg at-sign-p)
|
|
|
85
|
+ stream))
|
|
113
|
86
|
|
|
114
|
|
-;;; PARSE-HEX-FLOAT -- Public
|
|
115
|
|
-;;;
|
|
116
|
|
-;;; Parse a C-style float hex strings. Always returns a double-float.
|
|
117
|
|
-;;; Error-checking is enabled for malformed strings.
|
|
118
|
87
|
(define-condition hex-parse-error (parse-error)
|
|
119
|
88
|
((text :initarg :text :reader hex-parse-error-text)
|
|
120
|
89
|
(message :initarg :message :reader hex-parse-error-message))
|
| ... |
... |
@@ -122,68 +91,14 @@ |
|
122
|
91
|
(format s "Hex float parse error in ~S: ~A"
|
|
123
|
92
|
(hex-parse-error-text c) (hex-parse-error-message c)))))
|
|
124
|
93
|
|
|
125
|
|
-#+nil
|
|
126
|
|
-(defun parse-hex-float (str)
|
|
127
|
|
- "Parses hex floats using scale-float for the exponent. Strictly hex-literal only."
|
|
128
|
|
- (let* ((str (string-trim '(#\Space #\Tab #\Newline #\Return)
|
|
129
|
|
- (string-downcase str)))
|
|
130
|
|
- (len (length str)))
|
|
131
|
|
- (when (zerop len)
|
|
132
|
|
- (error 'hex-parse-error :text str :message "Empty string"))
|
|
133
|
|
-
|
|
134
|
|
- (let* ((ends-with-f (and (> len 1)
|
|
135
|
|
- (char= (char str (1- len)) #\f)))
|
|
136
|
|
- (effective-len (if ends-with-f
|
|
137
|
|
- (1- len) len))
|
|
138
|
|
- (prototype (if ends-with-f
|
|
139
|
|
- 1.0f0 1.0d0))
|
|
140
|
|
- (has-sign (or (char= (char str 0) #\-)
|
|
141
|
|
- (char= (char str 0) #\+)))
|
|
142
|
|
- (sign (if (and has-sign
|
|
143
|
|
- (char= (char str 0) #\-))
|
|
144
|
|
- -1 1))
|
|
145
|
|
- (start (if has-sign 1 0)))
|
|
146
|
|
-
|
|
147
|
|
- (unless (and (<= (+ start 2) effective-len)
|
|
148
|
|
- (string= str "0x" :start1 start :end1 (+ start 2)))
|
|
149
|
|
- (error 'hex-parse-error :text str :message "Missing '0x' prefix"))
|
|
150
|
|
-
|
|
151
|
|
- (let ((p-pos (position #\p str :start start :end effective-len)))
|
|
152
|
|
- (unless p-pos
|
|
153
|
|
- (error 'hex-parse-error :text str :message "Missing exponent 'p'"))
|
|
154
|
|
-
|
|
155
|
|
- (let* ((sig-start (+ start 2))
|
|
156
|
|
- (dot-pos (position #\. str :start sig-start :end p-pos))
|
|
157
|
|
- (exp-start (1+ p-pos))
|
|
158
|
|
- ;; Leading hex: digits before the dot
|
|
159
|
|
- (leading-str (subseq str sig-start (or dot-pos p-pos)))
|
|
160
|
|
- ;; Trailing hex: digits after the dot
|
|
161
|
|
- (trailing-str (if dot-pos (subseq str (1+ dot-pos) p-pos) ""))
|
|
162
|
|
- (has-digits (or (plusp (length leading-str))
|
|
163
|
|
- (plusp (length trailing-str)))))
|
|
164
|
|
-
|
|
165
|
|
- (unless has-digits
|
|
166
|
|
- (error 'hex-parse-error :text str :message "No hex digits in significand"))
|
|
167
|
|
-
|
|
168
|
|
- (handler-case
|
|
169
|
|
- (let* ((leading-int (if (string= leading-str "") 0
|
|
170
|
|
- (parse-integer leading-str :radix 16)))
|
|
171
|
|
- (trailing-len (length trailing-str))
|
|
172
|
|
- (trailing-int (if (string= trailing-str "") 0
|
|
173
|
|
- (parse-integer trailing-str :radix 16)))
|
|
174
|
|
- ;; Calculate the significand as a float: leading + (trailing / 16^len)
|
|
175
|
|
- (significand (float (+ leading-int
|
|
176
|
|
- (/ trailing-int (expt 16 trailing-len)))
|
|
177
|
|
- prototype))
|
|
178
|
|
- ;; The exponent after 'p'
|
|
179
|
|
- (raw-exponent (parse-integer str :start exp-start :end effective-len)))
|
|
180
|
|
- ;; Use scale-float to apply the binary exponent efficiently
|
|
181
|
|
- (* sign (scale-float significand raw-exponent)))
|
|
182
|
|
- (error (c)
|
|
183
|
|
- (error 'hex-parse-error :text str :message (format nil "~A" c)))))))))
|
|
184
|
|
-
|
|
|
94
|
+;;; PARSE-HEX-FLOAT-FROM-STREAM -- Public
|
|
|
95
|
+;;;
|
|
|
96
|
+;;; Parse a C-style float hex string from a stream. Invalid formats
|
|
|
97
|
+;;; signal an error. A single-float or double-float may be returned.
|
|
185
|
98
|
(defun parse-hex-float-from-stream (stream)
|
|
186
|
|
- "Reads hex float from stream using double-float accumulation and a 6-character exponent buffer."
|
|
|
99
|
+ "Reads a C-style hex float number from STREAM. A single-float or
|
|
|
100
|
+ double-float number is returned. A HEX-PARSE-ERROR is signaled for
|
|
|
101
|
+ an invalid format."
|
|
187
|
102
|
(let* ((sign 1.0d0)
|
|
188
|
103
|
(char (peek-char t stream))) ; Skip whitespace
|
|
189
|
104
|
|
| ... |
... |
@@ -251,6 +166,15 @@ |
|
251
|
166
|
(float result 1.0f0)
|
|
252
|
167
|
result)))))))
|
|
253
|
168
|
|
|
254
|
|
-(defun parse-hex-float (str)
|
|
255
|
|
- (with-input-from-string (s str)
|
|
256
|
|
- (parse-hex-float-from-stream s))) |
|
|
169
|
+;;; PARSE-HEX-FLOAT -- Public
|
|
|
170
|
+;;;
|
|
|
171
|
+;;; Parse a C-style hex float number from either a string or a stream.
|
|
|
172
|
+(defun parse-hex-float (obj)
|
|
|
173
|
+ "Parse a C-style hex float number from OBJ which is either a string or a stream."
|
|
|
174
|
+ (declare (type (or string stream) obj))
|
|
|
175
|
+ (etypecase obj
|
|
|
176
|
+ (string
|
|
|
177
|
+ (with-input-from-string (s obj)
|
|
|
178
|
+ (parse-hex-float-from-stream s)))
|
|
|
179
|
+ (stream
|
|
|
180
|
+ (parse-hex-float-from-stream obj)))) |