-
Notifications
You must be signed in to change notification settings - Fork 42
/
numbers.lisp
302 lines (264 loc) · 10.6 KB
/
numbers.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
(in-package :serapeum)
(defsubst fixnump (n)
"Same as `(typep N 'fixnum)'."
(typep n 'fixnum))
(declaim (ftype (function (t) (values (not (member 0 0.0s0 0.0d0 -0.0s0 -0.0d0))
boolean &optional))
null-if-zero))
(defun null-if-zero (x)
"If X is a nonzero number, return it, otherwise return nil.
The second value is T if X was nonzero."
(null-if x 0 :test #'=))
(define-post-modify-macro finc (&optional (delta 1)) +
"Like `incf', but returns the old value instead of the new.
An alternative to using -1 as the starting value of a counter, which
can prevent optimization.")
(define-post-modify-macro fdec (&optional (delta 1)) -
"Like `decf', but returns the old value instead of the new.")
;;; Loose adaption of the parser in SBCL's reader.lisp.
(defun make-float (number divisor &optional (format *read-default-float-format*))
(assert (subtypep format 'float))
(coerce (/ number divisor) format))
(defun exponent-char? (char)
(case char
((#\e #\s #\f #\l #\d) t)
((#\E #\S #\F #\L #\D) t)
(t nil)))
(defun exponent-char-format (exponent-char)
(ecase (char-downcase exponent-char)
(#\e *read-default-float-format*)
(#\s 'short-float)
(#\f 'single-float)
(#\d 'double-float)
(#\l 'long-float)))
(defun read-float-aux (stream junk-allowed type)
#.+merge-tail-calls+
(labels ((junk ()
(error "Junk in string"))
(next ()
(let ((char (read-char stream nil nil)))
(values char (and char (digit-char-p char)))))
(before-decimal (number)
(multiple-value-bind (char digit) (next)
(cond ((null char)
(coerce number type))
((eql char #\.)
(after-decimal number 1))
(digit
(before-decimal (+ (* number 10) digit)))
((exponent-char? char)
(exponent number 1 0 (exponent-char-format char)))
(t (if junk-allowed
number
(junk))))))
(after-decimal (number divisor)
(multiple-value-bind (char digit) (next)
(cond ((null char)
(make-float number divisor type))
((exponent-char? char)
(exponent number divisor 0
(exponent-char-format char)))
(digit
(after-decimal (+ (* number 10) digit)
(* divisor 10)))
(t (if junk-allowed
(make-float number divisor)
(junk))))))
(exponent (n d e f &optional neg)
(multiple-value-bind (char digit) (next)
(cond ((null char)
(let ((e (if neg (- e) e)))
(make-float (* (expt 10 e) n) d f)))
((eql char #\+)
(exponent n d e f nil))
((eql char #\-)
(exponent n d e f t))
(digit
(exponent n d (+ (* e 10) digit) f neg))
(t (if junk-allowed
(let ((e (if neg (- e) e)))
(make-float (* (expt 10 e) n) d f))
(junk)))))))
(before-decimal 0)))
(defun read-float (stream junk-allowed type)
(let ((char (read-char stream nil nil)))
(cond ((null char)
(if junk-allowed
(coerce 0 type)
(error "Empty string cannot be parsed as float")))
((eql char #\+) (read-float-aux stream junk-allowed type))
((eql char #\-) (- (read-float-aux stream junk-allowed type)))
((or (digit-char-p char)
(eql char #\.))
(unread-char char stream)
(read-float-aux stream junk-allowed type))
(t (if junk-allowed
(coerce 0 type)
(error "Junk in string"))))))
(defun parse-float (string &key (start 0) (end (length string)) junk-allowed
(type *read-default-float-format* type-supplied-p))
"Parse STRING as a float of TYPE.
The type of the float is determined by, in order:
- TYPE, if it is supplied;
- The type specified in the exponent of the string;
- or `*read-default-float-format*'.
(parse-float \"1.0\") => 1.0s0
(parse-float \"1.0d0\") => 1.0d0
(parse-float \"1.0s0\" :type 'double-float) => 1.0d0
Of course you could just use `parse-number', but sometimes only a
float will do."
(assert (subtypep type 'float))
(with-input-from-string (stream string :start start :end end)
(let ((float (read-float stream junk-allowed type)))
(if type-supplied-p
(coerce float type)
float))))
;;; When parse-float is called with a constant `:type' argument, wrap
;;; it in a `the' form.
(define-compiler-macro parse-float (&whole decline string &rest args
&key type
&allow-other-keys)
(if (and type (constantp type))
(let ((type (eval type)))
(assert (subtypep type 'float))
`(locally (declare (notinline parse-float))
(truly-the ,type
(parse-float ,string ,@args))))
decline))
(declaim (inline round-to))
(defun round-to (number &optional (divisor 1))
"Like `round', but return the resulting number.
(round 15 10) => 2
(round-to 15 10) => 20"
(* (round number divisor) divisor))
(declaim-constant-function round-to)
(defun bits (int &key big-endian)
"Return a bit vector of the bits in INT.
Defaults to little-endian."
(let ((bits (make-array (integer-length int)
:element-type 'bit)))
(with-subtype-dispatch integer
((unsigned-byte 32) (unsigned-byte 64) fixnum)
int
(if big-endian
(loop for i below (integer-length int)
for j downfrom (1- (integer-length int)) to 0
do (setf (sbit bits j)
(if (logbitp i int)
1
0)))
(loop for i below (integer-length int)
do (setf (sbit bits i)
(if (logbitp i int)
1
0)))))
bits))
(defun unbits (bits &key big-endian)
"Turn a sequence of BITS into an integer.
Defaults to little-endian."
(declare (bit-vector bits))
(with-type-dispatch (bit-vector simple-bit-vector) bits
(if big-endian
(reduce (lambda (x y)
(+ (ash x 1) y))
bits)
(loop with int = 0
for bit across bits
for i from 0
do (setf int (logior int (ash bit i)))
finally (return int)))))
(declaim (inline shrink grow))
(defun shrink (n by)
"Decrease N by a factor."
(- n (* n by)))
(defun grow (n by)
"Increase N by a factor."
(+ n (* n by)))
(define-modify-macro shrinkf (n) shrink
"Shrink the value in a place by a factor.")
(define-modify-macro growf (n) grow
"Grow the value in a place by a factor.")
(defun random-in-range (low high)
"Random number in the range [low,high).
LOW and HIGH are automatically swapped if HIGH is less than LOW.
Note that the value of LOW+HIGH may be greater than the range that can
be represented as a number in CL. E.g., you can generate a random double float with
(random-in-range most-negative-double-float most-positive-double-float)
even though (+ most-negative-double-float most-positive-double-float)
would cause a floating-point overflow.
From Zetalisp."
(when (> low high)
(rotatef low high))
(when (= low high)
(error 'arithmetic-error
:operation 'random-in-range
:operands (list low high)))
(if (and (minusp low) (plusp high))
;; Arrange for float contagion if LOW and HIGH are of different
;; precisions. E.g. (random-in-range most-negative-single-float
;; most-positive-double-float) should return the same range of
;; results as (random-in-range (coerce
;; most-negative-single-float 'double-float)
;; most-positive-double-float).
(let ((low (+ low (* 0 high)))
(high (+ high (* 0 low))))
;; We do it this way lest low+high exceed the possible size of a
;; float. E.g. (random-in-range most-negative-double-float
;; most-positive-double-float) should work.
(+ (- (random (abs low)))
(random high)))
(let ((range (- high low)))
(+ low (random range)))))
(defun random-range-type (low high)
(assert (< low high))
(let ((types-worth-checking
'#.(remove-duplicates
'(integer single-float short-float double-float long-float)
:test #'type=
:from-end t)))
;; Low and high have the same type.
(when-let (interval-type
(find-if (lambda (type)
(and (typep low type)
(typep high type)))
types-worth-checking))
(let ((type `(,interval-type ,low (,high))))
(assert (subtypep type 'number))
(assert (subtypep type interval-type))
;; Note (high) is exclusive.
(assert (not (typep high type)))
(assert (typep low type))
type))))
(define-compiler-macro random-in-range (&whole call low high)
"When LOW and HIGH are both constants, declare the type of the
result."
(when (constantp low)
(setf low (eval low)))
(when (constantp high)
(setf high (eval high)))
(unless (and (numberp low) (numberp high))
(return-from random-in-range
call))
(when (> low high)
(rotatef low high))
(when (= low high)
(error 'arithmetic-error
:operation 'random-in-range
:operands (list low high)))
(if-let (type (random-range-type low high))
`(locally (declare (notinline random-in-range))
(truly-the (values ,type &optional)
(random-in-range ,low ,high)))
call))
(defun float-precision-contagion (&rest ns)
"Perform numeric contagion on the elements of NS.
That is, if any element of NS is a float, then every number in NS will
be returned as \"a float of the largest format among all the
floating-point arguments to the function\".
This does nothing but numeric contagion: the number of arguments
returned is the same as the number of arguments given."
(declare (dynamic-extent ns))
(let* ((zeros (mapcar (op (* _ 0)) ns))
(zero (apply #'+ zeros)))
(mapcar (op (+ _ zero))
ns)))