lisp 随机函数的奇怪行为

Strange behavior with the lisp random function

有没有人可以向我解释以下关于带有浮点数的随机函数的行为以及如何摆脱它:

CL-USER> (loop for i from 1 to 20 collect (* 0.1 (random 100)))        

;; with sbcl ...
(9.2 4.4 9.5 0.5 9.7 5.8 4.3 9.900001 3.7 6.8 2.6000001 9.5 1.6 8.900001 3.3 1.7 5.1 5.5 4.2000003 8.2)

;; with closure ...
(7.7000003 7.2000003 1.7 5.6 7.5 2.2 5.0 7.6 2.0 4.9 2.9 1.6 0.4 6.1 3.3 7.1 8.7 6.5 5.6 9.2)

有什么奇怪的行为?

您是否担心,例如,0.1 * 99 似乎是 9.90001?这对于浮点数来说是正常的。他们不准确。他们牺牲准确性以获得范围。不放弃就不能指望得到。

如果您想要 rational 个数字,请不要使用浮点数...

(loop for i from 1 to 20 collect (/ (random 100) 10))

在浮点数的上下文中,这完全符合预期并且是适当的。其他语言都一样。

二进制浮点数(由 IEEE 定义)不能准确表示所有小数。例如,小数0.2在二进制中是0.0011001100110011....,所以你不能用有限的位数来精确表示它。因此,许多浮点数必然是四舍五入的。如果你把它们加起来,舍入误差就会累积。有时它们会相互抵消,有时则不会。如果你将它们相乘(就像你所做的那样),它们就会相乘。

如果您想获得精确的分数,Common Lisp 可以让您使用 rationals。例如,如果你计算 (/ 1 5),你会得到 1/5,这是这样一个有理数的打印表示,这是精确的(在内部,计算是对分子和分母进行的)。您可以使用这些确切数字进行计算:

CL-USER> (+ 1/5 2/3)
13/15

CL-USER> (+ 3/5 2/5)
1

为了打印出来为小数,可以使用格式控制~f,例如:

CL-USER> (format t "~,3f" 13/15)
0.867
(loop for i from 1 to 20 collect (read-from-string (format nil "~v$" 1 (/ (random 100) 10))))