1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-20 11:40:18 +02:00

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
This commit is contained in:
Mark H Weaver 2011-01-30 10:51:36 -05:00 committed by Andy Wingo
parent 05a5e5d6d0
commit bfe1f03aac
3 changed files with 42 additions and 14 deletions

11
NEWS
View file

@ -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

View file

@ -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);

View file

@ -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