How to check if the result of a calculation is natural?
For another Euler struggle of the project (using SBCL 1.3.17) I want to check if a number <5 digit number is a pentagonal number. This can be easily checked if the result
(/ (+ 1 (sqrt (+ 1 (* 24 number)))) 6)
- natural number. Ignoring that a natural number is not an integer because it number
will only have positive values, I started using it intergerp
as a predicate, but it didn't work for all the numbers tested. So I came up with the following:
(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)))
Which is great for simple examples i.e. with a low number, but again with an error for large numbers like 1533776805. After a while, I remembered my old Fortran days and ended up with:
(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)))
which clearly reduce rounding errors and give correct results, but leave me feeling like I must have missed something much simpler and more lazy. Is it just paranoia?
source to share
Substance
If you are using CLISP you will get
(defun pentagonal-side (number)
(/ (+ 1 (sqrt (+ 1 (* 24 number)))) 6))
(pentagonal-side 51)
==> 6
(pentagonal-side 1533)
==> 32.135838
(pentagonal-side 1533776805)
==> 31977
This is because ANSI CL allows you to sqrt
return rational decisions, and CLISP does. Thus, you can use integerp
:
(integerp (pentagonal-side 1533776805))
==> T
If your lisp ( SBCL ) always returns float, then you need to use enough precision , e.g .:
(pentagonal-side 1533776805d0) ; double-float
==> 31977.0d0
(pentagonal-side 1533776805f0) ; single-float
==> 31977.0
(pentagonal-side 1533776805s0) ; short-float
==> 31976.8s0
So, in your case, just pass the appropriate float
:
(zerop (mod (pentagonal-side 1533776805d0) 1))
==> T
Caveat
Seems to single-float
be enough, right?
(zerop (mod (pentagonal-side 1533776805f0) 1))
==> T
Nope!
(zerop (mod (pentagonal-side (1+ 1533776805f0)) 1))
==> T
Use "roundtrip"
It is not always easy to guess in advance which float type is appropriate. Moreover, it is quite possible that yours number
will be too big even for your lisp long-float
. (CLISP has arbitrary float precision , most lisps don't, and even then you have to decide which precision to use beforehand.)
This way it's easier to stick with integers: make sure the calculated one pentagonal-side
matches the original number via roundtrip:
(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
Style
Names
Styles cannot be mixed. is-...
is C / Java style. ...-p
- lisp style. I suggest you stick with the latter for your lisp code.
Floating
No need to convert all your numbers to floats:
(defun pentagonal-side-double (number)
(/ (+ 1 (sqrt (+ 1 (* 24 (float number 1d0))))) 6))
should do all your calculations use double-float
.
Return value
Use (zerop m)
instead (when (zerop m) t)
. Generally speaking, when
is is used in the "procedural context" when the return value is discarded. If you are using value, you should use if
, and is (if (zerop m) t nil)
exactly the same as (zerop m)
.
Several meanings
You should probably use
plus nth-value
instead
.multiple-value-bind
ignore
Standard functions
More readable to write (1+ ...)
than (+ 1 ...)
.
source to share