如何检查计算结果是否为自然数?
How to check if the result of a calculation is a natural number?
对于另一个 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
to return rationals and CLISP does that. Thus you can use integerp
:
(integerp (pentagonal-side 1533776805))
==> T
如果你口齿不清(SBCL) always returns a float, you need to use 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
使用"roundtrip"
提前猜出哪种浮点类型合适并不总是那么容易。
此外,可以想象,即使对于 Lisp 的 long-float
,您的 number
也太大了。
(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
无需将 所有 你的数字转换为浮点数:
(defun pentagonal-side-double (number)
(/ (+ 1 (sqrt (+ 1 (* 24 (float number 1d0))))) 6))
应该让你所有的计算
使用 double-float
.
Return值
使用 (zerop m)
而不是 (when (zerop m) t)
。
一般来说,when
是
在 "procedural context" 中使用,当 return 值被丢弃时。
如果您使用该值,则应改用 if
,
(if (zerop m) t nil)
与 (zerop m)
.
完全相同
Multiple values
你应该
使用 nth-value
代替
multiple-value-bind
加上 ignore
.
标准函数
写(1+ ...)
比写(+ 1 ...)
更具可读性。
对于另一个 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
to return rationals and CLISP does that. Thus you can use integerp
:
(integerp (pentagonal-side 1533776805))
==> T
如果你口齿不清(SBCL) always returns a float, you need to use 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
使用"roundtrip"
提前猜出哪种浮点类型合适并不总是那么容易。
此外,可以想象,即使对于 Lisp 的 long-float
,您的 number
也太大了。
(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
无需将 所有 你的数字转换为浮点数:
(defun pentagonal-side-double (number)
(/ (+ 1 (sqrt (+ 1 (* 24 (float number 1d0))))) 6))
应该让你所有的计算
使用 double-float
.
Return值
使用 (zerop m)
而不是 (when (zerop m) t)
。
一般来说,when
是
在 "procedural context" 中使用,当 return 值被丢弃时。
如果您使用该值,则应改用 if
,
(if (zerop m) t nil)
与 (zerop m)
.
Multiple values
你应该
使用 nth-value
代替
multiple-value-bind
加上 ignore
.
标准函数
写(1+ ...)
比写(+ 1 ...)
更具可读性。