A Common Lisp fixed-point numeric type package intended to be similar to the Ada language type. The focus is providing a useful abstraction for known reliable precision in a specific range. This package uses CLOS to encapsulate the underlying type.
If the reader macro is installed (install-reader-macro)
, then fixed point values can be read without floating point precision issues.
A small utility package (:fixed/real-time) provides a portable fixed-point type representing the internal real time.
Please let me know if you find this useful, or encounter issues.
;; Ordinary power-of-2 fixed point type that supports a resolution of 1/10.
;; This is represented by a 1/16 resolution value.
> (defdelta foo 1/10)
;; Reader macro usage.
> #q foo 1.25
#<FOO 5/4>
;; Fixed point type with precise resolution
;; This is represented by a 1/10 resolution value.
> (defdelta bar 1/10 :small 1/10)
;; Adding range info
> (defdelta foobar 0.01 :small 0.01 :low 0.00 :high 1.00)
> (defparameter fb (make-foobar 0.5))
FB
> fb
#<FOOBAR 0.5>
> (f+ fb (make-foobar 1/2))
#<FOOBAR 1.0>
> (f+ fb (make-foobar 0.51))
;; ERROR: The value 101 is not of type (MOD 101).
> (setf (foobar fb) 0.49)
#<FOOBAR 0.48999998>
> (f+ fb (make-foobar 0.51))
#<FOOBAR 1.0>
;; Base 10 decimal types are simply created like this:
> (fixed:defdecimal milli 3)
MILLI
;; Using the make-milli function works...but comes with
;; floating point precision issues.
> (make-milli 123456789.001)
#<MILLI 123456782.336>
0.0
;; Using the #q reader avoids floating point representation.
> #q milli 123456789.001
#<MILLI 123456789.001>
A fixed-point reader macro provides a method to input fixed-point literals in decimal form. The reader macro uses the Q format to define a fixed-point spec for the following value.
Install the reader macro as a Q dispatch on # with (install-q-reader)
.
e.g.
;; Read in fixed-point literals that can be represented exactly by a Q8 spec.
> #Q8 1.5
3/2
> #Q8 0.0078125
1/128
;; Read in a fixed-point literal that can be represented exactly by a Q3 spec, and one that can't.
> #Q3 1.5
3/2
> #Q3 0.0078125
;; ERROR: 0.0078125 is not a #Q3
Bounds checking can also be performed when the maximum number of useable bits are provided in the Q spec.
;; Read in the most positive Q7.8 value.
> #Q7.8 255.99609375
65535/256
> #Q7.8 256.0
;; Error: 256.0 is not a #Q7.8
> #Q7.8 -256.0
-256
Decimal fixed-point values can be read as well with #QD
and an optional spec value for digits.
e.g.
> #QD 1.2345678901234567890
1234567890123456789/1000000000000000000
> #QD3 1.2345678901234567890
;; ERROR: 1.2345678901234567890 is not a #QD3
> #QD3 1.234
617/500
> (float *)
1.234
defdelta name delta [:small small-value] [:low low-value] [:high high-value]
=> delta-name
defdecimal name power [:low low-value] [:high high-value]
=> decimal-name
name --- a symbol
delta --- real number
power --- integer
small-value, low-value, and high-value --- optional real numbers
defdelta defines a fixed-point number type named name capable of representing a value with at least the accuracy provided in delta.
If small-value is provided in defdelta, it must be a real value no greater than delta. small-value is used as the minimum resolution scaling factor for the underlying value. When small-value is not provided, it will be chosen automatically and will be no larger than delta.
The small-value can be any real number, but rationals are recommended to avoid unexpected rounding behaviors for some of the operations. If necessary, consider entering a decimal value using the provided #Q reader macro. The following are equivalent.
(defdelta a-fixed-type #qd 0.2 :small #qd 0.2)
(defdelta a-fixed-type 1/5 :small 1/5)
defdecimal defines a fixed-point number type named name capable of representing a base-10 decimal value with up to power number of digits to the right of the decimal. The small-value selected will be (expt 10 (- power)). Note: This declaration is different from the Ada decimal type where you must still define the delta (but as a power-of-10), and you define the number of digits to use in the underlying type.
low-value and high-value are both optional for defdelta or defdecimal, and are used to define the most-negative and most-positive values of the fixed point type.
defdecimal is essentially identical to defdelta when called with an identical delta and small that is a power of 10. The only difference is that values that have a defdecimal defined type will always be printed in decimal form. Values with a defdelta defined type may be printed as rationals.
defdelta and defdecimal create a set of functions and generic methods associated with name.
Operation | Type | Description |
---|---|---|
(make-name value) | Function | Return a new instance of name with value rounded as necessary with *rounding-method* |
(make-name-value value) | Function | Return a new instance of name with the provided underlying value |
(name fp) | Function | Return the value in the name instance scaled by small |
(name-value fp) | Function | Returns the underlying value of an instance of name |
(set-name fp value) | Generic | Set the value of a name instance, rounding as necessary with *rounding-method* |
(set-name-value fp value) | Function | Set the underlying integer value of an instance of name |
(setf (name fp) value) | setf | Set the value of fp with rounding as necessary with *rounding-method* |
(setf (name-value fp) value) | setf | Set the underlying value of fp |
(small fp) or (small 'name) | Generic | Return the small when passed 'name or an instance of name |
(delta fp) or (delta 'name) | Generic | Return the delta when passed 'name or an instance of name |
(size fp) or (size 'name) | Generic | Return the number of bits required to store the underlying value of name when it is ranged, otherwise return :INFINITY |
+MOST-POSITIVE-NAME+ is defined for each fixed-point type and is either the most positive value, or :POSITIVE-INFINITY if unlimited.
+MOST-NEGATIVE-NAME+ is defined for each fixed-point type and is either the most negative value, or :NEGATIVE-INFINITY if unlimited.
Generic Function Predicates: f= f/= f> f>= f< f<=
Generic Arithmetic Operations: f+ f- f* f/
A utility package that implements a fixed-point type for internal real time.
;; Get the current internal real time as a fixed point
> (defparameter the-time (current-time))
THE-TIME
> the-time
#<REAL-TIME 3711125.080>
;; do some stuff
;; calculate deltat
> (f- (current-time) the-time)
#<REAL-TIME 15.616>
MIT