对于另一个 Project Euler 斗争(使用 SBCL 1.3.17),我想测试一个数字是否是 pentagonal number .如果
的结果,这可以很容易地测试(/ (+ 1 (sqrt (+ 1 (* 24 number)))) 6)
是一个自然数。忽略自然数不仅仅是整数,因为 number
将只有正值,我开始使用 intergerp
作为谓词,但它不适用于所有测试的数字。所以我想出了以下办法:
(defun is-pentagonal-p (number)
"Returns T if NUMBER is a pentagonal number."
(multiple-value-bind (n m)
(floor (/ (+ 1 (sqrt (+ 1 (* 24 number)))) 6))
(declare (ignore n))
(when (zerop m) t)))
这对于简单的例子工作得很好,即小数,但对于大数 1533776805 再次失败。过了很长一段时间后,我想起了我以前的 Fortran 日子,结果是:
(defun is-pentagonal-p (number)
"Returns T if NUMBER is a pentagonal number."
(multiple-value-bind (n m)
(floor (/ (+ 1.0d0 (sqrt (+ 1.0d0 (* 24.0d0 number)))) 6.0d0))
(declare (ignore n))
(when (zerop m) t)))
它显着降低了舍入误差并给出了正确的结果,但让我觉得我一定错过了一些更简单、更易懂的东西。这只是偏执狂吗?
最佳答案
物质
如果您使用 CLISP , 你会得到
(defun pentagonal-side (number)
(/ (+ 1 (sqrt (+ 1 (* 24 number)))) 6))
(pentagonal-side 51)
==> 6
(pentagonal-side 1533)
==> 32.135838
(pentagonal-side 1533776805)
==> 31977
这是因为 ANSI CL 允许 sqrt
返回有理数,CLISP 就是这样做的。因此你可以使用 integerp
:
(integerp (pentagonal-side 1533776805))
==> T
如果你的 lisp ( SBCL ) 总是返回一个 float ,你需要使用 sufficient precision ,例如:
(pentagonal-side 1533776805d0) ; double-float
==> 31977.0d0
(pentagonal-side 1533776805f0) ; single-float
==> 31977.0
(pentagonal-side 1533776805s0) ; short-float
==> 31976.8s0
因此,在您的情况下,只需传递适当的 float
:
(zerop (mod (pentagonal-side 1533776805d0) 1))
==> T
警告
似乎single-float
是
够了吧?
(zerop (mod (pentagonal-side 1533776805f0) 1))
==> T
不!
(zerop (mod (pentagonal-side (1+ 1533776805f0)) 1))
==> T
使用“往返”
预先猜测哪种浮点类型合适并不总是那么容易。
此外,可以想象,您的 number
甚至对于您的 Lisp 的 long-float
来说都太大了。 .
(CLISP 有 arbitrary float precision ,大多数 lisp 没有,即使那样你也需要提前决定使用哪个精度。)
因此更容易坚持使用整数:确保您计算的 pentagonal-side
与往返的原始数字相对应:
(defun pentagonal-side-int (area)
(/ (+ 1 (isqrt (+ 1 (* 24 area)))) 6))
(defun pentagonal-area (side)
(/ (- (* 3 side side) side) 2))
(pentagonal-side-int 1533776805)
==> 31977
(pentagonal-area 31977)
==> 1533776805
(defun pentagonal-number-p (number)
(let ((side (pentagonal-side-int number)))
(and (integerp side)
(= number (pentagonal-area side)))))
(pentagonal-number-p 1533776805)
==> T
(pentagonal-number-p 1533776804)
==> NIL
(pentagonal-number-p 1533776806)
==> NIL
风格
名字
混合样式不是一个好主意。 is-...
是 C/Java
风格。 ...-p
是 Lisp 风格。我建议你坚持
后者用于您的 Lisp 代码。
Float contagion
无需将您的所有数字转换为 float :
(defun pentagonal-side-double (number)
(/ (+ 1 (sqrt (+ 1 (* 24 (float number 1d0))))) 6))
应该让你所有的计算
使用 double-float
.
返回值
使用 (zerop m)
而不是 (when (zerop m) t)
。
一般来说,when
是
在“过程上下文”中使用,当返回值被丢弃时。
如果使用该值,则应使用 if
反而,
并且 (if (zerop m) t nil)
与 (zerop m)
完全相同。
Multiple values
你应该
使用 nth-value
反而
的 multiple-value-bind
加上 ignore
.
标准函数
(1+ ...)
比 (+ 1 ...)
更具可读性。
关于floating-point - 如何检查计算结果是否为自然数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44597644/