>From 37dd087091b3d7305a0546d5d35659249967c8cc 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 | 28 +++++++++++++++++----------- test-suite/tests/numbers.test | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 33e49a4..b78e1d1 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 955404d..0dc0ed1 100644 --- a/libguile/numbers.c +++ b/libguile/numbers.c @@ -3070,23 +3070,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_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))) + 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))) + { + /* 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 01bccda..8fa7605 100644 --- a/test-suite/tests/numbers.test +++ b/test-suite/tests/numbers.test @@ -3112,8 +3112,23 @@ (with-test-prefix "expt" (pass-if (documented? expt)) + + ;; + ;; integer-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 will throw an exception. + ;; 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-exception "non-numeric base" exception:wrong-type-arg - (expt #t 0)) + (expt #t 2)) + (pass-if (eqv? 1 (expt 0 0))) (pass-if (eqv? 1 (expt 0.0 0))) (pass-if (eqv? 1.0 (expt 0 0.0))) @@ -3279,8 +3294,21 @@ (with-test-prefix "integer-expt" (pass-if (documented? integer-expt)) + ;; + ;; integer-expt no longer requires its first argument to be a scheme + ;; number, for the sake of extensibility. 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 will throw + ;; an exception, because `*' will fail. 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 + ;; (integer-expt #t 0)) + ;; (pass-if-exception "non-numeric base" exception:wrong-type-arg - (integer-expt #t 0)) + (integer-expt #t 2)) + (pass-if-exception "2^+inf" exception:wrong-type-arg (integer-expt 2 +inf.0)) (pass-if-exception "2^-inf" exception:wrong-type-arg -- 1.5.6.5