diff --git a/NEWS b/NEWS index 33e49a483..b78e1d1e7 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,17 @@ integer-expt. This is more correct, and conforming to R6RS, but seems to be incompatible with R5RS, which would return 0 for all non-zero values of N. +*** `expt' and `integer-expt' are more generic, less strict + +When raising to an exact non-negative integer exponent, `expt' and +`integer-expt' are now able to exponentiate any object that can be +multiplied using `*'. They can also raise an object to an exact +negative integer power if its reciprocal can be taken using `/'. +In order to allow this, the type of the first argument is no longer +checked when raising to an exact integer power. If the exponent is 0 +or 1, the first parameter is not manipulated at all, and need not +even support multiplication. + *** Infinities are no longer integers, nor rationals scm_integer_p `integer?' and scm_rational_p `rational?' now return #f diff --git a/libguile/numbers.c b/libguile/numbers.c index 41d178b90..d08d15f11 100644 --- a/libguile/numbers.c +++ b/libguile/numbers.c @@ -3052,23 +3052,29 @@ SCM_DEFINE (scm_integer_expt, "integer-expt", 2, 0, 0, int i2_is_big = 0; SCM acc = SCM_I_MAKINUM (1L); - SCM_VALIDATE_NUMBER (SCM_ARG1, n); - if (!SCM_I_INUMP (k) && !SCM_BIGP (k)) + /* Specifically refrain from checking the type of the first argument. + This allows us to exponentiate any object that can be multiplied. + If we must raise to a negative power, we must also be able to + take its reciprocal. */ + if (!SCM_LIKELY (SCM_I_INUMP (k)) && !SCM_LIKELY (SCM_BIGP (k))) SCM_WRONG_TYPE_ARG (2, k); - if (scm_is_true (scm_zero_p (n))) + if (SCM_UNLIKELY (scm_is_eq (k, SCM_INUM0))) + return SCM_INUM1; /* n^(exact0) is exact 1, regardless of n */ + else if (SCM_UNLIKELY (scm_is_eq (n, SCM_I_MAKINUM (-1L)))) + return scm_is_false (scm_even_p (k)) ? n : SCM_INUM1; + /* The next check is necessary only because R6RS specifies different + behavior for 0^(-k) than for (/ 0). If n is not a scheme number, + we simply skip this case and move on. */ + else if (SCM_NUMBERP (n) && scm_is_true (scm_zero_p (n))) { - if (scm_is_true (scm_zero_p (k))) /* 0^0 == 1 per R5RS */ - return acc; /* return exact 1, regardless of n */ - else if (scm_is_true (scm_positive_p (k))) + /* k cannot be 0 at this point, because we + have already checked for that case above */ + if (scm_is_true (scm_positive_p (k))) return n; else /* return NaN for (0 ^ k) for negative k per R6RS */ return scm_nan (); } - else if (scm_is_eq (n, acc)) - return acc; - else if (scm_is_eq (n, SCM_I_MAKINUM (-1L))) - return scm_is_false (scm_even_p (k)) ? n : acc; if (SCM_I_INUMP (k)) i2 = SCM_I_INUM (k); diff --git a/test-suite/tests/numbers.test b/test-suite/tests/numbers.test index 0d711b06d..d85e44cd8 100644 --- a/test-suite/tests/numbers.test +++ b/test-suite/tests/numbers.test @@ -3110,8 +3110,21 @@ (with-test-prefix "expt" (pass-if (documented? expt)) - (pass-if-exception "non-numeric base" exception:wrong-type-arg - (expt #t 0)) + + ;; + ;; expt no longer requires its first argument to be a scheme number, + ;; for the sake of extensibility, and expt calls integer-expt for + ;; integer powers. To raise to a positive power, all that is required + ;; is that it can be multiplied using `*'. For negative powers we + ;; must also be able to find the reciprocal. If we try to raise #t to + ;; any power other than 0 or 1 it may throw an exception, depending on + ;; whether * has been defined for #t. However, when raising to the 0 + ;; or 1 power, the first argument is not manipulated at all. + ;; + ;; (pass-if-exception "non-numeric base" exception:wrong-type-arg + ;; (expt #t 0)) + ;; + (pass-if (eqv? 1 (expt 0 0))) (pass-if (eqv? 1 (expt 0.0 0))) (pass-if (eqv? 1.0 (expt 0 0.0))) @@ -3277,8 +3290,6 @@ (with-test-prefix "integer-expt" (pass-if (documented? integer-expt)) - (pass-if-exception "non-numeric base" exception:wrong-type-arg - (integer-expt #t 0)) (pass-if-exception "2^+inf" exception:wrong-type-arg (integer-expt 2 +inf.0)) (pass-if-exception "2^-inf" exception:wrong-type-arg