【问题标题】:How to check if the result of a calculation is a natural number?如何检查计算结果是否为自然数?
【发布时间】:2017-11-19 17:26:32
【问题描述】:

对于另一个 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)))

它显式地降低了舍入误差并给出了正确的结果,但让我觉得我一定错过了一些更简单、更简洁的东西。这只是偏执狂吗?

【问题讨论】:

  • is-<...>-p 是 Lisp 风格的 -p 后缀和 C/Java 风格的 is 前缀的组合。它会震动双方人群的审美感受。
  • @sds 我不知道这个问题。我不记得为什么我开始对谓词使用这种命名方案。我绝对不想打扰你的审美!
  • 在我的解决方案中,我使用了(defun pentap(x) (subtypep (type-of (/ (1+ (sqrt (1+ (* x 24)))) 6)) 'integer)),(有效),但我不知道这是否是最好的测试......(或者即使它真的正确!)。跨度>
  • @Renzo:这是重新实现integerp 的一种非常有创意的方式:-)
  • @sds,很可能,当时我在至少 35 年后开始重新使用 lisp! :)

标签: floating-point integer common-lisp precision


【解决方案1】:

物质

如果你使用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) 总是返回一个浮点数,你需要使用 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

使用“往返”

预先猜出哪种浮点类型是合适的并不总是容易的。 此外,即使对于 Lisp 的 long-float,您的 number 也会太大,这是完全可以想象的。 (CLISP 有 arbitrary float precision,大多数 lisps 没有,即使这样你也需要提前决定使用哪种精度。)

因此更容易坚持使用整数:确保您计算的 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

返回值

使用(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 ...) 更具可读性。

【讨论】:

  • 这真的很有帮助!我不知道 NTH-VALUE。
猜你喜欢
  • 2017-07-12
  • 2020-04-09
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-09
  • 2014-04-13
相关资源
最近更新 更多