Common Lisp 有着丰富的数字类型,其中包括整数、有理数、浮点数以及复数。
以下是相关的资料:
[Numbers](https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node16.html#SECTION00610000000000000000)
[Numbers, Characters and Strings](http://www.gigamonkeys.com/book/numbers-characters-and-strings.html)
概述
整数类型
Common Lisp 提供一个真正的整数类型,叫做 大数(bignum)
,这个值的大小只取决于内存的大小(不是机器的字大小)。例如,下面的例子在64位的机器上会溢出。
* (expt 2 200)
1606938044258990275541962092341162602522202993782792835301376
考虑到效率因素,整数会被限制在修正的比特位内,叫做 修正数(fixnum)
。可以查看相对应的变量来查看整数的大小范围
most-positive-fixnum
4611686018427387903
most-negative-fixnum
-4611686018427387904
整型相对应的函数有:
[isqrt](http://clhs.lisp.se/Body/f_sqrt_.htm)
:返回最大的正根
(isqrt 10)
;; => 3
(isqrt 4)
;; => 2
[gcd](http://clhs.lisp.se/Body/f_gcd.htm)
:返回最大公因数
(gcd 12 15)
;; => 3
[lcm](http://clhs.lisp.se/Body/f_lcm.htm#lcm)
:返回最小公倍数
(lcm 9 12)
;; 36
与其他的低级语言类似,Common Lisp 也提供了相对应的十六进制,以及从二进制到三十六进制的表示方法:
#xFF
;; 255
#2r1010
;; 10
#3r21
;; 7
#4r33
;; 15
#8r11
9
#16rFF
255
#36rz
35
有理数
有理数是由两个大数
组成的[分数(ratio)](http://clhs.lisp.se/Body/t_ratio.htm)
。所以可以表示任意大小的有理数:
(/ (1+ (expt 2 100) (expt 2 100)))
1267650600228229401496703205377/1267650600228229401496703205376
其中 整型(integer)
是 有理数(rational)
类中的一个子类。
浮点数
浮点数试图使用有限的比特位来表示连续的实数。这就意味着大部分实数是近似的表示。这个特性会导致一些令人不快的意外,尤其是在内部十进制和二进制的转换上。如果你需要经常处理浮点数,建议阅读 What Every Computer Scientist Should Know About Floating-Point Arithmetic。
Common Lisp 标准中的浮点数有以下几种:
short-float
single-float
double-float
long-float
用来获取浮点数的精度的:
short-float-epsilon
single-float-epsilon
double-float-epsilon
long-float-epsilon
浮点数类型
浮点数的默认类型是由 *read-default-float-format*
来确定的,默认类型为 SINGLE-FLOAT
,如果要将浮点数设为 double-float
型的,在数值后面加上 d0
,其他类型的话分别为 s0
f0
d0
l0
e0
(默认,single float):
(type-of 1.24)
SINGLE-FLOAT
(type-of 1.24d0)
DOUBLE-FLOAT
当然,也可以通过修改 read-default-float-format*
来修改默认的浮点类型
(setq *read-default-float-format* 'double-float)
(type-of 1.24)
DOUBLE-FLOAT
有一点值得注意的,在整数后面加一个 .
的话,这个数依然被认为是整型,而不是浮点数。
type-of 10.)
(INTEGER 0 4611686018427387903)
(type-of 10.0)
SINGLE-FLOAT
浮点数溢出处理
如果浮点数过大,将会导致溢出,在 SBCL 中,会出现如下错误:
(exp 1000)
; Evaluation aborted on #<FLOATING-POINT-OVERFLOW {10041720B3}>.
可以通过修改一些值,让溢出返回 +infinity
,在 SBCL 中是这样的:
(sb-int:set-floating-point-modes :traps '(:INVALID :DIVIDE-BY-ZERO))
(exp 1000)
#.SB-EXT:SINGLE-FLOAT-POSITIVE-INFINITY
(/ 1 (exp 1000))
0.0
在 CCL 中:
(set-fpu-mode :overflow nil)
在 SBCL 中检查浮点数的配置:
(sb-int:get-floating-point-modes)
(:TRAPS (:OVERFLOW :INVALID :DIVIDE-BY-ZERO) :ROUNDING-MODE :NEAREST :CURRENT-EXCEPTIONS NIL :ACCRUED-EXCEPTIONS NIL :FAST-MODE NIL)
任意精度
对于高精度的计算的话,需要使用 QuickLisp 中的 computable-reals 包
(ql:quickload :computable-reals)
(use-package :computable-reals)
(sqrt-r 2)
+1.41421356237309504880...
(sin-r (/r +pi-r+ 2))
+1.00000000000000000000...
结果的精度由变量 *PRINT-PREC*
控制,默认是小数点后 20 位
(setq *PRINT-PREC* 50)
(sqrt-r 2)
+1.41421356237309504880168872420969807856967187537695...
复数
复数的实部和虚部必须要是同一类型——可以是有理数或是浮点数中的一种。
复数可由宏 #C
或函数 complex
,其中宏 #C
创建是里面不能使用表达式
#C(1 1)
#C(1 1)
#C((+ 1 2) 5)
;; Evaluation aborted on #<TYPE-ERROR expected-type: REAL datum: (+1 2)>.
(complex (+1 2) 5)
#C(3 5)
当复数中都是浮点数时,默认会转换为精度更高的那个类型:
(type-of #C(1 1))
(COMPLEX (INTEGER 1 1))
(type-of #C(1.0 1))
(COMPLEX (SINGLE-FLOAT 1.0 1.0))
(type-of #C(1.0 1d0))
(COMPLEX (DOUBLE-FLOAT 1.0d0 1.0d0))
使用 realpart
和 imagepart
获取复数的实部和虚部
(realpart #C(7 9))
7
(imagepart #C(4.2 9.5))
9.5
复数运算
Common Lisp 中的数学运算函数也可以处理复数
(sqrt -1)
#C(0.0 1.0)
(exp #C(0.0 0.5))
#C(0.87758255 0.47942555)
(sin #C(1.0 1.0))
#C(1.2984576 0.63496387)
字符串转换为数字
parse-integer
, parse-float
(ql:quickload :parse-float)
(use-package :parse-float)
(parse-float "23.4e2" :type 'double-float)
2340.0d0
6
近似值
ceiling
, floor
, round
, truncate
* (ceiling 1.42)
2
-0.58000004
* (floor 1.42)
1
0.41999996
* (round 1.42)
1
0.41999996
* (truncate 1.42)
1
0.41999996
floor
与 truncate
的区别
* (truncate -1.42)
-1
-0.41999996
* (floor -1.42)
-2
0.58000004
* (ceiling -1.42)
-1
-0.41999996
fceiling
, ffloor
, fround
, ftruncate
* (ftruncate 1.3)
1.0
0.29999995
* (type-of (ftruncate 1.3))
SINGLE-FLOAT
* (type-of (ftruncate 1.3d0))
DOUBLE-FLOAT
数字比较
=
,常量 single-float-epsilon
是 =
处理的最小的数值。
* single-float-epsilon
5.960465e-8
* (= (+ 1s0 5e-8) 1s0)
T
* (= (+ 1s0 6e-8) 1s0)
NIL
这并不表示 single-float
的精确范围是 0 到 6e-8
* (= (+ 10s0 4e-7) 10s0)
T
* (= (+ 10s0 5e-7) 10s0)
NIL
这表示 single-float
大致是精确到小数点后 7 位。当在一连串的计算中,这个小错误最终会会合成一个很大的错误。如下面的例子:
* (< (abs (- (+ 10s0 5e-7)
10s0))
1s-6)
T
=
可以比较不同类型的数据,可以将它和 eql
做比较
* (= 3 3.0)
T
* (eql 3 3.0)
NIL
操作一系列数字
有很多处理惰性(无规则)数字序列的包/库:
处理罗马数字
format
中的 ~@r
* (format nil "~@r" 42)
"XLII"
随机数的生成
random
* (random 10)
7
* (type-of (random 10))
(INTEGER 0 4611686018427387903)
* (type-of (random 10.0))
SINGLE-FLOAT
* (type-of (random 10d0))
DOUBLE-FLOAT
随机数的种子(seed)存储在 *random-state
中,这个是有内部解释器单独实现的。函数make-random-state
可以用来生成或是拷贝新的随机态(random states)
* (dotimes (i 3)
(let ((*random-state* (make-random-state nil)))
(format t "~a~%"
(loop for i from 0 below 10 collecting (random 10)))))
(8 3 9 2 1 8 0 0 4 1)
(8 3 9 2 1 8 0 0 4 1)
(8 3 9 2 1 8 0 0 4 1)
比特位操作
Common Lisp 中也提供了位运算,下表是与 C/C++ 相对应的函数:
Common Lisp | C/C++ | 描述 |
---|---|---|
(logand a b c) |
a & b & c |
按位与 |
(logior a b c) |
a | b | c |
按位或 |
(lognot a) |
~a |
取非 |
(logxor a b c) |
a ^ b ^ c |
异或 |
(ash a 3) |
a << 3 |
左移位 |
(ash a -3) |
a >> 3 |
右移位 |
负数的位运算也是将其看作二进制进行运算的
* (logior 1 2 4 8)
15
;; Explanation:
;; 0001
;; 0010
;; 0100
;; | 1000
;; -------
;; 1111
* (logand 2 -3 4)
0
;; Explanation:
;; 0010 (2)
;; 1101 (two's complement of -3)
;; & 0100 (4)
;; -------
;; 0000
* (logxor 1 3 7 15)
10
;; Explanation:
;; 0001
;; 0011
;; 0111
;; ^ 1111
;; -------
;; 1010
* (lognot -1)
0
;; Explanation:
;; 11 -> 00
* (lognot -3)
2
;; 101 -> 010
* (ash 3 2)
12
;; Explanation:
;; 11 -> 1100
* (ash -5 -2)
-2
;; Explanation
;; 11011 -> 110
更多关于位操作的函数见 CLHS page