Some of the other answers on this page suffer from problems with floating point precision, and are thus incorrect; in particular, expt
can return floating point approximations. As one commenter pointed out, this can lead to numbers which are not cubes being reported as cubes. The number 9,261,000 is a perfect cube (the cube of 210), but the number 9,261,101 is not a perfect cube; yet two of the three other answers here report that 9,261,101 is a perfect cube.
One good solution is to find the cube root of n
, convert that to an integer, and to compare the cube of that integer with n
, avoiding the precision issues.
Another solution is to use binary search with integer values. I include this solution here not because it is the best solution, but because it is good to remember that fundamental ideas like binary search can be widely applied. In a circumstance where you are unsure about how to handle floating point precision problems, a binary search over integers may get you to a correct result.
(defun cubep (n)
(let ((n (abs n)))
(flet ((midpoint (a b) (truncate (+ a b) 2))
(closep (a b) (< (- b a) 2)))
(labels ((bsearch (start end)
(let* ((mid (midpoint start end))
(mid-cubed (expt mid 3)))
(cond ((closep start end) nil)
((< mid-cubed n)
(bsearch mid end))
((> mid-cubed n)
(bsearch start mid))
(t t)))))
(or (zerop n) ; test ends of search range
(= n 1)
(bsearch 0 n))))))
Here, since non-negative numbers with cube roots correspond with negative numbers with cube roots, only the absolute value of n
is considered. The integers between 0 and n
are searched until the cube root of n
is found, or until the search interval is too small to contain another integer.
CL-USER> (cubep 8)
T
CL-USER> (cubep -27)
T
CL-USER> (cubep 11)
NIL
CL-USER> (cubep 9261000)
T
CL-USER> (cubep 9261001)
NIL
The floating point precision problems mentioned earlier become noticeable when numbers begin to get large. Here is a function to compare the results of two implementations:
(defun compare (func1 func2 a b)
(let ((failures (loop for n from a to b
unless (eql (funcall func1 n) (funcall func2 n))
collect n)))
(if (null failures)
(format t "PASSED~%")
(format t "FAILURES: ~A~%" failures))))
Here are the results of comparing cubep
with the incorrect cube-p
; 12 numbers between 9,260,000 and 10,000,000 are mis-reported as perfect cubes due to mishandling of floating point numbers. These are large input numbers, but they aren't that large. The results are identical for the incorrect version of is-cube-p
:
CL-USER> (compare #'cubep #'cube-p 9260000 10000000)
FAILURES: (9260999 9261001 9393930 9393932 9528127 9528129 9663596 9663598
9800343 9800345 9938374 9938376)