From bfe1f03aac3df63a5e4b0b670338a728c7b45cf7 Mon Sep 17 00:00:00 2001 From: Mark H Weaver Date: Sun, 30 Jan 2011 10:51:36 -0500 Subject: [PATCH] Improve extensibility of `expt' and `integer-expt' * libguile/numbers.c (scm_integer_expt): No longer require that the first argument be a number, in order to improve extensibility. This allows us to efficiently raise arbitrary objects to an integer power as long as we can multiply those objects. For example, this allows us to efficiently exponentiate matrices if we define only multiplication methods for matrices. Note also that scm_expt calls this procedure whenever the exponent is an integer, regardless of the type of the first argument. Also rearrange the order in which we test special cases. * test-suite/tests/numbers.test (expt, integer-expt): Comment out tests that required `(expt #t 0)' and `(integer-expt #t 0)' to throw exceptions. Add tests for (expt #t 2) and `(integer-expt #t 2) instead. * NEWS: Add NEWS entry --- NEWS | 11 +++++++++++ libguile/numbers.c | 26 ++++++++++++++++---------- test-suite/tests/numbers.test | 19 +++++++++++++++---- 3 files changed, 42 insertions(+), 14 deletions(-) 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