floating-point - 如何检查计算结果是否为自然数?

标签 floating-point integer common-lisp precision

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

相关文章:

c - 如何在C中比较整数和换行符?

Java:如何打印整数对象的引用?

lisp - 通用 LISP (SBCL) : Returning values from within loops

lisp - Lisp 中的 Flatten Nests 函数 - 需要帮助理解

lisp - Common Lisp 对 car 和 cdr 的引用行为

java - 为什么 Double.MIN_VALUE 不是负数

.net - Math.Round(double,decimal) 是否总是返回一致的结果

c++ - 将整数重新解释为 float 是否安全?

java - 在java中将 double 值1234567.1234转换为 float

java - 如何在java中执行整数的范围比较,例如.x>a<y