Merge remote-tracking branch 'origin/stable-2.0'
[bpt/guile.git] / libguile / numbers.c
index d500145..9857e18 100644 (file)
@@ -1,4 +1,6 @@
-/* Copyright (C) 1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+/* Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+ *   2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012,
+ *   2013 Free Software Foundation, Inc.
  *
  * Portions Copyright 1990, 1991, 1992, 1993 by AT&T Bell Laboratories
  * and Bellcore.  See scm_divide.
 
 \f
 /* General assumptions:
- * All objects satisfying SCM_COMPLEXP() have a non-zero complex component.
  * All objects satisfying SCM_BIGP() are too large to fit in a fixnum.
  * If an object satisfies integer?, it's either an inum, a bignum, or a real.
  * If floor (r) == r, r is an int, and mpz_set_d will DTRT.
+ *     XXX What about infinities?  They are equal to their own floor!  -mhw
  * All objects satisfying SCM_FRACTIONP are never an integer.
  */
 
@@ -45,6 +47,8 @@
 #  include <config.h>
 #endif
 
+#include <verify.h>
+
 #include <math.h>
 #include <string.h>
 #include <unicase.h>
 #include <complex.h>
 #endif
 
+#include <stdarg.h>
+
 #include "libguile/_scm.h"
 #include "libguile/feature.h"
 #include "libguile/ports.h"
 #include "libguile/root.h"
 #include "libguile/smob.h"
 #include "libguile/strings.h"
+#include "libguile/bdw-gc.h"
 
 #include "libguile/validate.h"
 #include "libguile/numbers.h"
 #ifndef M_LOG10E
 #define M_LOG10E   0.43429448190325182765
 #endif
+#ifndef M_LN2
+#define M_LN2     0.69314718055994530942
+#endif
 #ifndef M_PI
 #define M_PI       3.14159265358979323846
 #endif
 
+/* FIXME: We assume that FLT_RADIX is 2 */
+verify (FLT_RADIX == 2);
+
+typedef scm_t_signed_bits scm_t_inum;
+#define scm_from_inum(x) (scm_from_signed_integer (x))
+
+/* Tests to see if a C double is neither infinite nor a NaN.
+   TODO: if it's available, use C99's isfinite(x) instead */
+#define DOUBLE_IS_FINITE(x) (!isinf(x) && !isnan(x))
+
+/* On some platforms, isinf(x) returns 0, 1 or -1, indicating the sign
+   of the infinity, but other platforms return a boolean only. */
+#define DOUBLE_IS_POSITIVE_INFINITY(x) (isinf(x) && ((x) > 0))
+#define DOUBLE_IS_NEGATIVE_INFINITY(x) (isinf(x) && ((x) < 0))
+
+/* Test an inum to see if it can be converted to a double without loss
+   of precision.  Note that this will sometimes return 0 even when 1
+   could have been returned, e.g. for large powers of 2.  It is designed
+   to be a fast check to optimize common cases. */
+#define INUM_LOSSLESSLY_CONVERTIBLE_TO_DOUBLE(n)                        \
+  (SCM_I_FIXNUM_BIT-1 <= DBL_MANT_DIG                                   \
+   || ((n) ^ ((n) >> (SCM_I_FIXNUM_BIT-1))) < (1L << DBL_MANT_DIG))
+
+#if ! HAVE_DECL_MPZ_INITS
+
+/* GMP < 5.0.0 lacks `mpz_inits' and `mpz_clears'.  Provide them.  */
+
+#define VARARG_MPZ_ITERATOR(func)              \
+  static void                                  \
+  func ## s (mpz_t x, ...)                     \
+  {                                            \
+    va_list  ap;                               \
+                                               \
+    va_start (ap, x);                          \
+    while (x != NULL)                          \
+      {                                                \
+       func (x);                               \
+       x = va_arg (ap, mpz_ptr);               \
+      }                                                \
+    va_end (ap);                               \
+  }
+
+VARARG_MPZ_ITERATOR (mpz_init)
+VARARG_MPZ_ITERATOR (mpz_clear)
+
+#endif
+
 \f
 
 /*
 /* the macro above will not work as is with fractions */
 
 
+/* Default to 1, because as we used to hard-code `free' as the
+   deallocator, we know that overriding these functions with
+   instrumented `malloc' / `free' is OK.  */
+int scm_install_gmp_memory_functions = 1;
 static SCM flo0;
+static SCM exactly_one_half;
+static SCM flo_log10e;
 
 #define SCM_SWAP(x, y) do { SCM __t = x; x = y; y = __t; } while (0)
 
@@ -116,9 +179,9 @@ static double acosh (double x) { return log (x + sqrt (x * x - 1)); }
 static double atanh (double x) { return 0.5 * log ((1 + x) / (1 - x)); }
 #endif
 
-/* mpz_cmp_d in gmp 4.1.3 doesn't recognise infinities, so xmpz_cmp_d uses
-   an explicit check.  In some future gmp (don't know what version number),
-   mpz_cmp_d is supposed to do this itself.  */
+/* mpz_cmp_d in GMP before 4.2 didn't recognise infinities, so
+   xmpz_cmp_d uses an explicit check.  Starting with GMP 4.2 (released
+   in March 2006), mpz_cmp_d now handles infinities properly.  */
 #if 1
 #define xmpz_cmp_d(z, d)                                \
   (isinf (d) ? (d < 0.0 ? 1 : -1) : mpz_cmp_d (z, d))
@@ -128,7 +191,7 @@ static double atanh (double x) { return 0.5 * log ((1 + x) / (1 - x)); }
 
 
 #if defined (GUILE_I)
-#if HAVE_COMPLEX_DOUBLE
+#if defined HAVE_COMPLEX_DOUBLE
 
 /* For an SCM object Z which is a complex number (ie. satisfies
    SCM_COMPLEXP), return its value as a C level "complex double". */
@@ -152,6 +215,42 @@ scm_from_complex_double (complex double z)
 static mpz_t z_negative_one;
 
 \f
+
+/* Clear the `mpz_t' embedded in bignum PTR.  */
+static void
+finalize_bignum (void *ptr, void *data)
+{
+  SCM bignum;
+
+  bignum = SCM_PACK_POINTER (ptr);
+  mpz_clear (SCM_I_BIG_MPZ (bignum));
+}
+
+/* The next three functions (custom_libgmp_*) are passed to
+   mp_set_memory_functions (in GMP) so that memory used by the digits
+   themselves is known to the garbage collector.  This is needed so
+   that GC will be run at appropriate times.  Otherwise, a program which
+   creates many large bignums would malloc a huge amount of memory
+   before the GC runs. */
+static void *
+custom_gmp_malloc (size_t alloc_size)
+{
+  return scm_malloc (alloc_size);
+}
+
+static void *
+custom_gmp_realloc (void *old_ptr, size_t old_size, size_t new_size)
+{
+  return scm_realloc (old_ptr, new_size);
+}
+
+static void
+custom_gmp_free (void *ptr, size_t size)
+{
+  free (ptr);
+}
+
+
 /* Return a new uninitialized bignum.  */
 static inline SCM
 make_bignum (void)
@@ -163,9 +262,12 @@ make_bignum (void)
                                 "bignum");
   p[0] = scm_tc16_big;
 
+  scm_i_set_finalizer (p, finalize_bignum, NULL);
+
   return SCM_PACK (p);
 }
 
+
 SCM
 scm_i_mkbig ()
 {
@@ -175,6 +277,21 @@ scm_i_mkbig ()
   return z;
 }
 
+static SCM
+scm_i_inum2big (scm_t_inum x)
+{
+  /* Return a newly created bignum initialized to X. */
+  SCM z = make_bignum ();
+#if SIZEOF_VOID_P == SIZEOF_LONG
+  mpz_init_set_si (SCM_I_BIG_MPZ (z), x);
+#else
+  /* Note that in this case, you'll also have to check all mpz_*_ui and
+     mpz_*_si invocations in Guile. */
+#error creation of mpz not implemented for this inum size
+#endif
+  return z;
+}
+
 SCM
 scm_i_long2big (long x)
 {
@@ -244,82 +361,57 @@ scm_i_dbl2num (double u)
 
   if (u < (double) (SCM_MOST_POSITIVE_FIXNUM+1)
       && u >= (double) SCM_MOST_NEGATIVE_FIXNUM)
-    return SCM_I_MAKINUM ((long) u);
+    return SCM_I_MAKINUM ((scm_t_inum) u);
   else
     return scm_i_dbl2big (u);
 }
 
-/* scm_i_big2dbl() rounds to the closest representable double, in accordance
-   with R5RS exact->inexact.
-
-   The approach is to use mpz_get_d to pick out the high DBL_MANT_DIG bits
-   (ie. truncate towards zero), then adjust to get the closest double by
-   examining the next lower bit and adding 1 (to the absolute value) if
-   necessary.
-
-   Bignums exactly half way between representable doubles are rounded to the
-   next higher absolute value (ie. away from zero).  This seems like an
-   adequate interpretation of R5RS "numerically closest", and it's easier
-   and faster than a full "nearest-even" style.
+static SCM round_right_shift_exact_integer (SCM n, long count);
 
-   The bit test must be done on the absolute value of the mpz_t, which means
-   we need to use mpz_getlimbn.  mpz_tstbit is not right, it treats
-   negatives as twos complement.
+/* scm_i_big2dbl_2exp() is like frexp for bignums: it converts the
+   bignum b into a normalized significand and exponent such that
+   b = significand * 2^exponent and 1/2 <= abs(significand) < 1.
+   The return value is the significand rounded to the closest
+   representable double, and the exponent is placed into *expon_p.
+   If b is zero, then the returned exponent and significand are both
+   zero. */
 
-   In current gmp 4.1.3, mpz_get_d rounding is unspecified.  It ends up
-   following the hardware rounding mode, but applied to the absolute value
-   of the mpz_t operand.  This is not what we want so we put the high
-   DBL_MANT_DIG bits into a temporary.  In some future gmp, don't know when,
-   mpz_get_d is supposed to always truncate towards zero.
-
-   ENHANCE-ME: The temporary init+clear to force the rounding in gmp 4.1.3
-   is a slowdown.  It'd be faster to pick out the relevant high bits with
-   mpz_getlimbn if we could be bothered coding that, and if the new
-   truncating gmp doesn't come out.  */
-
-double
-scm_i_big2dbl (SCM b)
+static double
+scm_i_big2dbl_2exp (SCM b, long *expon_p)
 {
-  double result;
-  size_t bits;
-
-  bits = mpz_sizeinbase (SCM_I_BIG_MPZ (b), 2);
-
-#if 1
-  {
-    /* Current GMP, eg. 4.1.3, force truncation towards zero */
-    mpz_t  tmp;
-    if (bits > DBL_MANT_DIG)
-      {
-        size_t  shift = bits - DBL_MANT_DIG;
-        mpz_init2 (tmp, DBL_MANT_DIG);
-        mpz_tdiv_q_2exp (tmp, SCM_I_BIG_MPZ (b), shift);
-        result = ldexp (mpz_get_d (tmp), shift);
-        mpz_clear (tmp);
-      }
-    else
-      {
-        result = mpz_get_d (SCM_I_BIG_MPZ (b));
-      }
-  }
-#else
-  /* Future GMP */
-  result = mpz_get_d (SCM_I_BIG_MPZ (b));
-#endif
+  size_t bits = mpz_sizeinbase (SCM_I_BIG_MPZ (b), 2);
+  size_t shift = 0;
 
   if (bits > DBL_MANT_DIG)
     {
-      unsigned long  pos = bits - DBL_MANT_DIG - 1;
-      /* test bit number "pos" in absolute value */
-      if (mpz_getlimbn (SCM_I_BIG_MPZ (b), pos / GMP_NUMB_BITS)
-          & ((mp_limb_t) 1 << (pos % GMP_NUMB_BITS)))
+      shift = bits - DBL_MANT_DIG;
+      b = round_right_shift_exact_integer (b, shift);
+      if (SCM_I_INUMP (b))
         {
-          result += ldexp ((double) mpz_sgn (SCM_I_BIG_MPZ (b)), pos + 1);
+          int expon;
+          double signif = frexp (SCM_I_INUM (b), &expon);
+          *expon_p = expon + shift;
+          return signif;
         }
     }
 
-  scm_remember_upto_here_1 (b);
-  return result;
+  {
+    long expon;
+    double signif = mpz_get_d_2exp (&expon, SCM_I_BIG_MPZ (b));
+    scm_remember_upto_here_1 (b);
+    *expon_p = expon + shift;
+    return signif;
+  }
+}
+
+/* scm_i_big2dbl() rounds to the closest representable double,
+   in accordance with R5RS exact->inexact.  */
+double
+scm_i_big2dbl (SCM b)
+{
+  long expon;
+  double signif = scm_i_big2dbl_2exp (b, &expon);
+  return ldexp (signif, expon);
 }
 
 SCM
@@ -329,7 +421,7 @@ scm_i_normbig (SCM b)
   /* presume b is a bignum */
   if (mpz_fits_slong_p (SCM_I_BIG_MPZ (b)))
     {
-      long val = mpz_get_si (SCM_I_BIG_MPZ (b));
+      scm_t_inum val = mpz_get_si (SCM_I_BIG_MPZ (b));
       if (SCM_FIXABLE (val))
         b = SCM_I_MAKINUM (val);
     }
@@ -342,7 +434,7 @@ scm_i_mpz2num (mpz_t b)
   /* convert a mpz number to a SCM number. */
   if (mpz_fits_slong_p (b))
     {
-      long val = mpz_get_si (b);
+      scm_t_inum val = mpz_get_si (b);
       if (SCM_FIXABLE (val))
         return SCM_I_MAKINUM (val);
     }
@@ -354,129 +446,265 @@ scm_i_mpz2num (mpz_t b)
   }
 }
 
-/* this is needed when we want scm_divide to make a float, not a ratio, even if passed two ints */
-static SCM scm_divide2real (SCM x, SCM y);
-
+/* Make the ratio NUMERATOR/DENOMINATOR, where:
+    1. NUMERATOR and DENOMINATOR are exact integers
+    2. NUMERATOR and DENOMINATOR are reduced to lowest terms: gcd(n,d) == 1 */
 static SCM
-scm_i_make_ratio (SCM numerator, SCM denominator)
-#define FUNC_NAME "make-ratio"
+scm_i_make_ratio_already_reduced (SCM numerator, SCM denominator)
 {
-  /* First make sure the arguments are proper.
-   */
-  if (SCM_I_INUMP (denominator))
+  /* Flip signs so that the denominator is positive. */
+  if (scm_is_false (scm_positive_p (denominator)))
     {
-      if (scm_is_eq (denominator, SCM_INUM0))
+      if (SCM_UNLIKELY (scm_is_eq (denominator, SCM_INUM0)))
        scm_num_overflow ("make-ratio");
-      if (scm_is_eq (denominator, SCM_I_MAKINUM(1)))
-       return numerator;
-    }
-  else 
-    {
-      if (!(SCM_BIGP(denominator)))
-       SCM_WRONG_TYPE_ARG (2, denominator);
+      else
+       {
+         numerator = scm_difference (numerator, SCM_UNDEFINED);
+         denominator = scm_difference (denominator, SCM_UNDEFINED);
+       }
     }
-  if (!SCM_I_INUMP (numerator) && !SCM_BIGP (numerator))
-    SCM_WRONG_TYPE_ARG (1, numerator);
 
-  /* Then flip signs so that the denominator is positive.
-   */
-  if (scm_is_true (scm_negative_p (denominator)))
-    {
-      numerator = scm_difference (numerator, SCM_UNDEFINED);
-      denominator = scm_difference (denominator, SCM_UNDEFINED);
-    }
+  /* Check for the integer case */
+  if (scm_is_eq (denominator, SCM_INUM1))
+    return numerator;
 
-  /* Now consider for each of the four fixnum/bignum combinations
-     whether the rational number is really an integer.
-  */
-  if (SCM_I_INUMP (numerator))
+  return scm_double_cell (scm_tc16_fraction,
+                         SCM_UNPACK (numerator),
+                         SCM_UNPACK (denominator), 0);
+}
+
+static SCM scm_exact_integer_quotient (SCM x, SCM y);
+
+/* Make the ratio NUMERATOR/DENOMINATOR */
+static SCM
+scm_i_make_ratio (SCM numerator, SCM denominator)
+#define FUNC_NAME "make-ratio"
+{
+  /* Make sure the arguments are proper */
+  if (!SCM_LIKELY (SCM_I_INUMP (numerator) || SCM_BIGP (numerator)))
+    SCM_WRONG_TYPE_ARG (1, numerator);
+  else if (!SCM_LIKELY (SCM_I_INUMP (denominator) || SCM_BIGP (denominator)))
+    SCM_WRONG_TYPE_ARG (2, denominator);
+  else
     {
-      long  x = SCM_I_INUM (numerator);
-      if (scm_is_eq (numerator, SCM_INUM0))
-       return SCM_INUM0;
-      if (SCM_I_INUMP (denominator))
+      SCM the_gcd = scm_gcd (numerator, denominator);
+      if (!(scm_is_eq (the_gcd, SCM_INUM1)))
        {
-         long y;
-         y = SCM_I_INUM (denominator);
-         if (x == y)
-           return SCM_I_MAKINUM(1);
-         if ((x % y) == 0)
-           return SCM_I_MAKINUM (x / y);
+         /* Reduce to lowest terms */
+         numerator = scm_exact_integer_quotient (numerator, the_gcd);
+         denominator = scm_exact_integer_quotient (denominator, the_gcd);
        }
-      else
-        {
-          /* When x == SCM_MOST_NEGATIVE_FIXNUM we could have the negative
-             of that value for the denominator, as a bignum.  Apart from
-             that case, abs(bignum) > abs(inum) so inum/bignum is not an
-             integer.  */
-          if (x == SCM_MOST_NEGATIVE_FIXNUM
-              && mpz_cmp_ui (SCM_I_BIG_MPZ (denominator),
-                             - SCM_MOST_NEGATIVE_FIXNUM) == 0)
-           return SCM_I_MAKINUM(-1);
-        }
+      return scm_i_make_ratio_already_reduced (numerator, denominator);
     }
-  else if (SCM_BIGP (numerator))
+}
+#undef FUNC_NAME
+
+static mpz_t scm_i_divide2double_lo2b;
+
+/* Return the double that is closest to the exact rational N/D, with
+   ties rounded toward even mantissas.  N and D must be exact
+   integers. */
+static double
+scm_i_divide2double (SCM n, SCM d)
+{
+  int neg;
+  mpz_t nn, dd, lo, hi, x;
+  ssize_t e;
+
+  if (SCM_LIKELY (SCM_I_INUMP (d)))
     {
-      if (SCM_I_INUMP (denominator))
-       {
-         long yy = SCM_I_INUM (denominator);
-         if (mpz_divisible_ui_p (SCM_I_BIG_MPZ (numerator), yy))
-           return scm_divide (numerator, denominator);
-       }
-      else
-       {
-         if (scm_is_eq (numerator, denominator))
-           return SCM_I_MAKINUM(1);
-         if (mpz_divisible_p (SCM_I_BIG_MPZ (numerator),
-                              SCM_I_BIG_MPZ (denominator)))
-           return scm_divide(numerator, denominator);
-       }
+      if (SCM_LIKELY
+          (SCM_I_INUMP (n)
+           && INUM_LOSSLESSLY_CONVERTIBLE_TO_DOUBLE (SCM_I_INUM (n))
+           && INUM_LOSSLESSLY_CONVERTIBLE_TO_DOUBLE (SCM_I_INUM (d))))
+        /* If both N and D can be losslessly converted to doubles, then
+           we can rely on IEEE floating point to do proper rounding much
+           faster than we can. */
+        return ((double) SCM_I_INUM (n)) / ((double) SCM_I_INUM (d));
+
+      if (SCM_UNLIKELY (scm_is_eq (d, SCM_INUM0)))
+        {
+          if (scm_is_true (scm_positive_p (n)))
+            return 1.0 / 0.0;
+          else if (scm_is_true (scm_negative_p (n)))
+            return -1.0 / 0.0;
+          else
+            return 0.0 / 0.0;
+        }
+
+      mpz_init_set_si (dd, SCM_I_INUM (d));
     }
+  else
+    mpz_init_set (dd, SCM_I_BIG_MPZ (d));
 
-  /* No, it's a proper fraction.
-   */
+  if (SCM_I_INUMP (n))
+    mpz_init_set_si (nn, SCM_I_INUM (n));
+  else
+    mpz_init_set (nn, SCM_I_BIG_MPZ (n));
+
+  neg = (mpz_sgn (nn) < 0) ^ (mpz_sgn (dd) < 0);
+  mpz_abs (nn, nn);
+  mpz_abs (dd, dd);
+
+  /* Now we need to find the value of e such that:
+     For e <= 0:
+          b^{p-1} - 1/2b  <=      b^-e n / d  <  b^p - 1/2            [1A]
+             (2 b^p - 1)  <=  2 b b^-e n / d  <  (2 b^p - 1) b        [2A]
+           (2 b^p - 1) d  <=  2 b b^-e n      <  (2 b^p - 1) d b      [3A]
+
+     For e >= 0:
+          b^{p-1} - 1/2b  <=      n / b^e d   <  b^p - 1/2            [1B]
+             (2 b^p - 1)  <=  2 b n / b^e d   <  (2 b^p - 1) b        [2B]
+       (2 b^p - 1) d b^e  <=  2 b n           <  (2 b^p - 1) d b b^e  [3B]
+
+         where:  p = DBL_MANT_DIG
+                 b = FLT_RADIX  (here assumed to be 2)
+
+     After rounding, the mantissa must be an integer between b^{p-1} and
+     (b^p - 1), except for subnormal numbers.  In the inequations [1A]
+     and [1B], the middle expression represents the mantissa *before*
+     rounding, and therefore is bounded by the range of values that will
+     round to a floating-point number with the exponent e.  The upper
+     bound is (b^p - 1 + 1/2) = (b^p - 1/2), and is exclusive because
+     ties will round up to the next power of b.  The lower bound is
+     (b^{p-1} - 1/2b), and is inclusive because ties will round toward
+     this power of b.  Here we subtract 1/2b instead of 1/2 because it
+     is in the range of the next smaller exponent, where the
+     representable numbers are closer together by a factor of b.
+
+     Inequations [2A] and [2B] are derived from [1A] and [1B] by
+     multiplying by 2b, and in [3A] and [3B] we multiply by the
+     denominator of the middle value to obtain integer expressions.
+
+     In the code below, we refer to the three expressions in [3A] or
+     [3B] as lo, x, and hi.  If the number is normalizable, we will
+     achieve the goal: lo <= x < hi */
+
+  /* Make an initial guess for e */
+  e = mpz_sizeinbase (nn, 2) - mpz_sizeinbase (dd, 2) - (DBL_MANT_DIG-1);
+  if (e < DBL_MIN_EXP - DBL_MANT_DIG)
+    e = DBL_MIN_EXP - DBL_MANT_DIG;
+
+  /* Compute the initial values of lo, x, and hi
+     based on the initial guess of e */
+  mpz_inits (lo, hi, x, NULL);
+  mpz_mul_2exp (x, nn, 2 + ((e < 0) ? -e : 0));
+  mpz_mul (lo, dd, scm_i_divide2double_lo2b);
+  if (e > 0)
+    mpz_mul_2exp (lo, lo, e);
+  mpz_mul_2exp (hi, lo, 1);
+
+  /* Adjust e as needed to satisfy the inequality lo <= x < hi,
+     (but without making e less then the minimum exponent) */
+  while (mpz_cmp (x, lo) < 0 && e > DBL_MIN_EXP - DBL_MANT_DIG)
+    {
+      mpz_mul_2exp (x, x, 1);
+      e--;
+    }
+  while (mpz_cmp (x, hi) >= 0)
+    {
+      /* If we ever used lo's value again,
+         we would need to double lo here. */
+      mpz_mul_2exp (hi, hi, 1);
+      e++;
+    }
+
+  /* Now compute the rounded mantissa:
+     n / b^e d   (if e >= 0)
+     n b^-e / d  (if e <= 0) */
   {
-    SCM divisor = scm_gcd (numerator, denominator);
-    if (!(scm_is_eq (divisor, SCM_I_MAKINUM(1))))
-      {
-       numerator = scm_divide (numerator, divisor);
-       denominator = scm_divide (denominator, divisor);
-      }
-      
-    return scm_double_cell (scm_tc16_fraction,
-                           SCM_UNPACK (numerator),
-                           SCM_UNPACK (denominator), 0);
+    int cmp;
+    double result;
+
+    if (e < 0)
+      mpz_mul_2exp (nn, nn, -e);
+    else
+      mpz_mul_2exp (dd, dd, e);
+
+    /* mpz does not directly support rounded right
+       shifts, so we have to do it the hard way.
+       For efficiency, we reuse lo and hi.
+       hi == quotient, lo == remainder */
+    mpz_fdiv_qr (hi, lo, nn, dd);
+
+    /* The fractional part of the unrounded mantissa would be
+       remainder/dividend, i.e. lo/dd.  So we have a tie if
+       lo/dd = 1/2.  Multiplying both sides by 2*dd yields the
+       integer expression 2*lo = dd.  Here we do that comparison
+       to decide whether to round up or down. */
+    mpz_mul_2exp (lo, lo, 1);
+    cmp = mpz_cmp (lo, dd);
+    if (cmp > 0 || (cmp == 0 && mpz_odd_p (hi)))
+      mpz_add_ui (hi, hi, 1);
+
+    result = ldexp (mpz_get_d (hi), e);
+    if (neg)
+      result = -result;
+
+    mpz_clears (nn, dd, lo, hi, x, NULL);
+    return result;
   }
 }
-#undef FUNC_NAME
 
 double
 scm_i_fraction2double (SCM z)
 {
-  return scm_to_double (scm_divide2real (SCM_FRACTION_NUMERATOR (z), 
-                                        SCM_FRACTION_DENOMINATOR (z)));
+  return scm_i_divide2double (SCM_FRACTION_NUMERATOR (z),
+                              SCM_FRACTION_DENOMINATOR (z));
 }
 
-SCM_DEFINE (scm_exact_p, "exact?", 1, 0, 0, 
-            (SCM x),
+static int
+double_is_non_negative_zero (double x)
+{
+  static double zero = 0.0;
+
+  return !memcmp (&x, &zero, sizeof(double));
+}
+
+SCM_PRIMITIVE_GENERIC (scm_exact_p, "exact?", 1, 0, 0, 
+                      (SCM x),
            "Return @code{#t} if @var{x} is an exact number, @code{#f}\n"
            "otherwise.")
 #define FUNC_NAME s_scm_exact_p
 {
-  if (SCM_I_INUMP (x))
-    return SCM_BOOL_T;
-  if (SCM_BIGP (x))
+  if (SCM_INEXACTP (x))
+    return SCM_BOOL_F;
+  else if (SCM_NUMBERP (x))
     return SCM_BOOL_T;
-  if (SCM_FRACTIONP (x))
+  else
+    return scm_wta_dispatch_1 (g_scm_exact_p, x, 1, s_scm_exact_p);
+}
+#undef FUNC_NAME
+
+int
+scm_is_exact (SCM val)
+{
+  return scm_is_true (scm_exact_p (val));
+}
+
+SCM_PRIMITIVE_GENERIC (scm_inexact_p, "inexact?", 1, 0, 0,
+            (SCM x),
+           "Return @code{#t} if @var{x} is an inexact number, @code{#f}\n"
+           "else.")
+#define FUNC_NAME s_scm_inexact_p
+{
+  if (SCM_INEXACTP (x))
     return SCM_BOOL_T;
-  if (SCM_NUMBERP (x))
+  else if (SCM_NUMBERP (x))
     return SCM_BOOL_F;
-  SCM_WRONG_TYPE_ARG (1, x);
+  else
+    return scm_wta_dispatch_1 (g_scm_inexact_p, x, 1, s_scm_inexact_p);
 }
 #undef FUNC_NAME
 
+int
+scm_is_inexact (SCM val)
+{
+  return scm_is_true (scm_inexact_p (val));
+}
 
-SCM_DEFINE (scm_odd_p, "odd?", 1, 0, 0, 
+SCM_PRIMITIVE_GENERIC (scm_odd_p, "odd?", 1, 0, 0, 
             (SCM n),
            "Return @code{#t} if @var{n} is an odd number, @code{#f}\n"
            "otherwise.")
@@ -484,7 +712,7 @@ SCM_DEFINE (scm_odd_p, "odd?", 1, 0, 0,
 {
   if (SCM_I_INUMP (n))
     {
-      long val = SCM_I_INUM (n);
+      scm_t_inum val = SCM_I_INUM (n);
       return scm_from_bool ((val & 1L) != 0);
     }
   else if (SCM_BIGP (n))
@@ -493,25 +721,24 @@ SCM_DEFINE (scm_odd_p, "odd?", 1, 0, 0,
       scm_remember_upto_here_1 (n);
       return scm_from_bool (odd_p);
     }
-  else if (scm_is_true (scm_inf_p (n)))
-    return SCM_BOOL_T;
   else if (SCM_REALP (n))
     {
-      double rem = fabs (fmod (SCM_REAL_VALUE(n), 2.0));
-      if (rem == 1.0)
-       return SCM_BOOL_T;
-      else if (rem == 0.0)
-       return SCM_BOOL_F;
-      else
-       SCM_WRONG_TYPE_ARG (1, n);
+      double val = SCM_REAL_VALUE (n);
+      if (DOUBLE_IS_FINITE (val))
+       {
+         double rem = fabs (fmod (val, 2.0));
+         if (rem == 1.0)
+           return SCM_BOOL_T;
+         else if (rem == 0.0)
+           return SCM_BOOL_F;
+       }
     }
-  else
-    SCM_WRONG_TYPE_ARG (1, n);
+  return scm_wta_dispatch_1 (g_scm_odd_p, n, 1, s_scm_odd_p);
 }
 #undef FUNC_NAME
 
 
-SCM_DEFINE (scm_even_p, "even?", 1, 0, 0, 
+SCM_PRIMITIVE_GENERIC (scm_even_p, "even?", 1, 0, 0, 
             (SCM n),
            "Return @code{#t} if @var{n} is an even number, @code{#f}\n"
            "otherwise.")
@@ -519,7 +746,7 @@ SCM_DEFINE (scm_even_p, "even?", 1, 0, 0,
 {
   if (SCM_I_INUMP (n))
     {
-      long val = SCM_I_INUM (n);
+      scm_t_inum val = SCM_I_INUM (n);
       return scm_from_bool ((val & 1L) == 0);
     }
   else if (SCM_BIGP (n))
@@ -528,52 +755,64 @@ SCM_DEFINE (scm_even_p, "even?", 1, 0, 0,
       scm_remember_upto_here_1 (n);
       return scm_from_bool (even_p);
     }
-  else if (scm_is_true (scm_inf_p (n)))
-    return SCM_BOOL_T;
   else if (SCM_REALP (n))
     {
-      double rem = fabs (fmod (SCM_REAL_VALUE(n), 2.0));
-      if (rem == 1.0)
-       return SCM_BOOL_F;
-      else if (rem == 0.0)
-       return SCM_BOOL_T;
-      else
-       SCM_WRONG_TYPE_ARG (1, n);
+      double val = SCM_REAL_VALUE (n);
+      if (DOUBLE_IS_FINITE (val))
+       {
+         double rem = fabs (fmod (val, 2.0));
+         if (rem == 1.0)
+           return SCM_BOOL_F;
+         else if (rem == 0.0)
+           return SCM_BOOL_T;
+       }
     }
+  return scm_wta_dispatch_1 (g_scm_even_p, n, 1, s_scm_even_p);
+}
+#undef FUNC_NAME
+
+SCM_PRIMITIVE_GENERIC (scm_finite_p, "finite?", 1, 0, 0,
+                      (SCM x),
+           "Return @code{#t} if the real number @var{x} is neither\n"
+           "infinite nor a NaN, @code{#f} otherwise.")
+#define FUNC_NAME s_scm_finite_p
+{
+  if (SCM_REALP (x))
+    return scm_from_bool (DOUBLE_IS_FINITE (SCM_REAL_VALUE (x)));
+  else if (scm_is_real (x))
+    return SCM_BOOL_T;
   else
-    SCM_WRONG_TYPE_ARG (1, n);
+    return scm_wta_dispatch_1 (g_scm_finite_p, x, 1, s_scm_finite_p);
 }
 #undef FUNC_NAME
 
-SCM_DEFINE (scm_inf_p, "inf?", 1, 0, 0, 
-            (SCM x),
-           "Return @code{#t} if @var{x} is either @samp{+inf.0}\n"
-           "or @samp{-inf.0}, @code{#f} otherwise.")
+SCM_PRIMITIVE_GENERIC (scm_inf_p, "inf?", 1, 0, 0, 
+                      (SCM x),
+       "Return @code{#t} if the real number @var{x} is @samp{+inf.0} or\n"
+        "@samp{-inf.0}.  Otherwise return @code{#f}.")
 #define FUNC_NAME s_scm_inf_p
 {
   if (SCM_REALP (x))
     return scm_from_bool (isinf (SCM_REAL_VALUE (x)));
-  else if (SCM_COMPLEXP (x))
-    return scm_from_bool (isinf (SCM_COMPLEX_REAL (x))
-                         || isinf (SCM_COMPLEX_IMAG (x)));
-  else
+  else if (scm_is_real (x))
     return SCM_BOOL_F;
+  else
+    return scm_wta_dispatch_1 (g_scm_inf_p, x, 1, s_scm_inf_p);
 }
 #undef FUNC_NAME
 
-SCM_DEFINE (scm_nan_p, "nan?", 1, 0, 0, 
-            (SCM n),
-           "Return @code{#t} if @var{n} is a NaN, @code{#f}\n"
-           "otherwise.")
+SCM_PRIMITIVE_GENERIC (scm_nan_p, "nan?", 1, 0, 0, 
+                      (SCM x),
+           "Return @code{#t} if the real number @var{x} is a NaN,\n"
+            "or @code{#f} otherwise.")
 #define FUNC_NAME s_scm_nan_p
 {
-  if (SCM_REALP (n))
-    return scm_from_bool (isnan (SCM_REAL_VALUE (n)));
-  else if (SCM_COMPLEXP (n))
-    return scm_from_bool (isnan (SCM_COMPLEX_REAL (n))
-                    || isnan (SCM_COMPLEX_IMAG (n)));
-  else
+  if (SCM_REALP (x))
+    return scm_from_bool (isnan (SCM_REAL_VALUE (x)));
+  else if (scm_is_real (x))
     return SCM_BOOL_F;
+  else
+    return scm_wta_dispatch_1 (g_scm_nan_p, x, 1, s_scm_nan_p);
 }
 #undef FUNC_NAME
 
@@ -660,17 +899,29 @@ SCM_DEFINE (scm_nan, "nan", 0, 0, 0,
 SCM_PRIMITIVE_GENERIC (scm_abs, "abs", 1, 0, 0,
                       (SCM x),
                       "Return the absolute value of @var{x}.")
-#define FUNC_NAME
+#define FUNC_NAME s_scm_abs
 {
   if (SCM_I_INUMP (x))
     {
-      long int xx = SCM_I_INUM (x);
+      scm_t_inum xx = SCM_I_INUM (x);
       if (xx >= 0)
        return x;
       else if (SCM_POSFIXABLE (-xx))
        return SCM_I_MAKINUM (-xx);
       else
-       return scm_i_long2big (-xx);
+       return scm_i_inum2big (-xx);
+    }
+  else if (SCM_LIKELY (SCM_REALP (x)))
+    {
+      double xx = SCM_REAL_VALUE (x);
+      /* If x is a NaN then xx<0 is false so we return x unchanged */
+      if (xx < 0.0)
+        return scm_from_double (-xx);
+      /* Handle signed zeroes properly */
+      else if (SCM_UNLIKELY (xx == 0.0))
+       return flo0;
+      else
+        return x;
     }
   else if (SCM_BIGP (x))
     {
@@ -680,313 +931,3136 @@ SCM_PRIMITIVE_GENERIC (scm_abs, "abs", 1, 0, 0,
       else
        return x;
     }
-  else if (SCM_REALP (x))
-    {
-      /* note that if x is a NaN then xx<0 is false so we return x unchanged */
-      double xx = SCM_REAL_VALUE (x);
-      if (xx < 0.0)
-        return scm_from_double (-xx);
-      else
-        return x;
-    }
   else if (SCM_FRACTIONP (x))
     {
       if (scm_is_false (scm_negative_p (SCM_FRACTION_NUMERATOR (x))))
        return x;
-      return scm_i_make_ratio (scm_difference (SCM_FRACTION_NUMERATOR (x), SCM_UNDEFINED),
-                            SCM_FRACTION_DENOMINATOR (x));
+      return scm_i_make_ratio_already_reduced
+       (scm_difference (SCM_FRACTION_NUMERATOR (x), SCM_UNDEFINED),
+        SCM_FRACTION_DENOMINATOR (x));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_abs, x, 1, s_scm_abs);
+    return scm_wta_dispatch_1 (g_scm_abs, x, 1, s_scm_abs);
 }
 #undef FUNC_NAME
 
 
-SCM_GPROC (s_quotient, "quotient", 2, 0, 0, scm_quotient, g_quotient);
-/* "Return the quotient of the numbers @var{x} and @var{y}."
- */
-SCM
-scm_quotient (SCM x, SCM y)
+SCM_PRIMITIVE_GENERIC (scm_quotient, "quotient", 2, 0, 0,
+                      (SCM x, SCM y),
+       "Return the quotient of the numbers @var{x} and @var{y}.")
+#define FUNC_NAME s_scm_quotient
 {
-  if (SCM_I_INUMP (x))
+  if (SCM_LIKELY (scm_is_integer (x)))
     {
-      long xx = SCM_I_INUM (x);
-      if (SCM_I_INUMP (y))
+      if (SCM_LIKELY (scm_is_integer (y)))
+       return scm_truncate_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_quotient, x, y, SCM_ARG2, s_scm_quotient);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_quotient, x, y, SCM_ARG1, s_scm_quotient);
+}
+#undef FUNC_NAME
+
+SCM_PRIMITIVE_GENERIC (scm_remainder, "remainder", 2, 0, 0,
+                      (SCM x, SCM y),
+       "Return the remainder of the numbers @var{x} and @var{y}.\n"
+       "@lisp\n"
+       "(remainder 13 4) @result{} 1\n"
+       "(remainder -13 4) @result{} -1\n"
+       "@end lisp")
+#define FUNC_NAME s_scm_remainder
+{
+  if (SCM_LIKELY (scm_is_integer (x)))
+    {
+      if (SCM_LIKELY (scm_is_integer (y)))
+       return scm_truncate_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_remainder, x, y, SCM_ARG2, s_scm_remainder);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_remainder, x, y, SCM_ARG1, s_scm_remainder);
+}
+#undef FUNC_NAME
+
+
+SCM_PRIMITIVE_GENERIC (scm_modulo, "modulo", 2, 0, 0,
+                      (SCM x, SCM y),
+       "Return the modulo of the numbers @var{x} and @var{y}.\n"
+       "@lisp\n"
+       "(modulo 13 4) @result{} 1\n"
+       "(modulo -13 4) @result{} 3\n"
+       "@end lisp")
+#define FUNC_NAME s_scm_modulo
+{
+  if (SCM_LIKELY (scm_is_integer (x)))
+    {
+      if (SCM_LIKELY (scm_is_integer (y)))
+       return scm_floor_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_modulo, x, y, SCM_ARG2, s_scm_modulo);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_modulo, x, y, SCM_ARG1, s_scm_modulo);
+}
+#undef FUNC_NAME
+
+/* Return the exact integer q such that n = q*d, for exact integers n
+   and d, where d is known in advance to divide n evenly (with zero
+   remainder).  For large integers, this can be computed more
+   efficiently than when the remainder is unknown. */
+static SCM
+scm_exact_integer_quotient (SCM n, SCM d)
+#define FUNC_NAME "exact-integer-quotient"
+{
+  if (SCM_LIKELY (SCM_I_INUMP (n)))
+    {
+      scm_t_inum nn = SCM_I_INUM (n);
+      if (SCM_LIKELY (SCM_I_INUMP (d)))
        {
-         long yy = SCM_I_INUM (y);
-         if (yy == 0)
-           scm_num_overflow (s_quotient);
+         scm_t_inum dd = SCM_I_INUM (d);
+         if (SCM_UNLIKELY (dd == 0))
+           scm_num_overflow ("exact-integer-quotient");
          else
            {
-             long z = xx / yy;
-             if (SCM_FIXABLE (z))
-               return SCM_I_MAKINUM (z);
+             scm_t_inum qq = nn / dd;
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               return SCM_I_MAKINUM (qq);
              else
-               return scm_i_long2big (z);
+               return scm_i_inum2big (qq);
            }
        }
-      else if (SCM_BIGP (y))
+      else if (SCM_LIKELY (SCM_BIGP (d)))
        {
-         if ((SCM_I_INUM (x) == SCM_MOST_NEGATIVE_FIXNUM)
-             && (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
-                              - SCM_MOST_NEGATIVE_FIXNUM) == 0))
-            {
-              /* Special case:  x == fixnum-min && y == abs (fixnum-min) */
-             scm_remember_upto_here_1 (y);
-              return SCM_I_MAKINUM (-1);
-            }
+         /* n is an inum and d is a bignum.  Given that d is known to
+            divide n evenly, there are only two possibilities: n is 0,
+            or else n is fixnum-min and d is abs(fixnum-min). */
+         if (nn == 0)
+           return SCM_INUM0;
          else
-           return SCM_I_MAKINUM (0);
+           return SCM_I_MAKINUM (-1);
        }
       else
-       SCM_WTA_DISPATCH_2 (g_quotient, x, y, SCM_ARG2, s_quotient);
+       SCM_WRONG_TYPE_ARG (2, d);
     }
-  else if (SCM_BIGP (x))
+  else if (SCM_LIKELY (SCM_BIGP (n)))
     {
-      if (SCM_I_INUMP (y))
+      if (SCM_LIKELY (SCM_I_INUMP (d)))
        {
-         long yy = SCM_I_INUM (y);
-         if (yy == 0)
-           scm_num_overflow (s_quotient);
-         else if (yy == 1)
-           return x;
+         scm_t_inum dd = SCM_I_INUM (d);
+         if (SCM_UNLIKELY (dd == 0))
+           scm_num_overflow ("exact-integer-quotient");
+         else if (SCM_UNLIKELY (dd == 1))
+           return n;
          else
            {
-             SCM result = scm_i_mkbig ();
-             if (yy < 0)
+             SCM q = scm_i_mkbig ();
+             if (dd > 0)
+               mpz_divexact_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (n), dd);
+             else
                {
-                 mpz_tdiv_q_ui (SCM_I_BIG_MPZ (result),
-                                SCM_I_BIG_MPZ (x),
-                                - yy);
-                 mpz_neg (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (result));
+                 mpz_divexact_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (n), -dd);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
                }
-             else
-               mpz_tdiv_q_ui (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (x), yy);
-             scm_remember_upto_here_1 (x);
-             return scm_i_normbig (result);
+             scm_remember_upto_here_1 (n);
+             return scm_i_normbig (q);
            }
        }
-      else if (SCM_BIGP (y))
+      else if (SCM_LIKELY (SCM_BIGP (d)))
        {
-         SCM result = scm_i_mkbig ();
-         mpz_tdiv_q (SCM_I_BIG_MPZ (result),
-                     SCM_I_BIG_MPZ (x),
-                     SCM_I_BIG_MPZ (y));
-         scm_remember_upto_here_2 (x, y);
-         return scm_i_normbig (result);
+         SCM q = scm_i_mkbig ();
+         mpz_divexact (SCM_I_BIG_MPZ (q),
+                       SCM_I_BIG_MPZ (n),
+                       SCM_I_BIG_MPZ (d));
+         scm_remember_upto_here_2 (n, d);
+         return scm_i_normbig (q);
        }
       else
-       SCM_WTA_DISPATCH_2 (g_quotient, x, y, SCM_ARG2, s_quotient);
+       SCM_WRONG_TYPE_ARG (2, d);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_quotient, x, y, SCM_ARG1, s_quotient);
+    SCM_WRONG_TYPE_ARG (1, n);
+}
+#undef FUNC_NAME
+
+/* two_valued_wta_dispatch_2 is a version of SCM_WTA_DISPATCH_2 for
+   two-valued functions.  It is called from primitive generics that take
+   two arguments and return two values, when the core procedure is
+   unable to handle the given argument types.  If there are GOOPS
+   methods for this primitive generic, it dispatches to GOOPS and, if
+   successful, expects two values to be returned, which are placed in
+   *rp1 and *rp2.  If there are no GOOPS methods, it throws a
+   wrong-type-arg exception.
+
+   FIXME: This obviously belongs somewhere else, but until we decide on
+   the right API, it is here as a static function, because it is needed
+   by the *_divide functions below.
+*/
+static void
+two_valued_wta_dispatch_2 (SCM gf, SCM a1, SCM a2, int pos,
+                          const char *subr, SCM *rp1, SCM *rp2)
+{
+  SCM vals = scm_wta_dispatch_2 (gf, a1, a2, pos, subr);
+  
+  scm_i_extract_values_2 (vals, rp1, rp2);
+}
+
+SCM_DEFINE (scm_euclidean_quotient, "euclidean-quotient", 2, 0, 0,
+           (SCM x, SCM y),
+           "Return the integer @var{q} such that\n"
+           "@math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+           "where @math{0 <= @var{r} < abs(@var{y})}.\n"
+           "@lisp\n"
+           "(euclidean-quotient 123 10) @result{} 12\n"
+           "(euclidean-quotient 123 -10) @result{} -12\n"
+           "(euclidean-quotient -123 10) @result{} -13\n"
+           "(euclidean-quotient -123 -10) @result{} 13\n"
+           "(euclidean-quotient -123.2 -63.5) @result{} 2.0\n"
+           "(euclidean-quotient 16/3 -10/7) @result{} -3\n"
+           "@end lisp")
+#define FUNC_NAME s_scm_euclidean_quotient
+{
+  if (scm_is_false (scm_negative_p (y)))
+    return scm_floor_quotient (x, y);
+  else
+    return scm_ceiling_quotient (x, y);
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_euclidean_remainder, "euclidean-remainder", 2, 0, 0,
+           (SCM x, SCM y),
+           "Return the real number @var{r} such that\n"
+           "@math{0 <= @var{r} < abs(@var{y})} and\n"
+           "@math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+           "for some integer @var{q}.\n"
+           "@lisp\n"
+           "(euclidean-remainder 123 10) @result{} 3\n"
+           "(euclidean-remainder 123 -10) @result{} 3\n"
+           "(euclidean-remainder -123 10) @result{} 7\n"
+           "(euclidean-remainder -123 -10) @result{} 7\n"
+           "(euclidean-remainder -123.2 -63.5) @result{} 3.8\n"
+           "(euclidean-remainder 16/3 -10/7) @result{} 22/21\n"
+           "@end lisp")
+#define FUNC_NAME s_scm_euclidean_remainder
+{
+  if (scm_is_false (scm_negative_p (y)))
+    return scm_floor_remainder (x, y);
+  else
+    return scm_ceiling_remainder (x, y);
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_i_euclidean_divide, "euclidean/", 2, 0, 0,
+           (SCM x, SCM y),
+           "Return the integer @var{q} and the real number @var{r}\n"
+           "such that @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+           "and @math{0 <= @var{r} < abs(@var{y})}.\n"
+           "@lisp\n"
+           "(euclidean/ 123 10) @result{} 12 and 3\n"
+           "(euclidean/ 123 -10) @result{} -12 and 3\n"
+           "(euclidean/ -123 10) @result{} -13 and 7\n"
+           "(euclidean/ -123 -10) @result{} 13 and 7\n"
+           "(euclidean/ -123.2 -63.5) @result{} 2.0 and 3.8\n"
+           "(euclidean/ 16/3 -10/7) @result{} -3 and 22/21\n"
+           "@end lisp")
+#define FUNC_NAME s_scm_i_euclidean_divide
+{
+  if (scm_is_false (scm_negative_p (y)))
+    return scm_i_floor_divide (x, y);
+  else
+    return scm_i_ceiling_divide (x, y);
+}
+#undef FUNC_NAME
+
+void
+scm_euclidean_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  if (scm_is_false (scm_negative_p (y)))
+    return scm_floor_divide (x, y, qp, rp);
+  else
+    return scm_ceiling_divide (x, y, qp, rp);
+}
+
+static SCM scm_i_inexact_floor_quotient (double x, double y);
+static SCM scm_i_exact_rational_floor_quotient (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_floor_quotient, "floor-quotient", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the floor of @math{@var{x} / @var{y}}.\n"
+                      "@lisp\n"
+                      "(floor-quotient 123 10) @result{} 12\n"
+                      "(floor-quotient 123 -10) @result{} -13\n"
+                      "(floor-quotient -123 10) @result{} -13\n"
+                      "(floor-quotient -123 -10) @result{} 12\n"
+                      "(floor-quotient -123.2 -63.5) @result{} 1.0\n"
+                      "(floor-quotient 16/3 -10/7) @result{} -4\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_floor_quotient
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         scm_t_inum xx1 = xx;
+         scm_t_inum qq;
+         if (SCM_LIKELY (yy > 0))
+           {
+             if (SCM_UNLIKELY (xx < 0))
+               xx1 = xx - yy + 1;
+           }
+         else if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_floor_quotient);
+         else if (xx > 0)
+           xx1 = xx - yy - 1;
+         qq = xx1 / yy;
+         if (SCM_LIKELY (SCM_FIXABLE (qq)))
+           return SCM_I_MAKINUM (qq);
+         else
+           return scm_i_inum2big (qq);
+       }
+      else if (SCM_BIGP (y))
+       {
+         int sign = mpz_sgn (SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_1 (y);
+         if (sign > 0)
+           return SCM_I_MAKINUM ((xx < 0) ? -1 : 0);
+         else
+           return SCM_I_MAKINUM ((xx > 0) ? -1 : 0);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_floor_quotient (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_quotient, x, y, SCM_ARG2,
+                                   s_scm_floor_quotient);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_floor_quotient);
+         else if (SCM_UNLIKELY (yy == 1))
+           return x;
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             if (yy > 0)
+               mpz_fdiv_q_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (x), yy);
+             else
+               {
+                 mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+               }
+             scm_remember_upto_here_1 (x);
+             return scm_i_normbig (q);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM q = scm_i_mkbig ();
+         mpz_fdiv_q (SCM_I_BIG_MPZ (q),
+                     SCM_I_BIG_MPZ (x),
+                     SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         return scm_i_normbig (q);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_floor_quotient
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_quotient, x, y, SCM_ARG2,
+                                   s_scm_floor_quotient);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_floor_quotient
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_quotient, x, y, SCM_ARG2,
+                                   s_scm_floor_quotient);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_floor_quotient
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_quotient, x, y, SCM_ARG2,
+                                   s_scm_floor_quotient);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_floor_quotient, x, y, SCM_ARG1,
+                               s_scm_floor_quotient);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_floor_quotient (double x, double y)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_floor_quotient);  /* or return a NaN? */
+  else
+    return scm_from_double (floor (x / y));
+}
+
+static SCM
+scm_i_exact_rational_floor_quotient (SCM x, SCM y)
+{
+  return scm_floor_quotient
+    (scm_product (scm_numerator (x), scm_denominator (y)),
+     scm_product (scm_numerator (y), scm_denominator (x)));
+}
+
+static SCM scm_i_inexact_floor_remainder (double x, double y);
+static SCM scm_i_exact_rational_floor_remainder (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_floor_remainder, "floor-remainder", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the real number @var{r} such that\n"
+                      "@math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "where @math{@var{q} = floor(@var{x} / @var{y})}.\n"
+                      "@lisp\n"
+                      "(floor-remainder 123 10) @result{} 3\n"
+                      "(floor-remainder 123 -10) @result{} -7\n"
+                      "(floor-remainder -123 10) @result{} 7\n"
+                      "(floor-remainder -123 -10) @result{} -3\n"
+                      "(floor-remainder -123.2 -63.5) @result{} -59.7\n"
+                      "(floor-remainder 16/3 -10/7) @result{} -8/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_floor_remainder
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_floor_remainder);
+         else
+           {
+             scm_t_inum rr = xx % yy;
+             int needs_adjustment;
+
+             if (SCM_LIKELY (yy > 0))
+               needs_adjustment = (rr < 0);
+             else
+               needs_adjustment = (rr > 0);
+
+             if (needs_adjustment)
+               rr += yy;
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         int sign = mpz_sgn (SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_1 (y);
+         if (sign > 0)
+           {
+             if (xx < 0)
+               {
+                 SCM r = scm_i_mkbig ();
+                 mpz_sub_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), -xx);
+                 scm_remember_upto_here_1 (y);
+                 return scm_i_normbig (r);
+               }
+             else
+               return x;
+           }
+         else if (xx <= 0)
+           return x;
+         else
+           {
+             SCM r = scm_i_mkbig ();
+             mpz_add_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), xx);
+             scm_remember_upto_here_1 (y);
+             return scm_i_normbig (r);
+           }
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_floor_remainder (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_remainder, x, y, SCM_ARG2,
+                                   s_scm_floor_remainder);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_floor_remainder);
+         else
+           {
+             scm_t_inum rr;
+             if (yy > 0)
+               rr = mpz_fdiv_ui (SCM_I_BIG_MPZ (x), yy);
+             else
+               rr = -mpz_cdiv_ui (SCM_I_BIG_MPZ (x), -yy);
+             scm_remember_upto_here_1 (x);
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM r = scm_i_mkbig ();
+         mpz_fdiv_r (SCM_I_BIG_MPZ (r),
+                     SCM_I_BIG_MPZ (x),
+                     SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         return scm_i_normbig (r);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_floor_remainder
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_remainder, x, y, SCM_ARG2,
+                                   s_scm_floor_remainder);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_floor_remainder
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_remainder, x, y, SCM_ARG2,
+                                   s_scm_floor_remainder);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_floor_remainder
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_floor_remainder, x, y, SCM_ARG2,
+                                   s_scm_floor_remainder);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_floor_remainder, x, y, SCM_ARG1,
+                               s_scm_floor_remainder);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_floor_remainder (double x, double y)
+{
+  /* Although it would be more efficient to use fmod here, we can't
+     because it would in some cases produce results inconsistent with
+     scm_i_inexact_floor_quotient, such that x != q * y + r (not even
+     close).  In particular, when x is very close to a multiple of y,
+     then r might be either 0.0 or y, but those two cases must
+     correspond to different choices of q.  If r = 0.0 then q must be
+     x/y, and if r = y then q must be x/y-1.  If quotient chooses one
+     and remainder chooses the other, it would be bad.  */
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_floor_remainder);  /* or return a NaN? */
+  else
+    return scm_from_double (x - y * floor (x / y));
+}
+
+static SCM
+scm_i_exact_rational_floor_remainder (SCM x, SCM y)
+{
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+  SCM r1 = scm_floor_remainder (scm_product (scm_numerator (x), yd),
+                               scm_product (scm_numerator (y), xd));
+  return scm_divide (r1, scm_product (xd, yd));
+}
+
+
+static void scm_i_inexact_floor_divide (double x, double y,
+                                       SCM *qp, SCM *rp);
+static void scm_i_exact_rational_floor_divide (SCM x, SCM y,
+                                              SCM *qp, SCM *rp);
+
+SCM_PRIMITIVE_GENERIC (scm_i_floor_divide, "floor/", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the integer @var{q} and the real number @var{r}\n"
+                      "such that @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "and @math{@var{q} = floor(@var{x} / @var{y})}.\n"
+                      "@lisp\n"
+                      "(floor/ 123 10) @result{} 12 and 3\n"
+                      "(floor/ 123 -10) @result{} -13 and -7\n"
+                      "(floor/ -123 10) @result{} -13 and 7\n"
+                      "(floor/ -123 -10) @result{} 12 and -3\n"
+                      "(floor/ -123.2 -63.5) @result{} 1.0 and -59.7\n"
+                      "(floor/ 16/3 -10/7) @result{} -4 and -8/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_i_floor_divide
+{
+  SCM q, r;
+
+  scm_floor_divide(x, y, &q, &r);
+  return scm_values (scm_list_2 (q, r));
+}
+#undef FUNC_NAME
+
+#define s_scm_floor_divide s_scm_i_floor_divide
+#define g_scm_floor_divide g_scm_i_floor_divide
+
+void
+scm_floor_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_floor_divide);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             int needs_adjustment;
+
+             if (SCM_LIKELY (yy > 0))
+               needs_adjustment = (rr < 0);
+             else
+               needs_adjustment = (rr > 0);
+
+             if (needs_adjustment)
+               {
+                 rr += yy;
+                 qq--;
+               }
+
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               *qp = SCM_I_MAKINUM (qq);
+             else
+               *qp = scm_i_inum2big (qq);
+             *rp = SCM_I_MAKINUM (rr);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         int sign = mpz_sgn (SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_1 (y);
+         if (sign > 0)
+           {
+             if (xx < 0)
+               {
+                 SCM r = scm_i_mkbig ();
+                 mpz_sub_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), -xx);
+                 scm_remember_upto_here_1 (y);
+                 *qp = SCM_I_MAKINUM (-1);
+                 *rp = scm_i_normbig (r);
+               }
+             else
+               {
+                 *qp = SCM_INUM0;
+                 *rp = x;
+               }
+           }
+         else if (xx <= 0)
+           {
+             *qp = SCM_INUM0;
+             *rp = x;
+           }
+         else
+           {
+             SCM r = scm_i_mkbig ();
+             mpz_add_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), xx);
+             scm_remember_upto_here_1 (y);
+             *qp = SCM_I_MAKINUM (-1);
+             *rp = scm_i_normbig (r);
+           }
+         return;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_floor_divide (xx, SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_floor_divide, x, y, SCM_ARG2,
+                                         s_scm_floor_divide, qp, rp);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_floor_divide);
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             SCM r = scm_i_mkbig ();
+             if (yy > 0)
+               mpz_fdiv_qr_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                               SCM_I_BIG_MPZ (x), yy);
+             else
+               {
+                 mpz_cdiv_qr_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                                 SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+               }
+             scm_remember_upto_here_1 (x);
+             *qp = scm_i_normbig (q);
+             *rp = scm_i_normbig (r);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM q = scm_i_mkbig ();
+         SCM r = scm_i_mkbig ();
+         mpz_fdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                      SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         *qp = scm_i_normbig (q);
+         *rp = scm_i_normbig (r);
+         return;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_floor_divide
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_floor_divide, x, y, SCM_ARG2,
+                                         s_scm_floor_divide, qp, rp);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_floor_divide
+         (SCM_REAL_VALUE (x), scm_to_double (y), qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_floor_divide, x, y, SCM_ARG2,
+                                         s_scm_floor_divide, qp, rp);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_floor_divide
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_floor_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_floor_divide, x, y, SCM_ARG2,
+                                         s_scm_floor_divide, qp, rp);
+    }
+  else
+    return two_valued_wta_dispatch_2 (g_scm_floor_divide, x, y, SCM_ARG1,
+                                     s_scm_floor_divide, qp, rp);
+}
+
+static void
+scm_i_inexact_floor_divide (double x, double y, SCM *qp, SCM *rp)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_floor_divide);  /* or return a NaN? */
+  else
+    {
+      double q = floor (x / y);
+      double r = x - q * y;
+      *qp = scm_from_double (q);
+      *rp = scm_from_double (r);
+    }
+}
+
+static void
+scm_i_exact_rational_floor_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM r1;
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+
+  scm_floor_divide (scm_product (scm_numerator (x), yd),
+                   scm_product (scm_numerator (y), xd),
+                   qp, &r1);
+  *rp = scm_divide (r1, scm_product (xd, yd));
+}
+
+static SCM scm_i_inexact_ceiling_quotient (double x, double y);
+static SCM scm_i_exact_rational_ceiling_quotient (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_ceiling_quotient, "ceiling-quotient", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the ceiling of @math{@var{x} / @var{y}}.\n"
+                      "@lisp\n"
+                      "(ceiling-quotient 123 10) @result{} 13\n"
+                      "(ceiling-quotient 123 -10) @result{} -12\n"
+                      "(ceiling-quotient -123 10) @result{} -12\n"
+                      "(ceiling-quotient -123 -10) @result{} 13\n"
+                      "(ceiling-quotient -123.2 -63.5) @result{} 2.0\n"
+                      "(ceiling-quotient 16/3 -10/7) @result{} -3\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_ceiling_quotient
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_ceiling_quotient);
+         else
+           {
+             scm_t_inum xx1 = xx;
+             scm_t_inum qq;
+             if (SCM_LIKELY (yy > 0))
+               {
+                 if (SCM_LIKELY (xx >= 0))
+                   xx1 = xx + yy - 1;
+               }
+             else if (xx < 0)
+               xx1 = xx + yy + 1;
+             qq = xx1 / yy;
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               return SCM_I_MAKINUM (qq);
+             else
+               return scm_i_inum2big (qq);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         int sign = mpz_sgn (SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_1 (y);
+         if (SCM_LIKELY (sign > 0))
+           {
+             if (SCM_LIKELY (xx > 0))
+               return SCM_INUM1;
+             else if (SCM_UNLIKELY (xx == SCM_MOST_NEGATIVE_FIXNUM)
+                      && SCM_UNLIKELY (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
+                                      - SCM_MOST_NEGATIVE_FIXNUM) == 0))
+               {
+                 /* Special case: x == fixnum-min && y == abs (fixnum-min) */
+                 scm_remember_upto_here_1 (y);
+                 return SCM_I_MAKINUM (-1);
+               }
+             else
+               return SCM_INUM0;
+           }
+         else if (xx >= 0)
+           return SCM_INUM0;
+         else
+           return SCM_INUM1;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_quotient (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_quotient, x, y, SCM_ARG2,
+                                   s_scm_ceiling_quotient);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_ceiling_quotient);
+         else if (SCM_UNLIKELY (yy == 1))
+           return x;
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             if (yy > 0)
+               mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (x), yy);
+             else
+               {
+                 mpz_fdiv_q_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+               }
+             scm_remember_upto_here_1 (x);
+             return scm_i_normbig (q);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM q = scm_i_mkbig ();
+         mpz_cdiv_q (SCM_I_BIG_MPZ (q),
+                     SCM_I_BIG_MPZ (x),
+                     SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         return scm_i_normbig (q);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_quotient
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_quotient, x, y, SCM_ARG2,
+                                   s_scm_ceiling_quotient);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_ceiling_quotient
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_quotient, x, y, SCM_ARG2,
+                                   s_scm_ceiling_quotient);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_quotient
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_quotient, x, y, SCM_ARG2,
+                                   s_scm_ceiling_quotient);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_ceiling_quotient, x, y, SCM_ARG1,
+                               s_scm_ceiling_quotient);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_ceiling_quotient (double x, double y)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_ceiling_quotient);  /* or return a NaN? */
+  else
+    return scm_from_double (ceil (x / y));
+}
+
+static SCM
+scm_i_exact_rational_ceiling_quotient (SCM x, SCM y)
+{
+  return scm_ceiling_quotient
+    (scm_product (scm_numerator (x), scm_denominator (y)),
+     scm_product (scm_numerator (y), scm_denominator (x)));
+}
+
+static SCM scm_i_inexact_ceiling_remainder (double x, double y);
+static SCM scm_i_exact_rational_ceiling_remainder (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_ceiling_remainder, "ceiling-remainder", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the real number @var{r} such that\n"
+                      "@math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "where @math{@var{q} = ceiling(@var{x} / @var{y})}.\n"
+                      "@lisp\n"
+                      "(ceiling-remainder 123 10) @result{} -7\n"
+                      "(ceiling-remainder 123 -10) @result{} 3\n"
+                      "(ceiling-remainder -123 10) @result{} -3\n"
+                      "(ceiling-remainder -123 -10) @result{} 7\n"
+                      "(ceiling-remainder -123.2 -63.5) @result{} 3.8\n"
+                      "(ceiling-remainder 16/3 -10/7) @result{} 22/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_ceiling_remainder
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_ceiling_remainder);
+         else
+           {
+             scm_t_inum rr = xx % yy;
+             int needs_adjustment;
+
+             if (SCM_LIKELY (yy > 0))
+               needs_adjustment = (rr > 0);
+             else
+               needs_adjustment = (rr < 0);
+
+             if (needs_adjustment)
+               rr -= yy;
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         int sign = mpz_sgn (SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_1 (y);
+         if (SCM_LIKELY (sign > 0))
+           {
+             if (SCM_LIKELY (xx > 0))
+               {
+                 SCM r = scm_i_mkbig ();
+                 mpz_sub_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), xx);
+                 scm_remember_upto_here_1 (y);
+                 mpz_neg (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (r));
+                 return scm_i_normbig (r);
+               }
+             else if (SCM_UNLIKELY (xx == SCM_MOST_NEGATIVE_FIXNUM)
+                      && SCM_UNLIKELY (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
+                                      - SCM_MOST_NEGATIVE_FIXNUM) == 0))
+               {
+                 /* Special case: x == fixnum-min && y == abs (fixnum-min) */
+                 scm_remember_upto_here_1 (y);
+                 return SCM_INUM0;
+               }
+             else
+               return x;
+           }
+         else if (xx >= 0)
+           return x;
+         else
+           {
+             SCM r = scm_i_mkbig ();
+             mpz_add_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), -xx);
+             scm_remember_upto_here_1 (y);
+             mpz_neg (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (r));
+             return scm_i_normbig (r);
+           }
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_remainder (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_remainder, x, y, SCM_ARG2,
+                                   s_scm_ceiling_remainder);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_ceiling_remainder);
+         else
+           {
+             scm_t_inum rr;
+             if (yy > 0)
+               rr = -mpz_cdiv_ui (SCM_I_BIG_MPZ (x), yy);
+             else
+               rr = mpz_fdiv_ui (SCM_I_BIG_MPZ (x), -yy);
+             scm_remember_upto_here_1 (x);
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM r = scm_i_mkbig ();
+         mpz_cdiv_r (SCM_I_BIG_MPZ (r),
+                     SCM_I_BIG_MPZ (x),
+                     SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         return scm_i_normbig (r);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_remainder
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_remainder, x, y, SCM_ARG2,
+                                   s_scm_ceiling_remainder);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_ceiling_remainder
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_remainder, x, y, SCM_ARG2,
+                                   s_scm_ceiling_remainder);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_remainder
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_ceiling_remainder, x, y, SCM_ARG2,
+                                   s_scm_ceiling_remainder);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_ceiling_remainder, x, y, SCM_ARG1,
+                               s_scm_ceiling_remainder);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_ceiling_remainder (double x, double y)
+{
+  /* Although it would be more efficient to use fmod here, we can't
+     because it would in some cases produce results inconsistent with
+     scm_i_inexact_ceiling_quotient, such that x != q * y + r (not even
+     close).  In particular, when x is very close to a multiple of y,
+     then r might be either 0.0 or -y, but those two cases must
+     correspond to different choices of q.  If r = 0.0 then q must be
+     x/y, and if r = -y then q must be x/y+1.  If quotient chooses one
+     and remainder chooses the other, it would be bad.  */
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_ceiling_remainder);  /* or return a NaN? */
+  else
+    return scm_from_double (x - y * ceil (x / y));
+}
+
+static SCM
+scm_i_exact_rational_ceiling_remainder (SCM x, SCM y)
+{
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+  SCM r1 = scm_ceiling_remainder (scm_product (scm_numerator (x), yd),
+                                 scm_product (scm_numerator (y), xd));
+  return scm_divide (r1, scm_product (xd, yd));
+}
+
+static void scm_i_inexact_ceiling_divide (double x, double y,
+                                         SCM *qp, SCM *rp);
+static void scm_i_exact_rational_ceiling_divide (SCM x, SCM y,
+                                                SCM *qp, SCM *rp);
+
+SCM_PRIMITIVE_GENERIC (scm_i_ceiling_divide, "ceiling/", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the integer @var{q} and the real number @var{r}\n"
+                      "such that @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "and @math{@var{q} = ceiling(@var{x} / @var{y})}.\n"
+                      "@lisp\n"
+                      "(ceiling/ 123 10) @result{} 13 and -7\n"
+                      "(ceiling/ 123 -10) @result{} -12 and 3\n"
+                      "(ceiling/ -123 10) @result{} -12 and -3\n"
+                      "(ceiling/ -123 -10) @result{} 13 and 7\n"
+                      "(ceiling/ -123.2 -63.5) @result{} 2.0 and 3.8\n"
+                      "(ceiling/ 16/3 -10/7) @result{} -3 and 22/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_i_ceiling_divide
+{
+  SCM q, r;
+
+  scm_ceiling_divide(x, y, &q, &r);
+  return scm_values (scm_list_2 (q, r));
+}
+#undef FUNC_NAME
+
+#define s_scm_ceiling_divide s_scm_i_ceiling_divide
+#define g_scm_ceiling_divide g_scm_i_ceiling_divide
+
+void
+scm_ceiling_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_ceiling_divide);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             int needs_adjustment;
+
+             if (SCM_LIKELY (yy > 0))
+               needs_adjustment = (rr > 0);
+             else
+               needs_adjustment = (rr < 0);
+
+             if (needs_adjustment)
+               {
+                 rr -= yy;
+                 qq++;
+               }
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               *qp = SCM_I_MAKINUM (qq);
+             else
+               *qp = scm_i_inum2big (qq);
+             *rp = SCM_I_MAKINUM (rr);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         int sign = mpz_sgn (SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_1 (y);
+         if (SCM_LIKELY (sign > 0))
+           {
+             if (SCM_LIKELY (xx > 0))
+               {
+                 SCM r = scm_i_mkbig ();
+                 mpz_sub_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), xx);
+                 scm_remember_upto_here_1 (y);
+                 mpz_neg (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (r));
+                 *qp = SCM_INUM1;
+                 *rp = scm_i_normbig (r);
+               }
+             else if (SCM_UNLIKELY (xx == SCM_MOST_NEGATIVE_FIXNUM)
+                      && SCM_UNLIKELY (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
+                                      - SCM_MOST_NEGATIVE_FIXNUM) == 0))
+               {
+                 /* Special case: x == fixnum-min && y == abs (fixnum-min) */
+                 scm_remember_upto_here_1 (y);
+                 *qp = SCM_I_MAKINUM (-1);
+                 *rp = SCM_INUM0;
+               }
+             else
+               {
+                 *qp = SCM_INUM0;
+                 *rp = x;
+               }
+           }
+         else if (xx >= 0)
+           {
+             *qp = SCM_INUM0;
+             *rp = x;
+           }
+         else
+           {
+             SCM r = scm_i_mkbig ();
+             mpz_add_ui (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y), -xx);
+             scm_remember_upto_here_1 (y);
+             mpz_neg (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (r));
+             *qp = SCM_INUM1;
+             *rp = scm_i_normbig (r);
+           }
+         return;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_divide (xx, SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_ceiling_divide, x, y, SCM_ARG2,
+                                         s_scm_ceiling_divide, qp, rp);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_ceiling_divide);
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             SCM r = scm_i_mkbig ();
+             if (yy > 0)
+               mpz_cdiv_qr_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                               SCM_I_BIG_MPZ (x), yy);
+             else
+               {
+                 mpz_fdiv_qr_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                                 SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+               }
+             scm_remember_upto_here_1 (x);
+             *qp = scm_i_normbig (q);
+             *rp = scm_i_normbig (r);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM q = scm_i_mkbig ();
+         SCM r = scm_i_mkbig ();
+         mpz_cdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                      SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         *qp = scm_i_normbig (q);
+         *rp = scm_i_normbig (r);
+         return;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_divide
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_ceiling_divide, x, y, SCM_ARG2,
+                                         s_scm_ceiling_divide, qp, rp);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_ceiling_divide
+         (SCM_REAL_VALUE (x), scm_to_double (y), qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_ceiling_divide, x, y, SCM_ARG2,
+                                         s_scm_ceiling_divide, qp, rp);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_ceiling_divide
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_ceiling_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_ceiling_divide, x, y, SCM_ARG2,
+                                         s_scm_ceiling_divide, qp, rp);
+    }
+  else
+    return two_valued_wta_dispatch_2 (g_scm_ceiling_divide, x, y, SCM_ARG1,
+                                     s_scm_ceiling_divide, qp, rp);
+}
+
+static void
+scm_i_inexact_ceiling_divide (double x, double y, SCM *qp, SCM *rp)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_ceiling_divide);  /* or return a NaN? */
+  else
+    {
+      double q = ceil (x / y);
+      double r = x - q * y;
+      *qp = scm_from_double (q);
+      *rp = scm_from_double (r);
+    }
+}
+
+static void
+scm_i_exact_rational_ceiling_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM r1;
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+
+  scm_ceiling_divide (scm_product (scm_numerator (x), yd),
+                     scm_product (scm_numerator (y), xd),
+                     qp, &r1);
+  *rp = scm_divide (r1, scm_product (xd, yd));
+}
+
+static SCM scm_i_inexact_truncate_quotient (double x, double y);
+static SCM scm_i_exact_rational_truncate_quotient (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_truncate_quotient, "truncate-quotient", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return @math{@var{x} / @var{y}} rounded toward zero.\n"
+                      "@lisp\n"
+                      "(truncate-quotient 123 10) @result{} 12\n"
+                      "(truncate-quotient 123 -10) @result{} -12\n"
+                      "(truncate-quotient -123 10) @result{} -12\n"
+                      "(truncate-quotient -123 -10) @result{} 12\n"
+                      "(truncate-quotient -123.2 -63.5) @result{} 1.0\n"
+                      "(truncate-quotient 16/3 -10/7) @result{} -3\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_truncate_quotient
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_truncate_quotient);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               return SCM_I_MAKINUM (qq);
+             else
+               return scm_i_inum2big (qq);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         if (SCM_UNLIKELY (xx == SCM_MOST_NEGATIVE_FIXNUM)
+             && SCM_UNLIKELY (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
+                                          - SCM_MOST_NEGATIVE_FIXNUM) == 0))
+           {
+             /* Special case: x == fixnum-min && y == abs (fixnum-min) */
+             scm_remember_upto_here_1 (y);
+             return SCM_I_MAKINUM (-1);
+           }
+         else
+           return SCM_INUM0;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_truncate_quotient (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_quotient, x, y, SCM_ARG2,
+                                   s_scm_truncate_quotient);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_truncate_quotient);
+         else if (SCM_UNLIKELY (yy == 1))
+           return x;
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             if (yy > 0)
+               mpz_tdiv_q_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (x), yy);
+             else
+               {
+                 mpz_tdiv_q_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+               }
+             scm_remember_upto_here_1 (x);
+             return scm_i_normbig (q);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM q = scm_i_mkbig ();
+         mpz_tdiv_q (SCM_I_BIG_MPZ (q),
+                     SCM_I_BIG_MPZ (x),
+                     SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         return scm_i_normbig (q);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_truncate_quotient
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_quotient, x, y, SCM_ARG2,
+                                   s_scm_truncate_quotient);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_truncate_quotient
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_quotient, x, y, SCM_ARG2,
+                                   s_scm_truncate_quotient);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_truncate_quotient
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_quotient, x, y, SCM_ARG2,
+                                   s_scm_truncate_quotient);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_truncate_quotient, x, y, SCM_ARG1,
+                               s_scm_truncate_quotient);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_truncate_quotient (double x, double y)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_truncate_quotient);  /* or return a NaN? */
+  else
+    return scm_from_double (trunc (x / y));
+}
+
+static SCM
+scm_i_exact_rational_truncate_quotient (SCM x, SCM y)
+{
+  return scm_truncate_quotient
+    (scm_product (scm_numerator (x), scm_denominator (y)),
+     scm_product (scm_numerator (y), scm_denominator (x)));
+}
+
+static SCM scm_i_inexact_truncate_remainder (double x, double y);
+static SCM scm_i_exact_rational_truncate_remainder (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_truncate_remainder, "truncate-remainder", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the real number @var{r} such that\n"
+                      "@math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "where @math{@var{q} = truncate(@var{x} / @var{y})}.\n"
+                      "@lisp\n"
+                      "(truncate-remainder 123 10) @result{} 3\n"
+                      "(truncate-remainder 123 -10) @result{} 3\n"
+                      "(truncate-remainder -123 10) @result{} -3\n"
+                      "(truncate-remainder -123 -10) @result{} -3\n"
+                      "(truncate-remainder -123.2 -63.5) @result{} -59.7\n"
+                      "(truncate-remainder 16/3 -10/7) @result{} 22/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_truncate_remainder
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_truncate_remainder);
+         else
+           return SCM_I_MAKINUM (xx % yy);
+       }
+      else if (SCM_BIGP (y))
+       {
+         if (SCM_UNLIKELY (xx == SCM_MOST_NEGATIVE_FIXNUM)
+             && SCM_UNLIKELY (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
+                                          - SCM_MOST_NEGATIVE_FIXNUM) == 0))
+           {
+             /* Special case: x == fixnum-min && y == abs (fixnum-min) */
+             scm_remember_upto_here_1 (y);
+             return SCM_INUM0;
+           }
+         else
+           return x;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_truncate_remainder (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_remainder, x, y, SCM_ARG2,
+                                   s_scm_truncate_remainder);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_truncate_remainder);
+         else
+           {
+             scm_t_inum rr = (mpz_tdiv_ui (SCM_I_BIG_MPZ (x),
+                                           (yy > 0) ? yy : -yy)
+                              * mpz_sgn (SCM_I_BIG_MPZ (x)));
+             scm_remember_upto_here_1 (x);
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM r = scm_i_mkbig ();
+         mpz_tdiv_r (SCM_I_BIG_MPZ (r),
+                     SCM_I_BIG_MPZ (x),
+                     SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         return scm_i_normbig (r);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_truncate_remainder
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_remainder, x, y, SCM_ARG2,
+                                   s_scm_truncate_remainder);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_truncate_remainder
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_remainder, x, y, SCM_ARG2,
+                                   s_scm_truncate_remainder);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_truncate_remainder
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_truncate_remainder, x, y, SCM_ARG2,
+                                   s_scm_truncate_remainder);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_truncate_remainder, x, y, SCM_ARG1,
+                               s_scm_truncate_remainder);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_truncate_remainder (double x, double y)
+{
+  /* Although it would be more efficient to use fmod here, we can't
+     because it would in some cases produce results inconsistent with
+     scm_i_inexact_truncate_quotient, such that x != q * y + r (not even
+     close).  In particular, when x is very close to a multiple of y,
+     then r might be either 0.0 or sgn(x)*|y|, but those two cases must
+     correspond to different choices of q.  If quotient chooses one and
+     remainder chooses the other, it would be bad.  */
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_truncate_remainder);  /* or return a NaN? */
+  else
+    return scm_from_double (x - y * trunc (x / y));
+}
+
+static SCM
+scm_i_exact_rational_truncate_remainder (SCM x, SCM y)
+{
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+  SCM r1 = scm_truncate_remainder (scm_product (scm_numerator (x), yd),
+                                  scm_product (scm_numerator (y), xd));
+  return scm_divide (r1, scm_product (xd, yd));
+}
+
+
+static void scm_i_inexact_truncate_divide (double x, double y,
+                                          SCM *qp, SCM *rp);
+static void scm_i_exact_rational_truncate_divide (SCM x, SCM y,
+                                                 SCM *qp, SCM *rp);
+
+SCM_PRIMITIVE_GENERIC (scm_i_truncate_divide, "truncate/", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the integer @var{q} and the real number @var{r}\n"
+                      "such that @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "and @math{@var{q} = truncate(@var{x} / @var{y})}.\n"
+                      "@lisp\n"
+                      "(truncate/ 123 10) @result{} 12 and 3\n"
+                      "(truncate/ 123 -10) @result{} -12 and 3\n"
+                      "(truncate/ -123 10) @result{} -12 and -3\n"
+                      "(truncate/ -123 -10) @result{} 12 and -3\n"
+                      "(truncate/ -123.2 -63.5) @result{} 1.0 and -59.7\n"
+                      "(truncate/ 16/3 -10/7) @result{} -3 and 22/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_i_truncate_divide
+{
+  SCM q, r;
+
+  scm_truncate_divide(x, y, &q, &r);
+  return scm_values (scm_list_2 (q, r));
+}
+#undef FUNC_NAME
+
+#define s_scm_truncate_divide s_scm_i_truncate_divide
+#define g_scm_truncate_divide g_scm_i_truncate_divide
+
+void
+scm_truncate_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_truncate_divide);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               *qp = SCM_I_MAKINUM (qq);
+             else
+               *qp = scm_i_inum2big (qq);
+             *rp = SCM_I_MAKINUM (rr);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         if (SCM_UNLIKELY (xx == SCM_MOST_NEGATIVE_FIXNUM)
+             && SCM_UNLIKELY (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
+                                          - SCM_MOST_NEGATIVE_FIXNUM) == 0))
+           {
+             /* Special case: x == fixnum-min && y == abs (fixnum-min) */
+             scm_remember_upto_here_1 (y);
+             *qp = SCM_I_MAKINUM (-1);
+             *rp = SCM_INUM0;
+           }
+         else
+           {
+             *qp = SCM_INUM0;
+             *rp = x;
+           }
+         return;
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_truncate_divide (xx, SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_truncate_divide, x, y, SCM_ARG2,
+          s_scm_truncate_divide, qp, rp);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_truncate_divide);
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             scm_t_inum rr;
+             if (yy > 0)
+               rr = mpz_tdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                   SCM_I_BIG_MPZ (x), yy);
+             else
+               {
+                 rr = mpz_tdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                     SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+               }
+             rr *= mpz_sgn (SCM_I_BIG_MPZ (x));
+             scm_remember_upto_here_1 (x);
+             *qp = scm_i_normbig (q);
+             *rp = SCM_I_MAKINUM (rr);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         SCM q = scm_i_mkbig ();
+         SCM r = scm_i_mkbig ();
+         mpz_tdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                      SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+         scm_remember_upto_here_2 (x, y);
+         *qp = scm_i_normbig (q);
+         *rp = scm_i_normbig (r);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_truncate_divide
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_truncate_divide, x, y, SCM_ARG2,
+          s_scm_truncate_divide, qp, rp);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_truncate_divide
+         (SCM_REAL_VALUE (x), scm_to_double (y), qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_truncate_divide, x, y, SCM_ARG2,
+          s_scm_truncate_divide, qp, rp);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_truncate_divide
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_truncate_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_truncate_divide, x, y, SCM_ARG2,
+          s_scm_truncate_divide, qp, rp);
+    }
+  else
+    return two_valued_wta_dispatch_2 (g_scm_truncate_divide, x, y, SCM_ARG1,
+                                     s_scm_truncate_divide, qp, rp);
+}
+
+static void
+scm_i_inexact_truncate_divide (double x, double y, SCM *qp, SCM *rp)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_truncate_divide);  /* or return a NaN? */
+  else
+    {
+      double q = trunc (x / y);
+      double r = x - q * y;
+      *qp = scm_from_double (q);
+      *rp = scm_from_double (r);
+    }
+}
+
+static void
+scm_i_exact_rational_truncate_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM r1;
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+
+  scm_truncate_divide (scm_product (scm_numerator (x), yd),
+                      scm_product (scm_numerator (y), xd),
+                      qp, &r1);
+  *rp = scm_divide (r1, scm_product (xd, yd));
+}
+
+static SCM scm_i_inexact_centered_quotient (double x, double y);
+static SCM scm_i_bigint_centered_quotient (SCM x, SCM y);
+static SCM scm_i_exact_rational_centered_quotient (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_centered_quotient, "centered-quotient", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the integer @var{q} such that\n"
+                      "@math{@var{x} = @var{q}*@var{y} + @var{r}} where\n"
+                      "@math{-abs(@var{y}/2) <= @var{r} < abs(@var{y}/2)}.\n"
+                      "@lisp\n"
+                      "(centered-quotient 123 10) @result{} 12\n"
+                      "(centered-quotient 123 -10) @result{} -12\n"
+                      "(centered-quotient -123 10) @result{} -12\n"
+                      "(centered-quotient -123 -10) @result{} 12\n"
+                      "(centered-quotient -123.2 -63.5) @result{} 2.0\n"
+                      "(centered-quotient 16/3 -10/7) @result{} -4\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_centered_quotient
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_centered_quotient);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             if (SCM_LIKELY (xx > 0))
+               {
+                 if (SCM_LIKELY (yy > 0))
+                   {
+                     if (rr >= (yy + 1) / 2)
+                       qq++;
+                   }
+                 else
+                   {
+                     if (rr >= (1 - yy) / 2)
+                       qq--;
+                   }
+               }
+             else
+               {
+                 if (SCM_LIKELY (yy > 0))
+                   {
+                     if (rr < -yy / 2)
+                       qq--;
+                   }
+                 else
+                   {
+                     if (rr < yy / 2)
+                       qq++;
+                   }
+               }
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               return SCM_I_MAKINUM (qq);
+             else
+               return scm_i_inum2big (qq);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         /* Pass a denormalized bignum version of x (even though it
+            can fit in a fixnum) to scm_i_bigint_centered_quotient */
+         return scm_i_bigint_centered_quotient (scm_i_long2big (xx), y);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_centered_quotient (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_quotient, x, y, SCM_ARG2,
+                                   s_scm_centered_quotient);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_centered_quotient);
+         else if (SCM_UNLIKELY (yy == 1))
+           return x;
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             scm_t_inum rr;
+             /* Arrange for rr to initially be non-positive,
+                because that simplifies the test to see
+                if it is within the needed bounds. */
+             if (yy > 0)
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), yy);
+                 scm_remember_upto_here_1 (x);
+                 if (rr < -yy / 2)
+                   mpz_sub_ui (SCM_I_BIG_MPZ (q),
+                               SCM_I_BIG_MPZ (q), 1);
+               }
+             else
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), -yy);
+                 scm_remember_upto_here_1 (x);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+                 if (rr < yy / 2)
+                   mpz_add_ui (SCM_I_BIG_MPZ (q),
+                               SCM_I_BIG_MPZ (q), 1);
+               }
+             return scm_i_normbig (q);
+           }
+       }
+      else if (SCM_BIGP (y))
+       return scm_i_bigint_centered_quotient (x, y);
+      else if (SCM_REALP (y))
+       return scm_i_inexact_centered_quotient
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_quotient, x, y, SCM_ARG2,
+                                   s_scm_centered_quotient);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_centered_quotient
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_quotient, x, y, SCM_ARG2,
+                                   s_scm_centered_quotient);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_centered_quotient
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_quotient, x, y, SCM_ARG2,
+                                   s_scm_centered_quotient);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_centered_quotient, x, y, SCM_ARG1,
+                               s_scm_centered_quotient);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_centered_quotient (double x, double y)
+{
+  if (SCM_LIKELY (y > 0))
+    return scm_from_double (floor (x/y + 0.5));
+  else if (SCM_LIKELY (y < 0))
+    return scm_from_double (ceil (x/y - 0.5));
+  else if (y == 0)
+    scm_num_overflow (s_scm_centered_quotient);  /* or return a NaN? */
+  else
+    return scm_nan ();
+}
+
+/* Assumes that both x and y are bigints, though
+   x might be able to fit into a fixnum. */
+static SCM
+scm_i_bigint_centered_quotient (SCM x, SCM y)
+{
+  SCM q, r, min_r;
+
+  /* Note that x might be small enough to fit into a
+     fixnum, so we must not let it escape into the wild */
+  q = scm_i_mkbig ();
+  r = scm_i_mkbig ();
+
+  /* min_r will eventually become -abs(y)/2 */
+  min_r = scm_i_mkbig ();
+  mpz_tdiv_q_2exp (SCM_I_BIG_MPZ (min_r),
+                  SCM_I_BIG_MPZ (y), 1);
+
+  /* Arrange for rr to initially be non-positive,
+     because that simplifies the test to see
+     if it is within the needed bounds. */
+  if (mpz_sgn (SCM_I_BIG_MPZ (y)) > 0)
+    {
+      mpz_cdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+      scm_remember_upto_here_2 (x, y);
+      mpz_neg (SCM_I_BIG_MPZ (min_r), SCM_I_BIG_MPZ (min_r));
+      if (mpz_cmp (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (min_r)) < 0)
+       mpz_sub_ui (SCM_I_BIG_MPZ (q),
+                   SCM_I_BIG_MPZ (q), 1);
+    }
+  else
+    {
+      mpz_fdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+      scm_remember_upto_here_2 (x, y);
+      if (mpz_cmp (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (min_r)) < 0)
+       mpz_add_ui (SCM_I_BIG_MPZ (q),
+                   SCM_I_BIG_MPZ (q), 1);
+    }
+  scm_remember_upto_here_2 (r, min_r);
+  return scm_i_normbig (q);
+}
+
+static SCM
+scm_i_exact_rational_centered_quotient (SCM x, SCM y)
+{
+  return scm_centered_quotient
+    (scm_product (scm_numerator (x), scm_denominator (y)),
+     scm_product (scm_numerator (y), scm_denominator (x)));
+}
+
+static SCM scm_i_inexact_centered_remainder (double x, double y);
+static SCM scm_i_bigint_centered_remainder (SCM x, SCM y);
+static SCM scm_i_exact_rational_centered_remainder (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_centered_remainder, "centered-remainder", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the real number @var{r} such that\n"
+                      "@math{-abs(@var{y}/2) <= @var{r} < abs(@var{y}/2)}\n"
+                      "and @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "for some integer @var{q}.\n"
+                      "@lisp\n"
+                      "(centered-remainder 123 10) @result{} 3\n"
+                      "(centered-remainder 123 -10) @result{} 3\n"
+                      "(centered-remainder -123 10) @result{} -3\n"
+                      "(centered-remainder -123 -10) @result{} -3\n"
+                      "(centered-remainder -123.2 -63.5) @result{} 3.8\n"
+                      "(centered-remainder 16/3 -10/7) @result{} -8/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_centered_remainder
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_centered_remainder);
+         else
+           {
+             scm_t_inum rr = xx % yy;
+             if (SCM_LIKELY (xx > 0))
+               {
+                 if (SCM_LIKELY (yy > 0))
+                   {
+                     if (rr >= (yy + 1) / 2)
+                       rr -= yy;
+                   }
+                 else
+                   {
+                     if (rr >= (1 - yy) / 2)
+                       rr += yy;
+                   }
+               }
+             else
+               {
+                 if (SCM_LIKELY (yy > 0))
+                   {
+                     if (rr < -yy / 2)
+                       rr += yy;
+                   }
+                 else
+                   {
+                     if (rr < yy / 2)
+                       rr -= yy;
+                   }
+               }
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         /* Pass a denormalized bignum version of x (even though it
+            can fit in a fixnum) to scm_i_bigint_centered_remainder */
+         return scm_i_bigint_centered_remainder (scm_i_long2big (xx), y);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_centered_remainder (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_remainder, x, y, SCM_ARG2,
+                                   s_scm_centered_remainder);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_centered_remainder);
+         else
+           {
+             scm_t_inum rr;
+             /* Arrange for rr to initially be non-positive,
+                because that simplifies the test to see
+                if it is within the needed bounds. */
+             if (yy > 0)
+               {
+                 rr = - mpz_cdiv_ui (SCM_I_BIG_MPZ (x), yy);
+                 scm_remember_upto_here_1 (x);
+                 if (rr < -yy / 2)
+                   rr += yy;
+               }
+             else
+               {
+                 rr = - mpz_cdiv_ui (SCM_I_BIG_MPZ (x), -yy);
+                 scm_remember_upto_here_1 (x);
+                 if (rr < yy / 2)
+                   rr -= yy;
+               }
+             return SCM_I_MAKINUM (rr);
+           }
+       }
+      else if (SCM_BIGP (y))
+       return scm_i_bigint_centered_remainder (x, y);
+      else if (SCM_REALP (y))
+       return scm_i_inexact_centered_remainder
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_remainder, x, y, SCM_ARG2,
+                                   s_scm_centered_remainder);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_centered_remainder
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_remainder, x, y, SCM_ARG2,
+                                   s_scm_centered_remainder);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_centered_remainder
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_centered_remainder, x, y, SCM_ARG2,
+                                   s_scm_centered_remainder);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_centered_remainder, x, y, SCM_ARG1,
+                               s_scm_centered_remainder);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_centered_remainder (double x, double y)
+{
+  double q;
+
+  /* Although it would be more efficient to use fmod here, we can't
+     because it would in some cases produce results inconsistent with
+     scm_i_inexact_centered_quotient, such that x != r + q * y (not even
+     close).  In particular, when x-y/2 is very close to a multiple of
+     y, then r might be either -abs(y/2) or abs(y/2)-epsilon, but those
+     two cases must correspond to different choices of q.  If quotient
+     chooses one and remainder chooses the other, it would be bad. */
+  if (SCM_LIKELY (y > 0))
+    q = floor (x/y + 0.5);
+  else if (SCM_LIKELY (y < 0))
+    q = ceil (x/y - 0.5);
+  else if (y == 0)
+    scm_num_overflow (s_scm_centered_remainder);  /* or return a NaN? */
+  else
+    return scm_nan ();
+  return scm_from_double (x - q * y);
+}
+
+/* Assumes that both x and y are bigints, though
+   x might be able to fit into a fixnum. */
+static SCM
+scm_i_bigint_centered_remainder (SCM x, SCM y)
+{
+  SCM r, min_r;
+
+  /* Note that x might be small enough to fit into a
+     fixnum, so we must not let it escape into the wild */
+  r = scm_i_mkbig ();
+
+  /* min_r will eventually become -abs(y)/2 */
+  min_r = scm_i_mkbig ();
+  mpz_tdiv_q_2exp (SCM_I_BIG_MPZ (min_r),
+                  SCM_I_BIG_MPZ (y), 1);
+
+  /* Arrange for rr to initially be non-positive,
+     because that simplifies the test to see
+     if it is within the needed bounds. */
+  if (mpz_sgn (SCM_I_BIG_MPZ (y)) > 0)
+    {
+      mpz_cdiv_r (SCM_I_BIG_MPZ (r),
+                 SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+      mpz_neg (SCM_I_BIG_MPZ (min_r), SCM_I_BIG_MPZ (min_r));
+      if (mpz_cmp (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (min_r)) < 0)
+       mpz_add (SCM_I_BIG_MPZ (r),
+                SCM_I_BIG_MPZ (r),
+                SCM_I_BIG_MPZ (y));
+    }
+  else
+    {
+      mpz_fdiv_r (SCM_I_BIG_MPZ (r),
+                 SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+      if (mpz_cmp (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (min_r)) < 0)
+       mpz_sub (SCM_I_BIG_MPZ (r),
+                SCM_I_BIG_MPZ (r),
+                SCM_I_BIG_MPZ (y));
+    }
+  scm_remember_upto_here_2 (x, y);
+  return scm_i_normbig (r);
+}
+
+static SCM
+scm_i_exact_rational_centered_remainder (SCM x, SCM y)
+{
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+  SCM r1 = scm_centered_remainder (scm_product (scm_numerator (x), yd),
+                                  scm_product (scm_numerator (y), xd));
+  return scm_divide (r1, scm_product (xd, yd));
+}
+
+
+static void scm_i_inexact_centered_divide (double x, double y,
+                                          SCM *qp, SCM *rp);
+static void scm_i_bigint_centered_divide (SCM x, SCM y, SCM *qp, SCM *rp);
+static void scm_i_exact_rational_centered_divide (SCM x, SCM y,
+                                                 SCM *qp, SCM *rp);
+
+SCM_PRIMITIVE_GENERIC (scm_i_centered_divide, "centered/", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the integer @var{q} and the real number @var{r}\n"
+                      "such that @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "and @math{-abs(@var{y}/2) <= @var{r} < abs(@var{y}/2)}.\n"
+                      "@lisp\n"
+                      "(centered/ 123 10) @result{} 12 and 3\n"
+                      "(centered/ 123 -10) @result{} -12 and 3\n"
+                      "(centered/ -123 10) @result{} -12 and -3\n"
+                      "(centered/ -123 -10) @result{} 12 and -3\n"
+                      "(centered/ -123.2 -63.5) @result{} 2.0 and 3.8\n"
+                      "(centered/ 16/3 -10/7) @result{} -4 and -8/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_i_centered_divide
+{
+  SCM q, r;
+
+  scm_centered_divide(x, y, &q, &r);
+  return scm_values (scm_list_2 (q, r));
+}
+#undef FUNC_NAME
+
+#define s_scm_centered_divide s_scm_i_centered_divide
+#define g_scm_centered_divide g_scm_i_centered_divide
+
+void
+scm_centered_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_centered_divide);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             if (SCM_LIKELY (xx > 0))
+               {
+                 if (SCM_LIKELY (yy > 0))
+                   {
+                     if (rr >= (yy + 1) / 2)
+                       { qq++; rr -= yy; }
+                   }
+                 else
+                   {
+                     if (rr >= (1 - yy) / 2)
+                       { qq--; rr += yy; }
+                   }
+               }
+             else
+               {
+                 if (SCM_LIKELY (yy > 0))
+                   {
+                     if (rr < -yy / 2)
+                       { qq--; rr += yy; }
+                   }
+                 else
+                   {
+                     if (rr < yy / 2)
+                       { qq++; rr -= yy; }
+                   }
+               }
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               *qp = SCM_I_MAKINUM (qq);
+             else
+               *qp = scm_i_inum2big (qq);
+             *rp = SCM_I_MAKINUM (rr);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       {
+         /* Pass a denormalized bignum version of x (even though it
+            can fit in a fixnum) to scm_i_bigint_centered_divide */
+         return scm_i_bigint_centered_divide (scm_i_long2big (xx), y, qp, rp);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_centered_divide (xx, SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_centered_divide, x, y, SCM_ARG2,
+          s_scm_centered_divide, qp, rp);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_centered_divide);
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             scm_t_inum rr;
+             /* Arrange for rr to initially be non-positive,
+                because that simplifies the test to see
+                if it is within the needed bounds. */
+             if (yy > 0)
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), yy);
+                 scm_remember_upto_here_1 (x);
+                 if (rr < -yy / 2)
+                   {
+                     mpz_sub_ui (SCM_I_BIG_MPZ (q),
+                                 SCM_I_BIG_MPZ (q), 1);
+                     rr += yy;
+                   }
+               }
+             else
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), -yy);
+                 scm_remember_upto_here_1 (x);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+                 if (rr < yy / 2)
+                   {
+                     mpz_add_ui (SCM_I_BIG_MPZ (q),
+                                 SCM_I_BIG_MPZ (q), 1);
+                     rr -= yy;
+                   }
+               }
+             *qp = scm_i_normbig (q);
+             *rp = SCM_I_MAKINUM (rr);
+           }
+         return;
+       }
+      else if (SCM_BIGP (y))
+       return scm_i_bigint_centered_divide (x, y, qp, rp);
+      else if (SCM_REALP (y))
+       return scm_i_inexact_centered_divide
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_centered_divide, x, y, SCM_ARG2,
+          s_scm_centered_divide, qp, rp);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_centered_divide
+         (SCM_REAL_VALUE (x), scm_to_double (y), qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_centered_divide, x, y, SCM_ARG2,
+          s_scm_centered_divide, qp, rp);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_centered_divide
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_centered_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2
+         (g_scm_centered_divide, x, y, SCM_ARG2,
+          s_scm_centered_divide, qp, rp);
+    }
+  else
+    return two_valued_wta_dispatch_2 (g_scm_centered_divide, x, y, SCM_ARG1,
+                                     s_scm_centered_divide, qp, rp);
+}
+
+static void
+scm_i_inexact_centered_divide (double x, double y, SCM *qp, SCM *rp)
+{
+  double q, r;
+
+  if (SCM_LIKELY (y > 0))
+    q = floor (x/y + 0.5);
+  else if (SCM_LIKELY (y < 0))
+    q = ceil (x/y - 0.5);
+  else if (y == 0)
+    scm_num_overflow (s_scm_centered_divide);  /* or return a NaN? */
+  else
+    q = guile_NaN;
+  r = x - q * y;
+  *qp = scm_from_double (q);
+  *rp = scm_from_double (r);
+}
+
+/* Assumes that both x and y are bigints, though
+   x might be able to fit into a fixnum. */
+static void
+scm_i_bigint_centered_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM q, r, min_r;
+
+  /* Note that x might be small enough to fit into a
+     fixnum, so we must not let it escape into the wild */
+  q = scm_i_mkbig ();
+  r = scm_i_mkbig ();
+
+  /* min_r will eventually become -abs(y/2) */
+  min_r = scm_i_mkbig ();
+  mpz_tdiv_q_2exp (SCM_I_BIG_MPZ (min_r),
+                  SCM_I_BIG_MPZ (y), 1);
+
+  /* Arrange for rr to initially be non-positive,
+     because that simplifies the test to see
+     if it is within the needed bounds. */
+  if (mpz_sgn (SCM_I_BIG_MPZ (y)) > 0)
+    {
+      mpz_cdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+      mpz_neg (SCM_I_BIG_MPZ (min_r), SCM_I_BIG_MPZ (min_r));
+      if (mpz_cmp (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (min_r)) < 0)
+       {
+         mpz_sub_ui (SCM_I_BIG_MPZ (q),
+                     SCM_I_BIG_MPZ (q), 1);
+         mpz_add (SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (y));
+       }
+    }
+  else
+    {
+      mpz_fdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+      if (mpz_cmp (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (min_r)) < 0)
+       {
+         mpz_add_ui (SCM_I_BIG_MPZ (q),
+                     SCM_I_BIG_MPZ (q), 1);
+         mpz_sub (SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (r),
+                  SCM_I_BIG_MPZ (y));
+       }
+    }
+  scm_remember_upto_here_2 (x, y);
+  *qp = scm_i_normbig (q);
+  *rp = scm_i_normbig (r);
+}
+
+static void
+scm_i_exact_rational_centered_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM r1;
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+
+  scm_centered_divide (scm_product (scm_numerator (x), yd),
+                      scm_product (scm_numerator (y), xd),
+                      qp, &r1);
+  *rp = scm_divide (r1, scm_product (xd, yd));
+}
+
+static SCM scm_i_inexact_round_quotient (double x, double y);
+static SCM scm_i_bigint_round_quotient (SCM x, SCM y);
+static SCM scm_i_exact_rational_round_quotient (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_round_quotient, "round-quotient", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return @math{@var{x} / @var{y}} to the nearest integer,\n"
+                      "with ties going to the nearest even integer.\n"
+                      "@lisp\n"
+                      "(round-quotient 123 10) @result{} 12\n"
+                      "(round-quotient 123 -10) @result{} -12\n"
+                      "(round-quotient -123 10) @result{} -12\n"
+                      "(round-quotient -123 -10) @result{} 12\n"
+                      "(round-quotient 125 10) @result{} 12\n"
+                      "(round-quotient 127 10) @result{} 13\n"
+                      "(round-quotient 135 10) @result{} 14\n"
+                      "(round-quotient -123.2 -63.5) @result{} 2.0\n"
+                      "(round-quotient 16/3 -10/7) @result{} -4\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_round_quotient
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
+    {
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_round_quotient);
+         else
+           {
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             scm_t_inum ay = yy;
+             scm_t_inum r2 = 2 * rr;
+
+             if (SCM_LIKELY (yy < 0))
+               {
+                 ay = -ay;
+                 r2 = -r2;
+               }
+
+             if (qq & 1L)
+               {
+                 if (r2 >= ay)
+                   qq++;
+                 else if (r2 <= -ay)
+                   qq--;
+               }
+             else
+               {
+                 if (r2 > ay)
+                   qq++;
+                 else if (r2 < -ay)
+                   qq--;
+               }
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               return SCM_I_MAKINUM (qq);
+             else
+               return scm_i_inum2big (qq);
+           }
+       }
+      else if (SCM_BIGP (y))
+       {
+         /* Pass a denormalized bignum version of x (even though it
+            can fit in a fixnum) to scm_i_bigint_round_quotient */
+         return scm_i_bigint_round_quotient (scm_i_long2big (xx), y);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_round_quotient (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_round_quotient, x, y, SCM_ARG2,
+                                   s_scm_round_quotient);
+    }
+  else if (SCM_BIGP (x))
+    {
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
+       {
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_round_quotient);
+         else if (SCM_UNLIKELY (yy == 1))
+           return x;
+         else
+           {
+             SCM q = scm_i_mkbig ();
+             scm_t_inum rr;
+             int needs_adjustment;
+
+             if (yy > 0)
+               {
+                 rr = mpz_fdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                     SCM_I_BIG_MPZ (x), yy);
+                 if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+                   needs_adjustment = (2*rr >= yy);
+                 else
+                   needs_adjustment = (2*rr > yy);
+               }
+             else
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+                 if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+                   needs_adjustment = (2*rr <= yy);
+                 else
+                   needs_adjustment = (2*rr < yy);
+               }
+             scm_remember_upto_here_1 (x);
+             if (needs_adjustment)
+               mpz_add_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q), 1);
+             return scm_i_normbig (q);
+           }
+       }
+      else if (SCM_BIGP (y))
+       return scm_i_bigint_round_quotient (x, y);
+      else if (SCM_REALP (y))
+       return scm_i_inexact_round_quotient
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_round_quotient, x, y, SCM_ARG2,
+                                   s_scm_round_quotient);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_round_quotient
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_round_quotient, x, y, SCM_ARG2,
+                                   s_scm_round_quotient);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_round_quotient
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_quotient (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_round_quotient, x, y, SCM_ARG2,
+                                   s_scm_round_quotient);
+    }
+  else
+    return scm_wta_dispatch_2 (g_scm_round_quotient, x, y, SCM_ARG1,
+                               s_scm_round_quotient);
+}
+#undef FUNC_NAME
+
+static SCM
+scm_i_inexact_round_quotient (double x, double y)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_round_quotient);  /* or return a NaN? */
+  else
+    return scm_from_double (scm_c_round (x / y));
+}
+
+/* Assumes that both x and y are bigints, though
+   x might be able to fit into a fixnum. */
+static SCM
+scm_i_bigint_round_quotient (SCM x, SCM y)
+{
+  SCM q, r, r2;
+  int cmp, needs_adjustment;
+
+  /* Note that x might be small enough to fit into a
+     fixnum, so we must not let it escape into the wild */
+  q = scm_i_mkbig ();
+  r = scm_i_mkbig ();
+  r2 = scm_i_mkbig ();
+
+  mpz_fdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+              SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+  mpz_mul_2exp (SCM_I_BIG_MPZ (r2), SCM_I_BIG_MPZ (r), 1);  /* r2 = 2*r */
+  scm_remember_upto_here_2 (x, r);
+
+  cmp = mpz_cmpabs (SCM_I_BIG_MPZ (r2), SCM_I_BIG_MPZ (y));
+  if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+    needs_adjustment = (cmp >= 0);
+  else
+    needs_adjustment = (cmp > 0);
+  scm_remember_upto_here_2 (r2, y);
+
+  if (needs_adjustment)
+    mpz_add_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q), 1);
+
+  return scm_i_normbig (q);
 }
 
-SCM_GPROC (s_remainder, "remainder", 2, 0, 0, scm_remainder, g_remainder);
-/* "Return the remainder of the numbers @var{x} and @var{y}.\n"
- * "@lisp\n"
- * "(remainder 13 4) @result{} 1\n"
- * "(remainder -13 4) @result{} -1\n"
- * "@end lisp"
- */
-SCM
-scm_remainder (SCM x, SCM y)
+static SCM
+scm_i_exact_rational_round_quotient (SCM x, SCM y)
+{
+  return scm_round_quotient
+    (scm_product (scm_numerator (x), scm_denominator (y)),
+     scm_product (scm_numerator (y), scm_denominator (x)));
+}
+
+static SCM scm_i_inexact_round_remainder (double x, double y);
+static SCM scm_i_bigint_round_remainder (SCM x, SCM y);
+static SCM scm_i_exact_rational_round_remainder (SCM x, SCM y);
+
+SCM_PRIMITIVE_GENERIC (scm_round_remainder, "round-remainder", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the real number @var{r} such that\n"
+                      "@math{@var{x} = @var{q}*@var{y} + @var{r}}, where\n"
+                      "@var{q} is @math{@var{x} / @var{y}} rounded to the\n"
+                      "nearest integer, with ties going to the nearest\n"
+                      "even integer.\n"
+                      "@lisp\n"
+                      "(round-remainder 123 10) @result{} 3\n"
+                      "(round-remainder 123 -10) @result{} 3\n"
+                      "(round-remainder -123 10) @result{} -3\n"
+                      "(round-remainder -123 -10) @result{} -3\n"
+                      "(round-remainder 125 10) @result{} 5\n"
+                      "(round-remainder 127 10) @result{} -3\n"
+                      "(round-remainder 135 10) @result{} -5\n"
+                      "(round-remainder -123.2 -63.5) @result{} 3.8\n"
+                      "(round-remainder 16/3 -10/7) @result{} -8/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_round_remainder
 {
-  if (SCM_I_INUMP (x))
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
-      if (SCM_I_INUMP (y))
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long yy = SCM_I_INUM (y);
-         if (yy == 0)
-           scm_num_overflow (s_remainder);
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_round_remainder);
          else
            {
-             long z = SCM_I_INUM (x) % yy;
-             return SCM_I_MAKINUM (z);
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             scm_t_inum ay = yy;
+             scm_t_inum r2 = 2 * rr;
+
+             if (SCM_LIKELY (yy < 0))
+               {
+                 ay = -ay;
+                 r2 = -r2;
+               }
+
+             if (qq & 1L)
+               {
+                 if (r2 >= ay)
+                   rr -= yy;
+                 else if (r2 <= -ay)
+                   rr += yy;
+               }
+             else
+               {
+                 if (r2 > ay)
+                   rr -= yy;
+                 else if (r2 < -ay)
+                   rr += yy;
+               }
+             return SCM_I_MAKINUM (rr);
            }
        }
       else if (SCM_BIGP (y))
        {
-         if ((SCM_I_INUM (x) == SCM_MOST_NEGATIVE_FIXNUM)
-             && (mpz_cmp_ui (SCM_I_BIG_MPZ (y),
-                              - SCM_MOST_NEGATIVE_FIXNUM) == 0))
-            {
-              /* Special case:  x == fixnum-min && y == abs (fixnum-min) */
-             scm_remember_upto_here_1 (y);
-              return SCM_I_MAKINUM (0);
-            }
-         else
-           return x;
+         /* Pass a denormalized bignum version of x (even though it
+            can fit in a fixnum) to scm_i_bigint_round_remainder */
+         return scm_i_bigint_round_remainder
+           (scm_i_long2big (xx), y);
        }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_round_remainder (xx, SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_remainder (x, y);
       else
-       SCM_WTA_DISPATCH_2 (g_remainder, x, y, SCM_ARG2, s_remainder);
+       return scm_wta_dispatch_2 (g_scm_round_remainder, x, y, SCM_ARG2,
+                                   s_scm_round_remainder);
     }
   else if (SCM_BIGP (x))
     {
-      if (SCM_I_INUMP (y))
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long yy = SCM_I_INUM (y);
-         if (yy == 0)
-           scm_num_overflow (s_remainder);
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_round_remainder);
          else
            {
-             SCM result = scm_i_mkbig ();
-             if (yy < 0)
-               yy = - yy;
-             mpz_tdiv_r_ui (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ(x), yy);
-             scm_remember_upto_here_1 (x);
-             return scm_i_normbig (result);
+             SCM q = scm_i_mkbig ();
+             scm_t_inum rr;
+             int needs_adjustment;
+
+             if (yy > 0)
+               {
+                 rr = mpz_fdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                     SCM_I_BIG_MPZ (x), yy);
+                 if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+                   needs_adjustment = (2*rr >= yy);
+                 else
+                   needs_adjustment = (2*rr > yy);
+               }
+             else
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), -yy);
+                 if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+                   needs_adjustment = (2*rr <= yy);
+                 else
+                   needs_adjustment = (2*rr < yy);
+               }
+             scm_remember_upto_here_2 (x, q);
+             if (needs_adjustment)
+               rr -= yy;
+             return SCM_I_MAKINUM (rr);
            }
        }
       else if (SCM_BIGP (y))
-       {
-         SCM result = scm_i_mkbig ();
-         mpz_tdiv_r (SCM_I_BIG_MPZ (result),
-                     SCM_I_BIG_MPZ (x),
-                     SCM_I_BIG_MPZ (y));
-         scm_remember_upto_here_2 (x, y);
-         return scm_i_normbig (result);
-       }
+       return scm_i_bigint_round_remainder (x, y);
+      else if (SCM_REALP (y))
+       return scm_i_inexact_round_remainder
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y));
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_remainder (x, y);
       else
-       SCM_WTA_DISPATCH_2 (g_remainder, x, y, SCM_ARG2, s_remainder);
+       return scm_wta_dispatch_2 (g_scm_round_remainder, x, y, SCM_ARG2,
+                                   s_scm_round_remainder);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_round_remainder
+         (SCM_REAL_VALUE (x), scm_to_double (y));
+      else
+       return scm_wta_dispatch_2 (g_scm_round_remainder, x, y, SCM_ARG2,
+                                   s_scm_round_remainder);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_round_remainder
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y));
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_remainder (x, y);
+      else
+       return scm_wta_dispatch_2 (g_scm_round_remainder, x, y, SCM_ARG2,
+                                   s_scm_round_remainder);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_remainder, x, y, SCM_ARG1, s_remainder);
+    return scm_wta_dispatch_2 (g_scm_round_remainder, x, y, SCM_ARG1,
+                               s_scm_round_remainder);
 }
+#undef FUNC_NAME
 
+static SCM
+scm_i_inexact_round_remainder (double x, double y)
+{
+  /* Although it would be more efficient to use fmod here, we can't
+     because it would in some cases produce results inconsistent with
+     scm_i_inexact_round_quotient, such that x != r + q * y (not even
+     close).  In particular, when x-y/2 is very close to a multiple of
+     y, then r might be either -abs(y/2) or abs(y/2), but those two
+     cases must correspond to different choices of q.  If quotient
+     chooses one and remainder chooses the other, it would be bad. */
+
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_round_remainder);  /* or return a NaN? */
+  else
+    {
+      double q = scm_c_round (x / y);
+      return scm_from_double (x - q * y);
+    }
+}
 
-SCM_GPROC (s_modulo, "modulo", 2, 0, 0, scm_modulo, g_modulo);
-/* "Return the modulo of the numbers @var{x} and @var{y}.\n"
- * "@lisp\n"
- * "(modulo 13 4) @result{} 1\n"
- * "(modulo -13 4) @result{} 3\n"
- * "@end lisp"
- */
-SCM
-scm_modulo (SCM x, SCM y)
+/* Assumes that both x and y are bigints, though
+   x might be able to fit into a fixnum. */
+static SCM
+scm_i_bigint_round_remainder (SCM x, SCM y)
 {
-  if (SCM_I_INUMP (x))
+  SCM q, r, r2;
+  int cmp, needs_adjustment;
+
+  /* Note that x might be small enough to fit into a
+     fixnum, so we must not let it escape into the wild */
+  q = scm_i_mkbig ();
+  r = scm_i_mkbig ();
+  r2 = scm_i_mkbig ();
+
+  mpz_fdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+              SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+  scm_remember_upto_here_1 (x);
+  mpz_mul_2exp (SCM_I_BIG_MPZ (r2), SCM_I_BIG_MPZ (r), 1);  /* r2 = 2*r */
+
+  cmp = mpz_cmpabs (SCM_I_BIG_MPZ (r2), SCM_I_BIG_MPZ (y));
+  if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+    needs_adjustment = (cmp >= 0);
+  else
+    needs_adjustment = (cmp > 0);
+  scm_remember_upto_here_2 (q, r2);
+
+  if (needs_adjustment)
+    mpz_sub (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y));
+
+  scm_remember_upto_here_1 (y);
+  return scm_i_normbig (r);
+}
+
+static SCM
+scm_i_exact_rational_round_remainder (SCM x, SCM y)
+{
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+  SCM r1 = scm_round_remainder (scm_product (scm_numerator (x), yd),
+                               scm_product (scm_numerator (y), xd));
+  return scm_divide (r1, scm_product (xd, yd));
+}
+
+
+static void scm_i_inexact_round_divide (double x, double y, SCM *qp, SCM *rp);
+static void scm_i_bigint_round_divide (SCM x, SCM y, SCM *qp, SCM *rp);
+static void scm_i_exact_rational_round_divide (SCM x, SCM y, SCM *qp, SCM *rp);
+
+SCM_PRIMITIVE_GENERIC (scm_i_round_divide, "round/", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return the integer @var{q} and the real number @var{r}\n"
+                      "such that @math{@var{x} = @var{q}*@var{y} + @var{r}}\n"
+                      "and @var{q} is @math{@var{x} / @var{y}} rounded to the\n"
+                      "nearest integer, with ties going to the nearest even integer.\n"
+                      "@lisp\n"
+                      "(round/ 123 10) @result{} 12 and 3\n"
+                      "(round/ 123 -10) @result{} -12 and 3\n"
+                      "(round/ -123 10) @result{} -12 and -3\n"
+                      "(round/ -123 -10) @result{} 12 and -3\n"
+                      "(round/ 125 10) @result{} 12 and 5\n"
+                      "(round/ 127 10) @result{} 13 and -3\n"
+                      "(round/ 135 10) @result{} 14 and -5\n"
+                      "(round/ -123.2 -63.5) @result{} 2.0 and 3.8\n"
+                      "(round/ 16/3 -10/7) @result{} -4 and -8/21\n"
+                      "@end lisp")
+#define FUNC_NAME s_scm_i_round_divide
+{
+  SCM q, r;
+
+  scm_round_divide(x, y, &q, &r);
+  return scm_values (scm_list_2 (q, r));
+}
+#undef FUNC_NAME
+
+#define s_scm_round_divide s_scm_i_round_divide
+#define g_scm_round_divide g_scm_i_round_divide
+
+void
+scm_round_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
-      long xx = SCM_I_INUM (x);
-      if (SCM_I_INUMP (y))
+      scm_t_inum xx = SCM_I_INUM (x);
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long yy = SCM_I_INUM (y);
-         if (yy == 0)
-           scm_num_overflow (s_modulo);
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_round_divide);
          else
            {
-             /* C99 specifies that "%" is the remainder corresponding to a
-                 quotient rounded towards zero, and that's also traditional
-                 for machine division, so z here should be well defined.  */
-             long z = xx % yy;
-             long result;
+             scm_t_inum qq = xx / yy;
+             scm_t_inum rr = xx % yy;
+             scm_t_inum ay = yy;
+             scm_t_inum r2 = 2 * rr;
 
-             if (yy < 0)
-               {
-                 if (z > 0)
-                   result = z + yy;
-                 else
-                   result = z;
-               }
-             else
+             if (SCM_LIKELY (yy < 0))
                {
-                 if (z < 0)
-                   result = z + yy;
-                 else
-                   result = z;
+                 ay = -ay;
+                 r2 = -r2;
                }
-             return SCM_I_MAKINUM (result);
-           }
-       }
-      else if (SCM_BIGP (y))
-       {
-         int sgn_y = mpz_sgn (SCM_I_BIG_MPZ (y));
-           {
-             mpz_t z_x;
-             SCM result;
 
-             if (sgn_y < 0)
+             if (qq & 1L)
                {
-                 SCM pos_y = scm_i_clonebig (y, 0);
-                 /* do this after the last scm_op */
-                 mpz_init_set_si (z_x, xx);
-                 result = pos_y; /* re-use this bignum */
-                 mpz_mod (SCM_I_BIG_MPZ (result),
-                          z_x,
-                          SCM_I_BIG_MPZ (pos_y));        
-                 scm_remember_upto_here_1 (pos_y);
+                 if (r2 >= ay)
+                   { qq++; rr -= yy; }
+                 else if (r2 <= -ay)
+                   { qq--; rr += yy; }
                }
              else
                {
-                 result = scm_i_mkbig ();
-                 /* do this after the last scm_op */
-                 mpz_init_set_si (z_x, xx);
-                 mpz_mod (SCM_I_BIG_MPZ (result),
-                          z_x,
-                          SCM_I_BIG_MPZ (y));        
-                 scm_remember_upto_here_1 (y);
+                 if (r2 > ay)
+                   { qq++; rr -= yy; }
+                 else if (r2 < -ay)
+                   { qq--; rr += yy; }
                }
-        
-             if ((sgn_y < 0) && mpz_sgn (SCM_I_BIG_MPZ (result)) != 0)
-               mpz_add (SCM_I_BIG_MPZ (result),
-                        SCM_I_BIG_MPZ (y),
-                        SCM_I_BIG_MPZ (result));
-             scm_remember_upto_here_1 (y);
-             /* and do this before the next one */
-             mpz_clear (z_x);
-             return scm_i_normbig (result);
+             if (SCM_LIKELY (SCM_FIXABLE (qq)))
+               *qp = SCM_I_MAKINUM (qq);
+             else
+               *qp = scm_i_inum2big (qq);
+             *rp = SCM_I_MAKINUM (rr);
            }
+         return;
        }
+      else if (SCM_BIGP (y))
+       {
+         /* Pass a denormalized bignum version of x (even though it
+            can fit in a fixnum) to scm_i_bigint_round_divide */
+         return scm_i_bigint_round_divide
+           (scm_i_long2big (SCM_I_INUM (x)), y, qp, rp);
+       }
+      else if (SCM_REALP (y))
+       return scm_i_inexact_round_divide (xx, SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_divide (x, y, qp, rp);
       else
-       SCM_WTA_DISPATCH_2 (g_modulo, x, y, SCM_ARG2, s_modulo);
+       return two_valued_wta_dispatch_2 (g_scm_round_divide, x, y, SCM_ARG2,
+                                         s_scm_round_divide, qp, rp);
     }
   else if (SCM_BIGP (x))
     {
-      if (SCM_I_INUMP (y))
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long yy = SCM_I_INUM (y);
-         if (yy == 0)
-           scm_num_overflow (s_modulo);
+         scm_t_inum yy = SCM_I_INUM (y);
+         if (SCM_UNLIKELY (yy == 0))
+           scm_num_overflow (s_scm_round_divide);
          else
            {
-             SCM result = scm_i_mkbig ();
-             mpz_mod_ui (SCM_I_BIG_MPZ (result),
-                         SCM_I_BIG_MPZ (x),
-                         (yy < 0) ? - yy : yy);
+             SCM q = scm_i_mkbig ();
+             scm_t_inum rr;
+             int needs_adjustment;
+
+             if (yy > 0)
+               {
+                 rr = mpz_fdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                     SCM_I_BIG_MPZ (x), yy);
+                 if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+                   needs_adjustment = (2*rr >= yy);
+                 else
+                   needs_adjustment = (2*rr > yy);
+               }
+             else
+               {
+                 rr = - mpz_cdiv_q_ui (SCM_I_BIG_MPZ (q),
+                                       SCM_I_BIG_MPZ (x), -yy);
+                 mpz_neg (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q));
+                 if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+                   needs_adjustment = (2*rr <= yy);
+                 else
+                   needs_adjustment = (2*rr < yy);
+               }
              scm_remember_upto_here_1 (x);
-             if ((yy < 0) && (mpz_sgn (SCM_I_BIG_MPZ (result)) != 0))
-               mpz_sub_ui (SCM_I_BIG_MPZ (result),
-                           SCM_I_BIG_MPZ (result),
-                           - yy);
-             return scm_i_normbig (result);
+             if (needs_adjustment)
+               {
+                 mpz_add_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q), 1);
+                 rr -= yy;
+               }
+             *qp = scm_i_normbig (q);
+             *rp = SCM_I_MAKINUM (rr);
            }
+         return;
        }
       else if (SCM_BIGP (y))
-       {
-           {
-             SCM result = scm_i_mkbig ();
-             int y_sgn = mpz_sgn (SCM_I_BIG_MPZ (y));
-             SCM pos_y = scm_i_clonebig (y, y_sgn >= 0);
-             mpz_mod (SCM_I_BIG_MPZ (result),
-                      SCM_I_BIG_MPZ (x),
-                      SCM_I_BIG_MPZ (pos_y));
-        
-             scm_remember_upto_here_1 (x);
-             if ((y_sgn < 0) && (mpz_sgn (SCM_I_BIG_MPZ (result)) != 0))
-               mpz_add (SCM_I_BIG_MPZ (result),
-                        SCM_I_BIG_MPZ (y),
-                        SCM_I_BIG_MPZ (result));
-             scm_remember_upto_here_2 (y, pos_y);
-             return scm_i_normbig (result);
-           }
-       }
+       return scm_i_bigint_round_divide (x, y, qp, rp);
+      else if (SCM_REALP (y))
+       return scm_i_inexact_round_divide
+         (scm_i_big2dbl (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_divide (x, y, qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_round_divide, x, y, SCM_ARG2,
+                                         s_scm_round_divide, qp, rp);
+    }
+  else if (SCM_REALP (x))
+    {
+      if (SCM_REALP (y) || SCM_I_INUMP (y) ||
+         SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_inexact_round_divide
+         (SCM_REAL_VALUE (x), scm_to_double (y), qp, rp);
+      else
+       return two_valued_wta_dispatch_2 (g_scm_round_divide, x, y, SCM_ARG2,
+                                         s_scm_round_divide, qp, rp);
+    }
+  else if (SCM_FRACTIONP (x))
+    {
+      if (SCM_REALP (y))
+       return scm_i_inexact_round_divide
+         (scm_i_fraction2double (x), SCM_REAL_VALUE (y), qp, rp);
+      else if (SCM_I_INUMP (y) || SCM_BIGP (y) || SCM_FRACTIONP (y))
+       return scm_i_exact_rational_round_divide (x, y, qp, rp);
       else
-       SCM_WTA_DISPATCH_2 (g_modulo, x, y, SCM_ARG2, s_modulo);
+       return two_valued_wta_dispatch_2 (g_scm_round_divide, x, y, SCM_ARG2,
+                                         s_scm_round_divide, qp, rp);
+    }
+  else
+    return two_valued_wta_dispatch_2 (g_scm_round_divide, x, y, SCM_ARG1,
+                                     s_scm_round_divide, qp, rp);
+}
+
+static void
+scm_i_inexact_round_divide (double x, double y, SCM *qp, SCM *rp)
+{
+  if (SCM_UNLIKELY (y == 0))
+    scm_num_overflow (s_scm_round_divide);  /* or return a NaN? */
+  else
+    {
+      double q = scm_c_round (x / y);
+      double r = x - q * y;
+      *qp = scm_from_double (q);
+      *rp = scm_from_double (r);
     }
+}
+
+/* Assumes that both x and y are bigints, though
+   x might be able to fit into a fixnum. */
+static void
+scm_i_bigint_round_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM q, r, r2;
+  int cmp, needs_adjustment;
+
+  /* Note that x might be small enough to fit into a
+     fixnum, so we must not let it escape into the wild */
+  q = scm_i_mkbig ();
+  r = scm_i_mkbig ();
+  r2 = scm_i_mkbig ();
+
+  mpz_fdiv_qr (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (r),
+              SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
+  scm_remember_upto_here_1 (x);
+  mpz_mul_2exp (SCM_I_BIG_MPZ (r2), SCM_I_BIG_MPZ (r), 1);  /* r2 = 2*r */
+
+  cmp = mpz_cmpabs (SCM_I_BIG_MPZ (r2), SCM_I_BIG_MPZ (y));
+  if (mpz_odd_p (SCM_I_BIG_MPZ (q)))
+    needs_adjustment = (cmp >= 0);
   else
-    SCM_WTA_DISPATCH_2 (g_modulo, x, y, SCM_ARG1, s_modulo);
+    needs_adjustment = (cmp > 0);
+
+  if (needs_adjustment)
+    {
+      mpz_add_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q), 1);
+      mpz_sub (SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (y));
+    }
+
+  scm_remember_upto_here_2 (r2, y);
+  *qp = scm_i_normbig (q);
+  *rp = scm_i_normbig (r);
+}
+
+static void
+scm_i_exact_rational_round_divide (SCM x, SCM y, SCM *qp, SCM *rp)
+{
+  SCM r1;
+  SCM xd = scm_denominator (x);
+  SCM yd = scm_denominator (y);
+
+  scm_round_divide (scm_product (scm_numerator (x), yd),
+                   scm_product (scm_numerator (y), xd),
+                   qp, &r1);
+  *rp = scm_divide (r1, scm_product (xd, yd));
 }
 
+
 SCM_PRIMITIVE_GENERIC (scm_i_gcd, "gcd", 0, 2, 1,
                        (SCM x, SCM y, SCM rest),
                        "Return the greatest common divisor of all parameter values.\n"
@@ -1008,56 +4082,62 @@ SCM_PRIMITIVE_GENERIC (scm_i_gcd, "gcd", 0, 2, 1,
 SCM
 scm_gcd (SCM x, SCM y)
 {
-  if (SCM_UNBNDP (y))
+  if (SCM_UNLIKELY (SCM_UNBNDP (y)))
     return SCM_UNBNDP (x) ? SCM_INUM0 : scm_abs (x);
   
-  if (SCM_I_INUMP (x))
+  if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
-      if (SCM_I_INUMP (y))
+      if (SCM_LIKELY (SCM_I_INUMP (y)))
         {
-          long xx = SCM_I_INUM (x);
-          long yy = SCM_I_INUM (y);
-          long u = xx < 0 ? -xx : xx;
-          long v = yy < 0 ? -yy : yy;
-          long result;
-          if (xx == 0)
+          scm_t_inum xx = SCM_I_INUM (x);
+          scm_t_inum yy = SCM_I_INUM (y);
+          scm_t_inum u = xx < 0 ? -xx : xx;
+          scm_t_inum v = yy < 0 ? -yy : yy;
+          scm_t_inum result;
+          if (SCM_UNLIKELY (xx == 0))
            result = v;
-         else if (yy == 0)
+         else if (SCM_UNLIKELY (yy == 0))
            result = u;
          else
            {
-             long k = 1;
-             long t;
+             int k = 0;
              /* Determine a common factor 2^k */
-             while (!(1 & (u | v)))
+             while (((u | v) & 1) == 0)
                {
-                 k <<= 1;
+                 k++;
                  u >>= 1;
                  v >>= 1;
                }
              /* Now, any factor 2^n can be eliminated */
-             if (u & 1)
-               t = -v;
+             if ((u & 1) == 0)
+               while ((u & 1) == 0)
+                 u >>= 1;
              else
+               while ((v & 1) == 0)
+                 v >>= 1;
+             /* Both u and v are now odd.  Subtract the smaller one
+                from the larger one to produce an even number, remove
+                more factors of two, and repeat. */
+             while (u != v)
                {
-                 t = u;
-               b3:
-                 t = SCM_SRS (t, 1);
+                 if (u > v)
+                   {
+                     u -= v;
+                     while ((u & 1) == 0)
+                       u >>= 1;
+                   }
+                 else
+                   {
+                     v -= u;
+                     while ((v & 1) == 0)
+                       v >>= 1;
+                   }
                }
-             if (!(1 & t))
-               goto b3;
-             if (t > 0)
-               u = t;
-             else
-               v = -t;
-             t = u - v;
-             if (t != 0)
-               goto b3;
-             result = u * k;
+             result = u << k;
            }
           return (SCM_POSFIXABLE (result)
                  ? SCM_I_MAKINUM (result)
-                 : scm_i_long2big (result));
+                 : scm_i_inum2big (result));
         }
       else if (SCM_BIGP (y))
         {
@@ -1065,14 +4145,14 @@ scm_gcd (SCM x, SCM y)
           goto big_inum;
         }
       else
-        SCM_WTA_DISPATCH_2 (g_gcd, x, y, SCM_ARG2, s_gcd);
+        return scm_wta_dispatch_2 (g_gcd, x, y, SCM_ARG2, s_gcd);
     }
   else if (SCM_BIGP (x))
     {
       if (SCM_I_INUMP (y))
         {
-          unsigned long result;
-          long yy;
+          scm_t_bits result;
+          scm_t_inum yy;
         big_inum:
           yy = SCM_I_INUM (y);
           if (yy == 0)
@@ -1083,7 +4163,7 @@ scm_gcd (SCM x, SCM y)
           scm_remember_upto_here_1 (x);
           return (SCM_POSFIXABLE (result) 
                  ? SCM_I_MAKINUM (result)
-                 : scm_from_ulong (result));
+                 : scm_from_unsigned_integer (result));
         }
       else if (SCM_BIGP (y))
         {
@@ -1095,10 +4175,10 @@ scm_gcd (SCM x, SCM y)
           return scm_i_normbig (result);
         }
       else
-        SCM_WTA_DISPATCH_2 (g_gcd, x, y, SCM_ARG2, s_gcd);
+        return scm_wta_dispatch_2 (g_gcd, x, y, SCM_ARG2, s_gcd);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_gcd, x, y, SCM_ARG1, s_gcd);
+    return scm_wta_dispatch_2 (g_gcd, x, y, SCM_ARG1, s_gcd);
 }
 
 SCM_PRIMITIVE_GENERIC (scm_i_lcm, "lcm", 0, 2, 1,
@@ -1129,10 +4209,11 @@ scm_lcm (SCM n1, SCM n2)
       n2 = SCM_I_MAKINUM (1L);
     }
 
-  SCM_GASSERT2 (SCM_I_INUMP (n1) || SCM_BIGP (n1),
-                g_lcm, n1, n2, SCM_ARG1, s_lcm);
-  SCM_GASSERT2 (SCM_I_INUMP (n2) || SCM_BIGP (n2),
-                g_lcm, n1, n2, SCM_ARGn, s_lcm);
+  if (SCM_UNLIKELY (!(SCM_I_INUMP (n1) || SCM_BIGP (n1))))
+    return scm_wta_dispatch_2 (g_lcm, n1, n2, SCM_ARG1, s_lcm);
+  
+  if (SCM_UNLIKELY (!(SCM_I_INUMP (n2) || SCM_BIGP (n2))))
+    return scm_wta_dispatch_2 (g_lcm, n1, n2, SCM_ARG2, s_lcm);
 
   if (SCM_I_INUMP (n1))
     {
@@ -1150,7 +4231,7 @@ scm_lcm (SCM n1, SCM n2)
         inumbig:
           {
             SCM result = scm_i_mkbig ();
-            long nn1 = SCM_I_INUM (n1);
+            scm_t_inum nn1 = SCM_I_INUM (n1);
             if (nn1 == 0) return SCM_INUM0;
             if (nn1 < 0) nn1 = - nn1;
             mpz_lcm_ui (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (n2), nn1);
@@ -1240,7 +4321,7 @@ SCM_DEFINE (scm_i_logand, "logand", 0, 2, 1,
 SCM scm_logand (SCM n1, SCM n2)
 #define FUNC_NAME s_scm_logand
 {
-  long int nn1;
+  scm_t_inum nn1;
 
   if (SCM_UNBNDP (n2))
     {
@@ -1259,13 +4340,13 @@ SCM scm_logand (SCM n1, SCM n2)
       nn1 = SCM_I_INUM (n1);
       if (SCM_I_INUMP (n2))
        {
-         long nn2 = SCM_I_INUM (n2);
+         scm_t_inum nn2 = SCM_I_INUM (n2);
          return SCM_I_MAKINUM (nn1 & nn2);
        }
       else if SCM_BIGP (n2)
        {
        intbig: 
-         if (n1 == 0)
+         if (nn1 == 0)
            return SCM_INUM0;
          {
            SCM result_z = scm_i_mkbig ();
@@ -1330,7 +4411,7 @@ SCM_DEFINE (scm_i_logior, "logior", 0, 2, 1,
 SCM scm_logior (SCM n1, SCM n2)
 #define FUNC_NAME s_scm_logior
 {
-  long int nn1;
+  scm_t_inum nn1;
 
   if (SCM_UNBNDP (n2))
     {
@@ -1420,7 +4501,7 @@ SCM_DEFINE (scm_i_logxor, "logxor", 0, 2, 1,
 SCM scm_logxor (SCM n1, SCM n2)
 #define FUNC_NAME s_scm_logxor
 {
-  long int nn1;
+  scm_t_inum nn1;
 
   if (SCM_UNBNDP (n2))
     {
@@ -1437,7 +4518,7 @@ SCM scm_logxor (SCM n1, SCM n2)
       nn1 = SCM_I_INUM (n1);
       if (SCM_I_INUMP (n2))
        {
-         long nn2 = SCM_I_INUM (n2);
+         scm_t_inum nn2 = SCM_I_INUM (n2);
          return SCM_I_MAKINUM (nn1 ^ nn2);
        }
       else if (SCM_BIGP (n2))
@@ -1495,14 +4576,14 @@ SCM_DEFINE (scm_logtest, "logtest", 2, 0, 0,
            "@end lisp")
 #define FUNC_NAME s_scm_logtest
 {
-  long int nj;
+  scm_t_inum nj;
 
   if (SCM_I_INUMP (j))
     {
       nj = SCM_I_INUM (j);
       if (SCM_I_INUMP (k))
        {
-         long nk = SCM_I_INUM (k);
+         scm_t_inum nk = SCM_I_INUM (k);
          return scm_from_bool (nj & nk);
        }
       else if (SCM_BIGP (k))
@@ -1744,8 +4825,9 @@ SCM_DEFINE (scm_integer_expt, "integer-expt", 2, 0, 0,
            "Return @var{n} raised to the power @var{k}.  @var{k} must be an\n"
            "exact integer, @var{n} can be any number.\n"
            "\n"
-           "Negative @var{k} is supported, and results in @math{1/n^abs(k)}\n"
-           "in the usual way.  @math{@var{n}^0} is 1, as usual, and that\n"
+           "Negative @var{k} is supported, and results in\n"
+           "@math{1/@var{n}^abs(@var{k})} in the usual way.\n"
+           "@math{@var{n}^0} is 1, as usual, and that\n"
            "includes @math{0^0} is 1.\n"
            "\n"
            "@lisp\n"
@@ -1756,18 +4838,54 @@ SCM_DEFINE (scm_integer_expt, "integer-expt", 2, 0, 0,
            "@end lisp")
 #define FUNC_NAME s_scm_integer_expt
 {
-  long i2 = 0;
+  scm_t_inum i2 = 0;
   SCM z_i2 = SCM_BOOL_F;
   int i2_is_big = 0;
   SCM acc = SCM_I_MAKINUM (1L);
 
-  SCM_VALIDATE_NUMBER (SCM_ARG1, n);
+  /* 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);
 
-  /* 0^0 == 1 according to R5RS */
-  if (scm_is_eq (n, SCM_INUM0) || scm_is_eq (n, acc))
-    return scm_is_false (scm_zero_p(k)) ? n : acc;
-  else if (scm_is_eq (n, SCM_I_MAKINUM (-1L)))
-    return scm_is_false (scm_even_p (k)) ? n : acc;
+  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_FRACTIONP (n))
+    {
+      /* Optimize the fraction case by (a/b)^k ==> (a^k)/(b^k), to avoid
+         needless reduction of intermediate products to lowest terms.
+         If a and b have no common factors, then a^k and b^k have no
+         common factors.  Use 'scm_i_make_ratio_already_reduced' to
+         construct the final result, so that no gcd computations are
+         needed to exponentiate a fraction.  */
+      if (scm_is_true (scm_positive_p (k)))
+       return scm_i_make_ratio_already_reduced
+         (scm_integer_expt (SCM_FRACTION_NUMERATOR (n), k),
+          scm_integer_expt (SCM_FRACTION_DENOMINATOR (n), k));
+      else
+       {
+         k = scm_difference (k, SCM_UNDEFINED);
+         return scm_i_make_ratio_already_reduced
+           (scm_integer_expt (SCM_FRACTION_DENOMINATOR (n), k),
+            scm_integer_expt (SCM_FRACTION_NUMERATOR (n), k));
+       }
+    }
 
   if (SCM_I_INUMP (k))
     i2 = SCM_I_INUM (k);
@@ -1825,102 +4943,180 @@ SCM_DEFINE (scm_integer_expt, "integer-expt", 2, 0, 0,
 }
 #undef FUNC_NAME
 
+/* Efficiently compute (N * 2^COUNT),
+   where N is an exact integer, and COUNT > 0. */
+static SCM
+left_shift_exact_integer (SCM n, long count)
+{
+  if (SCM_I_INUMP (n))
+    {
+      scm_t_inum nn = SCM_I_INUM (n);
+
+      /* Left shift of count >= SCM_I_FIXNUM_BIT-1 will always
+         overflow a non-zero fixnum.  For smaller shifts we check the
+         bits going into positions above SCM_I_FIXNUM_BIT-1.  If they're
+         all 0s for nn>=0, or all 1s for nn<0 then there's no overflow.
+         Those bits are "nn >> (SCM_I_FIXNUM_BIT-1 - count)".  */
+
+      if (nn == 0)
+        return n;
+      else if (count < SCM_I_FIXNUM_BIT-1 &&
+               ((scm_t_bits) (SCM_SRS (nn, (SCM_I_FIXNUM_BIT-1 - count)) + 1)
+                <= 1))
+        return SCM_I_MAKINUM (nn << count);
+      else
+        {
+          SCM result = scm_i_inum2big (nn);
+          mpz_mul_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (result),
+                        count);
+          return result;
+        }
+    }
+  else if (SCM_BIGP (n))
+    {
+      SCM result = scm_i_mkbig ();
+      mpz_mul_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (n), count);
+      scm_remember_upto_here_1 (n);
+      return result;
+    }
+  else
+    scm_syserror ("left_shift_exact_integer");
+}
+
+/* Efficiently compute floor (N / 2^COUNT),
+   where N is an exact integer and COUNT > 0. */
+static SCM
+floor_right_shift_exact_integer (SCM n, long count)
+{
+  if (SCM_I_INUMP (n))
+    {
+      scm_t_inum nn = SCM_I_INUM (n);
+
+      if (count >= SCM_I_FIXNUM_BIT)
+        return (nn >= 0 ? SCM_INUM0 : SCM_I_MAKINUM (-1));
+      else
+        return SCM_I_MAKINUM (SCM_SRS (nn, count));
+    }
+  else if (SCM_BIGP (n))
+    {
+      SCM result = scm_i_mkbig ();
+      mpz_fdiv_q_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (n),
+                       count);
+      scm_remember_upto_here_1 (n);
+      return scm_i_normbig (result);
+    }
+  else
+    scm_syserror ("floor_right_shift_exact_integer");
+}
+
+/* Efficiently compute round (N / 2^COUNT),
+   where N is an exact integer and COUNT > 0. */
+static SCM
+round_right_shift_exact_integer (SCM n, long count)
+{
+  if (SCM_I_INUMP (n))
+    {
+      if (count >= SCM_I_FIXNUM_BIT)
+        return SCM_INUM0;
+      else
+        {
+          scm_t_inum nn = SCM_I_INUM (n);
+          scm_t_inum qq = SCM_SRS (nn, count);
+
+          if (0 == (nn & (1L << (count-1))))
+            return SCM_I_MAKINUM (qq);                /* round down */
+          else if (nn & ((1L << (count-1)) - 1))
+            return SCM_I_MAKINUM (qq + 1);            /* round up */
+          else
+            return SCM_I_MAKINUM ((~1L) & (qq + 1));  /* round to even */
+        }
+    }
+  else if (SCM_BIGP (n))
+    {
+      SCM q = scm_i_mkbig ();
+
+      mpz_fdiv_q_2exp (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (n), count);
+      if (mpz_tstbit (SCM_I_BIG_MPZ (n), count-1)
+          && (mpz_odd_p (SCM_I_BIG_MPZ (q))
+              || (mpz_scan1 (SCM_I_BIG_MPZ (n), 0) < count-1)))
+        mpz_add_ui (SCM_I_BIG_MPZ (q), SCM_I_BIG_MPZ (q), 1);
+      scm_remember_upto_here_1 (n);
+      return scm_i_normbig (q);
+    }
+  else
+    scm_syserror ("round_right_shift_exact_integer");
+}
+
 SCM_DEFINE (scm_ash, "ash", 2, 0, 0,
-            (SCM n, SCM cnt),
-           "Return @var{n} shifted left by @var{cnt} bits, or shifted right\n"
-           "if @var{cnt} is negative.  This is an ``arithmetic'' shift.\n"
-           "\n"
-           "This is effectively a multiplication by 2^@var{cnt}, and when\n"
-           "@var{cnt} is negative it's a division, rounded towards negative\n"
-           "infinity.  (Note that this is not the same rounding as\n"
-           "@code{quotient} does.)\n"
+            (SCM n, SCM count),
+           "Return @math{floor(@var{n} * 2^@var{count})}.\n"
+           "@var{n} and @var{count} must be exact integers.\n"
            "\n"
-           "With @var{n} viewed as an infinite precision twos complement,\n"
-           "@code{ash} means a left shift introducing zero bits, or a right\n"
-           "shift dropping bits.\n"
+           "With @var{n} viewed as an infinite-precision twos-complement\n"
+           "integer, @code{ash} means a left shift introducing zero bits\n"
+           "when @var{count} is positive, or a right shift dropping bits\n"
+           "when @var{count} is negative.  This is an ``arithmetic'' shift.\n"
            "\n"
            "@lisp\n"
            "(number->string (ash #b1 3) 2)     @result{} \"1000\"\n"
            "(number->string (ash #b1010 -1) 2) @result{} \"101\"\n"
            "\n"
-           ";; -23 is bits ...11101001, -6 is bits ...111010\n"
-           "(ash -23 -2) @result{} -6\n"
+           ";; -23 is bits ...11101001, -6 is bits ...111010\n"
+           "(ash -23 -2) @result{} -6\n"
+           "@end lisp")
+#define FUNC_NAME s_scm_ash
+{
+  if (SCM_I_INUMP (n) || SCM_BIGP (n))
+    {
+      long bits_to_shift = scm_to_long (count);
+
+      if (bits_to_shift > 0)
+        return left_shift_exact_integer (n, bits_to_shift);
+      else if (SCM_LIKELY (bits_to_shift < 0))
+        return floor_right_shift_exact_integer (n, -bits_to_shift);
+      else
+        return n;
+    }
+  else
+    SCM_WRONG_TYPE_ARG (SCM_ARG1, n);
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_round_ash, "round-ash", 2, 0, 0,
+            (SCM n, SCM count),
+           "Return @math{round(@var{n} * 2^@var{count})}.\n"
+           "@var{n} and @var{count} must be exact integers.\n"
+           "\n"
+           "With @var{n} viewed as an infinite-precision twos-complement\n"
+           "integer, @code{round-ash} means a left shift introducing zero\n"
+           "bits when @var{count} is positive, or a right shift rounding\n"
+           "to the nearest integer (with ties going to the nearest even\n"
+           "integer) when @var{count} is negative.  This is a rounded\n"
+           "``arithmetic'' shift.\n"
+           "\n"
+           "@lisp\n"
+           "(number->string (round-ash #b1 3) 2)     @result{} \"1000\"\n"
+           "(number->string (round-ash #b1010 -1) 2) @result{} \"101\"\n"
+           "(number->string (round-ash #b1010 -2) 2) @result{} \"10\"\n"
+           "(number->string (round-ash #b1011 -2) 2) @result{} \"11\"\n"
+           "(number->string (round-ash #b1101 -2) 2) @result{} \"11\"\n"
+           "(number->string (round-ash #b1110 -2) 2) @result{} \"100\"\n"
            "@end lisp")
-#define FUNC_NAME s_scm_ash
+#define FUNC_NAME s_scm_round_ash
 {
-  long bits_to_shift;
-  bits_to_shift = scm_to_long (cnt);
-
-  if (SCM_I_INUMP (n))
+  if (SCM_I_INUMP (n) || SCM_BIGP (n))
     {
-      long nn = SCM_I_INUM (n);
+      long bits_to_shift = scm_to_long (count);
 
       if (bits_to_shift > 0)
-        {
-          /* Left shift of bits_to_shift >= SCM_I_FIXNUM_BIT-1 will always
-             overflow a non-zero fixnum.  For smaller shifts we check the
-             bits going into positions above SCM_I_FIXNUM_BIT-1.  If they're
-             all 0s for nn>=0, or all 1s for nn<0 then there's no overflow.
-             Those bits are "nn >> (SCM_I_FIXNUM_BIT-1 -
-             bits_to_shift)".  */
-
-          if (nn == 0)
-            return n;
-
-          if (bits_to_shift < SCM_I_FIXNUM_BIT-1
-              && ((unsigned long)
-                  (SCM_SRS (nn, (SCM_I_FIXNUM_BIT-1 - bits_to_shift)) + 1)
-                  <= 1))
-            {
-              return SCM_I_MAKINUM (nn << bits_to_shift);
-            }
-          else
-            {
-              SCM result = scm_i_long2big (nn);
-              mpz_mul_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (result),
-                            bits_to_shift);
-              return result;
-            }
-        }
+        return left_shift_exact_integer (n, bits_to_shift);
+      else if (SCM_LIKELY (bits_to_shift < 0))
+        return round_right_shift_exact_integer (n, -bits_to_shift);
       else
-        {
-          bits_to_shift = -bits_to_shift;
-          if (bits_to_shift >= SCM_LONG_BIT)
-            return (nn >= 0 ? SCM_I_MAKINUM (0) : SCM_I_MAKINUM(-1));
-          else
-            return SCM_I_MAKINUM (SCM_SRS (nn, bits_to_shift));
-        }
-
-    }
-  else if (SCM_BIGP (n))
-    {
-      SCM result;
-
-      if (bits_to_shift == 0)
         return n;
-
-      result = scm_i_mkbig ();
-      if (bits_to_shift >= 0)
-        {
-          mpz_mul_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (n),
-                        bits_to_shift);
-          return result;
-        }
-      else
-        {
-          /* GMP doesn't have an fdiv_q_2exp variant returning just a long, so
-             we have to allocate a bignum even if the result is going to be a
-             fixnum.  */
-          mpz_fdiv_q_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (n),
-                           -bits_to_shift);
-          return scm_i_normbig (result);
-        }
-
     }
   else
-    {
-      SCM_WRONG_TYPE_ARG (SCM_ARG1, n);
-    }
+    SCM_WRONG_TYPE_ARG (SCM_ARG1, n);
 }
 #undef FUNC_NAME
 
@@ -1949,7 +5145,7 @@ SCM_DEFINE (scm_bit_extract, "bit-extract", 3, 0, 0,
 
   if (SCM_I_INUMP (n))
     {
-      long int in = SCM_I_INUM (n);
+      scm_t_inum in = SCM_I_INUM (n);
 
       /* When istart>=SCM_I_FIXNUM_BIT we can just limit the shift to
          SCM_I_FIXNUM_BIT-1 to get either 0 or -1 per the sign of "in". */
@@ -1961,7 +5157,7 @@ SCM_DEFINE (scm_bit_extract, "bit-extract", 3, 0, 0,
           * special case requires us to produce a result that has
           * more bits than can be stored in a fixnum.
           */
-          SCM result = scm_i_long2big (in);
+          SCM result = scm_i_inum2big (in);
           mpz_fdiv_r_2exp (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (result),
                            bits);
           return result;
@@ -2020,8 +5216,8 @@ SCM_DEFINE (scm_logcount, "logcount", 1, 0, 0,
 {
   if (SCM_I_INUMP (n))
     {
-      unsigned long int c = 0;
-      long int nn = SCM_I_INUM (n);
+      unsigned long c = 0;
+      scm_t_inum nn = SCM_I_INUM (n);
       if (nn < 0)
         nn = -1 - nn;
       while (nn)
@@ -2068,9 +5264,9 @@ SCM_DEFINE (scm_integer_length, "integer-length", 1, 0, 0,
 {
   if (SCM_I_INUMP (n))
     {
-      unsigned long int c = 0;
+      unsigned long c = 0;
       unsigned int l = 4;
-      long int nn = SCM_I_INUM (n);
+      scm_t_inum nn = SCM_I_INUM (n);
       if (nn < 0)
        nn = -1 - nn;
       while (nn)
@@ -2100,229 +5296,230 @@ SCM_DEFINE (scm_integer_length, "integer-length", 1, 0, 0,
 #undef FUNC_NAME
 
 /*** NUMBERS -> STRINGS ***/
-#define SCM_MAX_DBL_PREC  60
 #define SCM_MAX_DBL_RADIX 36
 
-/* this is an array starting with radix 2, and ending with radix SCM_MAX_DBL_RADIX */
-static int scm_dblprec[SCM_MAX_DBL_RADIX - 1];
-static double fx_per_radix[SCM_MAX_DBL_RADIX - 1][SCM_MAX_DBL_PREC];
-
-static
-void init_dblprec(int *prec, int radix) {
-   /* determine floating point precision by adding successively
-      smaller increments to 1.0 until it is considered == 1.0 */
-   double f = ((double)1.0)/radix;
-   double fsum = 1.0 + f;
-
-   *prec = 0;
-   while (fsum != 1.0)
-   {
-      if (++(*prec) > SCM_MAX_DBL_PREC)
-         fsum = 1.0;
-      else
-      {
-         f /= radix;
-         fsum = f + 1.0;
-      }
-   }
-   (*prec) -= 1;
-}
-
-static
-void init_fx_radix(double *fx_list, int radix)
-{
-  /* initialize a per-radix list of tolerances.  When added
-     to a number < 1.0, we can determine if we should raund
-     up and quit converting a number to a string. */
-   int i;
-   fx_list[0] = 0.0;
-   fx_list[1] = 0.5;
-   for( i = 2 ; i < SCM_MAX_DBL_PREC; ++i ) 
-     fx_list[i] = (fx_list[i-1] / radix);
-}
-
 /* use this array as a way to generate a single digit */
-static const char*number_chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+static const char number_chars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+static mpz_t dbl_minimum_normal_mantissa;
 
 static size_t
-idbl2str (double f, char *a, int radix)
+idbl2str (double dbl, char *a, int radix)
 {
-   int efmt, dpt, d, i, wp;
-   double *fx;
-#ifdef DBL_MIN_10_EXP
-   double f_cpy;
-   int exp_cpy;
-#endif /* DBL_MIN_10_EXP */
-   size_t ch = 0;
-   int exp = 0;
-
-   if(radix < 2 || 
-      radix > SCM_MAX_DBL_RADIX)
-   {
-      /* revert to existing behavior */
-      radix = 10;
-   }
+  int ch = 0;
 
-   wp = scm_dblprec[radix-2];
-   fx = fx_per_radix[radix-2];
+  if (radix < 2 || radix > SCM_MAX_DBL_RADIX)
+    /* revert to existing behavior */
+    radix = 10;
 
-  if (f == 0.0)
+  if (isinf (dbl))
     {
-#ifdef HAVE_COPYSIGN
-      double sgn = copysign (1.0, f);
-
-      if (sgn < 0.0)
-       a[ch++] = '-';
-#endif
-      goto zero;       /*{a[0]='0'; a[1]='.'; a[2]='0'; return 3;} */
+      strcpy (a, (dbl > 0.0) ? "+inf.0" : "-inf.0");
+      return 6;
     }
-
-  if (isinf (f))
+  else if (dbl > 0.0)
+    ;
+  else if (dbl < 0.0)
     {
-      if (f < 0)
-       strcpy (a, "-inf.0");
-      else
-       strcpy (a, "+inf.0");
-      return ch+6;
+      dbl = -dbl;
+      a[ch++] = '-';
     }
-  else if (isnan (f))
+  else if (dbl == 0.0)
     {
-      strcpy (a, "+nan.0");
-      return ch+6;
+      if (!double_is_non_negative_zero (dbl))
+        a[ch++] = '-';
+      strcpy (a + ch, "0.0");
+      return ch + 3;
     }
-
-  if (f < 0.0)
+  else if (isnan (dbl))
     {
-      f = -f;
-      a[ch++] = '-';
+      strcpy (a, "+nan.0");
+      return 6;
     }
 
-#ifdef DBL_MIN_10_EXP  /* Prevent unnormalized values, as from 
-                         make-uniform-vector, from causing infinite loops. */
-  /* just do the checking...if it passes, we do the conversion for our
-     radix again below */
-  f_cpy = f;
-  exp_cpy = exp;
+  /* Algorithm taken from "Printing Floating-Point Numbers Quickly and
+     Accurately" by Robert G. Burger and R. Kent Dybvig */
+  {
+    int e, k;
+    mpz_t f, r, s, mplus, mminus, hi, digit;
+    int f_is_even, f_is_odd;
+    int expon;
+    int show_exp = 0;
+
+    mpz_inits (f, r, s, mplus, mminus, hi, digit, NULL);
+    mpz_set_d (f, ldexp (frexp (dbl, &e), DBL_MANT_DIG));
+    if (e < DBL_MIN_EXP)
+      {
+        mpz_tdiv_q_2exp (f, f, DBL_MIN_EXP - e);
+        e = DBL_MIN_EXP;
+      }
+    e -= DBL_MANT_DIG;
 
-  while (f_cpy < 1.0)
-    {
-      f_cpy *= 10.0;
-      if (exp_cpy-- < DBL_MIN_10_EXP)
-       {
-         a[ch++] = '#';
-         a[ch++] = '.';
-         a[ch++] = '#';
-         return ch;
-       }
-    }
-  while (f_cpy > 10.0)
-    {
-      f_cpy *= 0.10;
-      if (exp_cpy++ > DBL_MAX_10_EXP)
-       {
-         a[ch++] = '#';
-         a[ch++] = '.';
-         a[ch++] = '#';
-         return ch;
-       }
-    }
-#endif
+    f_is_even = !mpz_odd_p (f);
+    f_is_odd = !f_is_even;
 
-  while (f < 1.0)
-    {
-      f *= radix;
-      exp--;
-    }
-  while (f > radix)
-    {
-      f /= radix;
-      exp++;
-    }
+    /* Initialize r, s, mplus, and mminus according
+       to Table 1 from the paper. */
+    if (e < 0)
+      {
+        mpz_set_ui (mminus, 1);
+        if (mpz_cmp (f, dbl_minimum_normal_mantissa) != 0
+            || e == DBL_MIN_EXP - DBL_MANT_DIG)
+          {
+            mpz_set_ui (mplus, 1);
+            mpz_mul_2exp (r, f, 1);
+            mpz_mul_2exp (s, mminus, 1 - e);
+          }
+        else
+          {
+            mpz_set_ui (mplus, 2);
+            mpz_mul_2exp (r, f, 2);
+            mpz_mul_2exp (s, mminus, 2 - e);
+          }
+      }
+    else
+      {
+        mpz_set_ui (mminus, 1);
+        mpz_mul_2exp (mminus, mminus, e);
+        if (mpz_cmp (f, dbl_minimum_normal_mantissa) != 0)
+          {
+            mpz_set (mplus, mminus);
+            mpz_mul_2exp (r, f, 1 + e);
+            mpz_set_ui (s, 2);
+          }
+        else
+          {
+            mpz_mul_2exp (mplus, mminus, 1);
+            mpz_mul_2exp (r, f, 2 + e);
+            mpz_set_ui (s, 4);
+          }
+      }
 
-  if (f + fx[wp] >= radix)
-    {
-      f = 1.0;
-      exp++;
-    }
- zero:
-#ifdef ENGNOT 
-  /* adding 9999 makes this equivalent to abs(x) % 3 */
-  dpt = (exp + 9999) % 3;
-  exp -= dpt++;
-  efmt = 1;
-#else
-  efmt = (exp < -3) || (exp > wp + 2);
-  if (!efmt)
+    /* Find the smallest k such that:
+         (r + mplus) / s <  radix^k  (if f is even)
+         (r + mplus) / s <= radix^k  (if f is odd) */
     {
-      if (exp < 0)
-       {
-         a[ch++] = '0';
-         a[ch++] = '.';
-         dpt = exp;
-         while (++dpt)
-           a[ch++] = '0';
-       }
-      else
-       dpt = exp + 1;
+      /* IMPROVE-ME: Make an initial guess to speed this up */
+      mpz_add (hi, r, mplus);
+      k = 0;
+      while (mpz_cmp (hi, s) >= f_is_odd)
+        {
+          mpz_mul_ui (s, s, radix);
+          k++;
+        }
+      if (k == 0)
+        {
+          mpz_mul_ui (hi, hi, radix);
+          while (mpz_cmp (hi, s) < f_is_odd)
+            {
+              mpz_mul_ui (r, r, radix);
+              mpz_mul_ui (mplus, mplus, radix);
+              mpz_mul_ui (mminus, mminus, radix);
+              mpz_mul_ui (hi, hi, radix);
+              k--;
+            }
+        }
     }
-  else
-    dpt = 1;
-#endif
 
-  do
-    {
-      d = f;
-      f -= d;
-      a[ch++] = number_chars[d];
-      if (f < fx[wp])
-       break;
-      if (f + fx[wp] >= 1.0)
-       {
-          a[ch - 1] = number_chars[d+1]; 
-         break;
-       }
-      f *= radix;
-      if (!(--dpt))
-       a[ch++] = '.';
-    }
-  while (wp--);
+    expon = k - 1;
+    if (k <= 0)
+      {
+        if (k <= -3)
+          {
+            /* Use scientific notation */
+            show_exp = 1;
+            k = 1;
+          }
+        else
+          {
+            int i;
 
-  if (dpt > 0)
-    {
-#ifndef ENGNOT
-      if ((dpt > 4) && (exp > 6))
-       {
-         d = (a[0] == '-' ? 2 : 1);
-         for (i = ch++; i > d; i--)
-           a[i] = a[i - 1];
-         a[d] = '.';
-         efmt = 1;
-       }
-      else
-#endif
-       {
-         while (--dpt)
-           a[ch++] = '0';
-         a[ch++] = '.';
-       }
-    }
-  if (a[ch - 1] == '.')
-    a[ch++] = '0';             /* trailing zero */
-  if (efmt && exp)
-    {
-      a[ch++] = 'e';
-      if (exp < 0)
-       {
-         exp = -exp;
-         a[ch++] = '-';
-       }
-      for (i = radix; i <= exp; i *= radix);
-      for (i /= radix; i; i /= radix)
-       {
-          a[ch++] = number_chars[exp / i];
-         exp %= i;
-       }
-    }
+            /* Print leading zeroes */
+            a[ch++] = '0';
+            a[ch++] = '.';
+            for (i = 0; i > k; i--)
+              a[ch++] = '0';
+          }
+      }
+
+    for (;;)
+      {
+        int end_1_p, end_2_p;
+        int d;
+
+        mpz_mul_ui (mplus, mplus, radix);
+        mpz_mul_ui (mminus, mminus, radix);
+        mpz_mul_ui (r, r, radix);
+        mpz_fdiv_qr (digit, r, r, s);
+        d = mpz_get_ui (digit);
+
+        mpz_add (hi, r, mplus);
+        end_1_p = (mpz_cmp (r, mminus) < f_is_even);
+        end_2_p = (mpz_cmp (s, hi) < f_is_even);
+        if (end_1_p || end_2_p)
+          {
+            mpz_mul_2exp (r, r, 1);
+            if (!end_2_p)
+              ;
+            else if (!end_1_p)
+              d++;
+            else if (mpz_cmp (r, s) >= !(d & 1))
+              d++;
+            a[ch++] = number_chars[d];
+            if (--k == 0)
+              a[ch++] = '.';
+            break;
+          }
+        else
+          {
+            a[ch++] = number_chars[d];
+            if (--k == 0)
+              a[ch++] = '.';
+          }
+      }
+
+    if (k > 0)
+      {
+        if (expon >= 7 && k >= 4 && expon >= k)
+          {
+            /* Here we would have to print more than three zeroes
+               followed by a decimal point and another zero.  It
+               makes more sense to use scientific notation. */
+
+            /* Adjust k to what it would have been if we had chosen
+               scientific notation from the beginning. */
+            k -= expon;
+
+            /* k will now be <= 0, with magnitude equal to the number of
+               digits that we printed which should now be put after the
+               decimal point. */
+
+            /* Insert a decimal point */
+            memmove (a + ch + k + 1, a + ch + k, -k);
+            a[ch + k] = '.';
+            ch++;
+
+            show_exp = 1;
+          }
+        else
+          {
+            for (; k > 0; k--)
+              a[ch++] = '0';
+            a[ch++] = '.';
+          }
+      }
+
+    if (k == 0)
+      a[ch++] = '0';
+
+    if (show_exp)
+      {
+        a[ch++] = 'e';
+        ch += scm_iint2str (expon, radix, a + ch);
+      }
+
+    mpz_clears (f, r, s, mplus, mminus, hi, digit, NULL);
+  }
   return ch;
 }
 
@@ -2331,17 +5528,20 @@ static size_t
 icmplx2str (double real, double imag, char *str, int radix)
 {
   size_t i;
+  double sgn;
   
   i = idbl2str (real, str, radix);
-  if (imag != 0.0)
-    {
-      /* Don't output a '+' for negative numbers or for Inf and
-        NaN.  They will provide their own sign. */
-      if (0 <= imag && !isinf (imag) && !isnan (imag))
-       str[i++] = '+';
-      i += idbl2str (imag, &str[i], radix);
-      str[i++] = 'i';
-    }
+#ifdef HAVE_COPYSIGN
+  sgn = copysign (1.0, imag);
+#else
+  sgn = imag;
+#endif
+  /* Don't output a '+' for negative numbers or for Inf and
+     NaN.  They will provide their own sign. */
+  if (sgn >= 0 && DOUBLE_IS_FINITE (imag))
+    str[i++] = '+';
+  i += idbl2str (imag, &str[i], radix);
+  str[i++] = 'i';
   return i;
 }
 
@@ -2384,6 +5584,9 @@ scm_iuint2str (scm_t_uintmax num, int rad, char *p)
   size_t i;
   scm_t_uintmax n = num;
 
+  if (rad < 2 || rad > 36)
+    scm_out_of_range ("scm_iuint2str", scm_from_int (rad));
+
   for (n /= rad; n > 0; n /= rad)
     j++;
 
@@ -2394,7 +5597,7 @@ scm_iuint2str (scm_t_uintmax num, int rad, char *p)
       int d = n % rad;
 
       n /= rad;
-      p[i] = d + ((d < 10) ? '0' : 'a' - 10);
+      p[i] = number_chars[d];
     }
   return j;
 }
@@ -2422,8 +5625,14 @@ SCM_DEFINE (scm_number_to_string, "number->string", 1, 1, 0,
   else if (SCM_BIGP (n))
     {
       char *str = mpz_get_str (NULL, base, SCM_I_BIG_MPZ (n));
+      size_t len = strlen (str);
+      void (*freefunc) (void *, size_t);
+      SCM ret;
+      mp_get_memory_functions (NULL, NULL, &freefunc);
       scm_remember_upto_here_1 (n);
-      return scm_take_locale_string (str);
+      ret = scm_from_latin1_stringn (str, len);
+      freefunc (str, len + 1);
+      return ret;
     }
   else if (SCM_FRACTIONP (n))
     {
@@ -2449,7 +5658,7 @@ int
 scm_print_real (SCM sexp, SCM port, scm_print_state *pstate SCM_UNUSED)
 {
   char num_buf[FLOBUFLEN];
-  scm_lfwrite (num_buf, iflo2str (sexp, num_buf, 10), port);
+  scm_lfwrite_unlocked (num_buf, iflo2str (sexp, num_buf, 10), port);
   return !0;
 }
 
@@ -2457,7 +5666,7 @@ void
 scm_i_print_double (double val, SCM port)
 {
   char num_buf[FLOBUFLEN];
-  scm_lfwrite (num_buf, idbl2str (val, num_buf, 10), port);
+  scm_lfwrite_unlocked (num_buf, idbl2str (val, num_buf, 10), port);
 }
 
 int
@@ -2465,7 +5674,7 @@ scm_print_complex (SCM sexp, SCM port, scm_print_state *pstate SCM_UNUSED)
 
 {
   char num_buf[FLOBUFLEN];
-  scm_lfwrite (num_buf, iflo2str (sexp, num_buf, 10), port);
+  scm_lfwrite_unlocked (num_buf, iflo2str (sexp, num_buf, 10), port);
   return !0;
 }
 
@@ -2473,7 +5682,7 @@ void
 scm_i_print_complex (double real, double imag, SCM port)
 {
   char num_buf[FLOBUFLEN];
-  scm_lfwrite (num_buf, icmplx2str (real, imag, num_buf, 10), port);
+  scm_lfwrite_unlocked (num_buf, icmplx2str (real, imag, num_buf, 10), port);
 }
 
 int
@@ -2481,7 +5690,7 @@ scm_i_print_fraction (SCM sexp, SCM port, scm_print_state *pstate SCM_UNUSED)
 {
   SCM str;
   str = scm_number_to_string (sexp, SCM_UNDEFINED);
-  scm_lfwrite_str (str, port);
+  scm_display (str, port);
   scm_remember_upto_here_1 (str);
   return !0;
 }
@@ -2490,9 +5699,12 @@ int
 scm_bigprint (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
 {
   char *str = mpz_get_str (NULL, 10, SCM_I_BIG_MPZ (exp));
+  size_t len = strlen (str);
+  void (*freefunc) (void *, size_t);
+  mp_get_memory_functions (NULL, NULL, &freefunc);
   scm_remember_upto_here_1 (exp);
-  scm_lfwrite (str, (size_t) strlen (str), port);
-  free (str);
+  scm_lfwrite_unlocked (str, len, port);
+  freefunc (str, len + 1);
   return !0;
 }
 /*** END nums->strs ***/
@@ -2505,14 +5717,15 @@ scm_bigprint (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
  * in R5RS.  Thus, the functions resemble syntactic units (<ureal R>,
  * <uinteger R>, ...) that are used to build up numbers in the grammar.  Some
  * points should be noted about the implementation:
+ *
  * * Each function keeps a local index variable 'idx' that points at the
  * current position within the parsed string.  The global index is only
  * updated if the function could parse the corresponding syntactic unit
  * successfully.
+ *
  * * Similarly, the functions keep track of indicators of inexactness ('#',
- * '.' or exponents) using local variables ('hash_seen', 'x').  Again, the
- * global exactness information is only updated after each part has been
- * successfully parsed.
+ * '.' or exponents) using local variables ('hash_seen', 'x').
+ *
  * * Sequences of digits are parsed into temporary variables holding fixnums.
  * Only if these fixnums would overflow, the result variables are updated
  * using the standard functions scm_add, scm_product, scm_divide etc.  Then,
@@ -2521,18 +5734,64 @@ scm_bigprint (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
  * digits, a number 1234567890 would be parsed in two parts 12345 and 67890,
  * and the result was computed as 12345 * 100000 + 67890.  In other words,
  * only every five digits two bignum operations were performed.
+ *
+ * Notes on the handling of exactness specifiers:
+ *
+ * When parsing non-real complex numbers, we apply exactness specifiers on
+ * per-component basis, as is done in PLT Scheme.  For complex numbers
+ * written in rectangular form, exactness specifiers are applied to the
+ * real and imaginary parts before calling scm_make_rectangular.  For
+ * complex numbers written in polar form, exactness specifiers are applied
+ * to the magnitude and angle before calling scm_make_polar.
+ * 
+ * There are two kinds of exactness specifiers: forced and implicit.  A
+ * forced exactness specifier is a "#e" or "#i" prefix at the beginning of
+ * the entire number, and applies to both components of a complex number.
+ * "#e" causes each component to be made exact, and "#i" causes each
+ * component to be made inexact.  If no forced exactness specifier is
+ * present, then the exactness of each component is determined
+ * independently by the presence or absence of a decimal point or hash mark
+ * within that component.  If a decimal point or hash mark is present, the
+ * component is made inexact, otherwise it is made exact.
+ *  
+ * After the exactness specifiers have been applied to each component, they
+ * are passed to either scm_make_rectangular or scm_make_polar to produce
+ * the final result.  Note that this will result in a real number if the
+ * imaginary part, magnitude, or angle is an exact 0.
+ * 
+ * For example, (string->number "#i5.0+0i") does the equivalent of:
+ * 
+ *   (make-rectangular (exact->inexact 5) (exact->inexact 0))
  */
 
 enum t_exactness {NO_EXACTNESS, INEXACT, EXACT};
 
 /* R5RS, section 7.1.1, lexical structure of numbers: <uinteger R>. */
 
-/* In non ASCII-style encodings the following macro might not work. */
-#define XDIGIT2UINT(d)                                                  \
-  (uc_is_property_decimal_digit ((int) (unsigned char) d)               \
-   ? (d) - '0'                                                          \
-   : uc_tolower ((int) (unsigned char) d) - 'a' + 10)
+/* Caller is responsible for checking that the return value is in range
+   for the given radix, which should be <= 36. */
+static unsigned int
+char_decimal_value (scm_t_uint32 c)
+{
+  /* uc_decimal_value returns -1 on error. When cast to an unsigned int,
+     that's certainly above any valid decimal, so we take advantage of
+     that to elide some tests. */
+  unsigned int d = (unsigned int) uc_decimal_value (c);
+
+  /* If that failed, try extended hexadecimals, then. Only accept ascii
+     hexadecimals. */
+  if (d >= 10U)
+    {
+      c = uc_tolower (c);
+      if (c >= (scm_t_uint32) 'a')
+        d = c - (scm_t_uint32)'a' + 10U;
+    }
+  return d;
+}
 
+/* Parse the substring of MEM starting at *P_IDX for an unsigned integer
+   in base RADIX.  Upon success, return the unsigned integer and update
+   *P_IDX and *P_EXACTNESS accordingly.  Return #f on failure.  */
 static SCM
 mem2uinteger (SCM mem, unsigned int *p_idx,
              unsigned int radix, enum t_exactness *p_exactness)
@@ -2550,9 +5809,7 @@ mem2uinteger (SCM mem, unsigned int *p_idx,
     return SCM_BOOL_F;
 
   c = scm_i_string_ref (mem, idx);
-  if (!uc_is_property_ascii_hex_digit ((scm_t_uint32) c))
-    return SCM_BOOL_F;
-  digit_value = XDIGIT2UINT (c);
+  digit_value = char_decimal_value (c);
   if (digit_value >= radix)
     return SCM_BOOL_F;
 
@@ -2561,21 +5818,21 @@ mem2uinteger (SCM mem, unsigned int *p_idx,
   while (idx != len)
     {
       scm_t_wchar c = scm_i_string_ref (mem, idx);
-      if (uc_is_property_ascii_hex_digit ((scm_t_uint32) c))
-       {
-         if (hash_seen)
-           break;
-         digit_value = XDIGIT2UINT (c);
-         if (digit_value >= radix)
-           break;
-       }
-      else if (c == '#')
+      if (c == '#')
        {
          hash_seen = 1;
          digit_value = 0;
        }
+      else if (hash_seen)
+        break;
       else
-       break;
+        {
+          digit_value = char_decimal_value (c);
+          /* This check catches non-decimals in addition to out-of-range
+             decimals.  */
+          if (digit_value >= radix)
+           break;
+       }
 
       idx++;
       if (SCM_MOST_POSITIVE_FIXNUM / radix < shift)
@@ -2632,7 +5889,7 @@ mem2decimal_from_point (SCM result, SCM mem,
       scm_t_bits shift = 1;
       scm_t_bits add = 0;
       unsigned int digit_value;
-      SCM big_shift = SCM_I_MAKINUM (1);
+      SCM big_shift = SCM_INUM1;
 
       idx++;
       while (idx != len)
@@ -2746,7 +6003,7 @@ mem2decimal_from_point (SCM result, SCM mem,
                break;
            }
 
-         if (exponent > SCM_MAXEXP)
+         if (exponent > ((sign == 1) ? SCM_MAXEXP : SCM_MAXEXP + DBL_DIG + 1))
            {
              size_t exp_len = idx - start;
              SCM exp_string = scm_i_substring_copy (mem, start, start + exp_len);
@@ -2758,7 +6015,7 @@ mem2decimal_from_point (SCM result, SCM mem,
          if (sign == 1)
            result = scm_product (result, e);
          else
-           result = scm_divide2real (result, e);
+           result = scm_divide (result, e);
 
          /* We've seen an exponent, thus the value is implicitly inexact. */
          x = INEXACT;
@@ -2782,7 +6039,8 @@ mem2decimal_from_point (SCM result, SCM mem,
 
 static SCM
 mem2ureal (SCM mem, unsigned int *p_idx,
-          unsigned int radix, enum t_exactness *p_exactness)
+          unsigned int radix, enum t_exactness forced_x,
+           int allow_inf_or_nan)
 {
   unsigned int idx = *p_idx;
   SCM result;
@@ -2790,26 +6048,58 @@ mem2ureal (SCM mem, unsigned int *p_idx,
 
   /* Start off believing that the number will be exact.  This changes
      to INEXACT if we see a decimal point or a hash. */
-  enum t_exactness x = EXACT;
+  enum t_exactness implicit_x = EXACT;
 
   if (idx == len)
     return SCM_BOOL_F;
 
-  if (idx+5 <= len && !scm_i_string_strcmp (mem, idx, "inf.0"))
-    {
-      *p_idx = idx+5;
-      return scm_inf ();
-    }
-
-  if (idx+4 < len && !scm_i_string_strcmp (mem, idx, "nan."))
-    {
-      /* Cobble up the fractional part.  We might want to set the
-        NaN's mantissa from it. */
-      idx += 4;
-      mem2uinteger (mem, &idx, 10, &x);
-      *p_idx = idx;
-      return scm_nan ();
-    }
+  if (allow_inf_or_nan && forced_x != EXACT && idx+5 <= len)
+    switch (scm_i_string_ref (mem, idx))
+      {
+      case 'i': case 'I':
+        switch (scm_i_string_ref (mem, idx + 1))
+          {
+          case 'n': case 'N':
+            switch (scm_i_string_ref (mem, idx + 2))
+              {
+              case 'f': case 'F':
+                if (scm_i_string_ref (mem, idx + 3) == '.'
+                    && scm_i_string_ref (mem, idx + 4) == '0')
+                  {
+                    *p_idx = idx+5;
+                    return scm_inf ();
+                  }
+              }
+          }
+      case 'n': case 'N':
+        switch (scm_i_string_ref (mem, idx + 1))
+          {
+          case 'a': case 'A':
+            switch (scm_i_string_ref (mem, idx + 2))
+              {
+              case 'n': case 'N':
+                if (scm_i_string_ref (mem, idx + 3) == '.')
+                  {
+                    /* Cobble up the fractional part.  We might want to
+                       set the NaN's mantissa from it. */
+                    idx += 4;
+                    if (!scm_is_eq (mem2uinteger (mem, &idx, 10, &implicit_x),
+                                    SCM_INUM0))
+                      {
+#if SCM_ENABLE_DEPRECATED == 1
+                        scm_c_issue_deprecation_warning
+                          ("Non-zero suffixes to `+nan.' are deprecated.  Use `+nan.0'.");
+#else
+                        return SCM_BOOL_F;
+#endif
+                      }
+          
+                    *p_idx = idx;
+                    return scm_nan ();
+                  }
+              }
+          }
+      }
 
   if (scm_i_string_ref (mem, idx) == '.')
     {
@@ -2820,14 +6110,14 @@ mem2ureal (SCM mem, unsigned int *p_idx,
       else if (!uc_is_property_decimal_digit ((scm_t_uint32) scm_i_string_ref (mem, idx+1)))
        return SCM_BOOL_F;
       else
-       result = mem2decimal_from_point (SCM_I_MAKINUM (0), mem,
-                                        p_idx, &x);
+       result = mem2decimal_from_point (SCM_INUM0, mem,
+                                        p_idx, &implicit_x);
     }
   else
     {
       SCM uinteger;
 
-      uinteger = mem2uinteger (mem, &idx, radix, &x);
+      uinteger = mem2uinteger (mem, &idx, radix, &implicit_x);
       if (scm_is_false (uinteger))
        return SCM_BOOL_F;
 
@@ -2841,8 +6131,8 @@ mem2ureal (SCM mem, unsigned int *p_idx,
           if (idx == len)
             return SCM_BOOL_F;
 
-         divisor = mem2uinteger (mem, &idx, radix, &x);
-         if (scm_is_false (divisor))
+         divisor = mem2uinteger (mem, &idx, radix, &implicit_x);
+         if (scm_is_false (divisor) || scm_is_eq (divisor, SCM_INUM0))
            return SCM_BOOL_F;
 
          /* both are int/big here, I assume */
@@ -2850,7 +6140,7 @@ mem2ureal (SCM mem, unsigned int *p_idx,
        }
       else if (radix == 10)
        {
-         result = mem2decimal_from_point (uinteger, mem, &idx, &x);
+         result = mem2decimal_from_point (uinteger, mem, &idx, &implicit_x);
          if (scm_is_false (result))
            return SCM_BOOL_F;
        }
@@ -2860,21 +6150,32 @@ mem2ureal (SCM mem, unsigned int *p_idx,
       *p_idx = idx;
     }
 
-  /* Update *p_exactness if the number just read was inexact.  This is
-     important for complex numbers, so that a complex number is
-     treated as inexact overall if either its real or imaginary part
-     is inexact.
-  */
-  if (x == INEXACT)
-    *p_exactness = x;
-
-  /* When returning an inexact zero, make sure it is represented as a
-     floating point value so that we can change its sign. 
-  */
-  if (scm_is_eq (result, SCM_I_MAKINUM(0)) && *p_exactness == INEXACT)
-    result = scm_from_double (0.0);
+  switch (forced_x)
+    {
+    case EXACT:
+      if (SCM_INEXACTP (result))
+       return scm_inexact_to_exact (result);
+      else
+       return result;
+    case INEXACT:
+      if (SCM_INEXACTP (result))
+       return result;
+      else
+       return scm_exact_to_inexact (result);
+    case NO_EXACTNESS:
+      if (implicit_x == INEXACT)
+       {
+         if (SCM_INEXACTP (result))
+           return result;
+         else
+           return scm_exact_to_inexact (result);
+       }
+      else
+       return result;
+    }
 
-  return result;
+  /* We should never get here */
+  scm_syserror ("mem2ureal");
 }
 
 
@@ -2882,7 +6183,7 @@ mem2ureal (SCM mem, unsigned int *p_idx,
 
 static SCM
 mem2complex (SCM mem, unsigned int idx,
-            unsigned int radix, enum t_exactness *p_exactness)
+            unsigned int radix, enum t_exactness forced_x)
 {
   scm_t_wchar c;
   int sign = 0;
@@ -2907,7 +6208,7 @@ mem2complex (SCM mem, unsigned int idx,
   if (idx == len)
     return SCM_BOOL_F;
 
-  ureal = mem2ureal (mem, &idx, radix, p_exactness);
+  ureal = mem2ureal (mem, &idx, radix, forced_x, sign != 0);
   if (scm_is_false (ureal))
     {
       /* input must be either +i or -i */
@@ -2922,7 +6223,7 @@ mem2complex (SCM mem, unsigned int idx,
          if (idx != len)
            return SCM_BOOL_F;
          
-         return scm_make_rectangular (SCM_I_MAKINUM (0), SCM_I_MAKINUM (sign));
+         return scm_make_rectangular (SCM_INUM0, SCM_I_MAKINUM (sign));
        }
       else
        return SCM_BOOL_F;
@@ -2946,7 +6247,7 @@ mem2complex (SCM mem, unsigned int idx,
            return SCM_BOOL_F;
          if (idx != len)
            return SCM_BOOL_F;
-         return scm_make_rectangular (SCM_I_MAKINUM (0), ureal);
+         return scm_make_rectangular (SCM_INUM0, ureal);
 
        case '@':
          /* polar input: <real>@<real>. */
@@ -2976,9 +6277,9 @@ mem2complex (SCM mem, unsigned int idx,
                  sign = -1;
                }
              else
-               sign = 1;
+               sign = 0;
 
-             angle = mem2ureal (mem, &idx, radix, p_exactness);
+             angle = mem2ureal (mem, &idx, radix, forced_x, sign != 0);
              if (scm_is_false (angle))
                return SCM_BOOL_F;
              if (idx != len)
@@ -3000,7 +6301,7 @@ mem2complex (SCM mem, unsigned int idx,
          else
            {
              int sign = (c == '+') ? 1 : -1;
-             SCM imag = mem2ureal (mem, &idx, radix, p_exactness);
+             SCM imag = mem2ureal (mem, &idx, radix, forced_x, sign != 0);
 
              if (scm_is_false (imag))
                imag = SCM_I_MAKINUM (sign);
@@ -3036,8 +6337,6 @@ scm_i_string_to_number (SCM mem, unsigned int default_radix)
   unsigned int idx = 0;
   unsigned int radix = NO_RADIX;
   enum t_exactness forced_x = NO_EXACTNESS;
-  enum t_exactness implicit_x = EXACT;
-  SCM result;
   size_t len = scm_i_string_length (mem);
 
   /* R5RS, section 7.1.1, lexical structure of numbers: <prefix R> */
@@ -3076,44 +6375,16 @@ scm_i_string_to_number (SCM mem, unsigned int default_radix)
          radix = HEX;
          break;
        default:
-         return SCM_BOOL_F;
-       }
-      idx += 2;
-    }
-
-  /* R5RS, section 7.1.1, lexical structure of numbers: <complex R> */
-  if (radix == NO_RADIX)
-    result = mem2complex (mem, idx, default_radix, &implicit_x);
-  else
-    result = mem2complex (mem, idx, (unsigned int) radix, &implicit_x);
-
-  if (scm_is_false (result))
-    return SCM_BOOL_F;
-
-  switch (forced_x)
-    {
-    case EXACT:
-      if (SCM_INEXACTP (result))
-       return scm_inexact_to_exact (result);
-      else
-       return result;
-    case INEXACT:
-      if (SCM_INEXACTP (result))
-       return result;
-      else
-       return scm_exact_to_inexact (result);
-    case NO_EXACTNESS:
-    default:
-      if (implicit_x == INEXACT)
-       {
-         if (SCM_INEXACTP (result))
-           return result;
-         else
-           return scm_exact_to_inexact (result);
+         return SCM_BOOL_F;
        }
-      else
-       return result;
+      idx += 2;
     }
+
+  /* R5RS, section 7.1.1, lexical structure of numbers: <complex R> */
+  if (radix == NO_RADIX)
+    radix = default_radix;
+
+  return mem2complex (mem, idx, radix, forced_x);
 }
 
 SCM
@@ -3157,40 +6428,6 @@ SCM_DEFINE (scm_string_to_number, "string->number", 1, 1, 0,
 /*** END strs->nums ***/
 
 
-SCM
-scm_bigequal (SCM x, SCM y)
-{
-  int result = mpz_cmp (SCM_I_BIG_MPZ (x), SCM_I_BIG_MPZ (y));
-  scm_remember_upto_here_2 (x, y);
-  return scm_from_bool (0 == result);
-}
-
-SCM
-scm_real_equalp (SCM x, SCM y)
-{
-  return scm_from_bool (SCM_REAL_VALUE (x) == SCM_REAL_VALUE (y));
-}
-
-SCM
-scm_complex_equalp (SCM x, SCM y)
-{
-  return scm_from_bool (SCM_COMPLEX_REAL (x) == SCM_COMPLEX_REAL (y)
-                  && SCM_COMPLEX_IMAG (x) == SCM_COMPLEX_IMAG (y));
-}
-
-SCM
-scm_i_fraction_equalp (SCM x, SCM y)
-{
-  if (scm_is_false (scm_equal_p (SCM_FRACTION_NUMERATOR (x),
-                              SCM_FRACTION_NUMERATOR (y)))
-      || scm_is_false (scm_equal_p (SCM_FRACTION_DENOMINATOR (x),
-                                 SCM_FRACTION_DENOMINATOR (y))))
-    return SCM_BOOL_F;
-  else
-    return SCM_BOOL_T;
-}
-
-
 SCM_DEFINE (scm_number_p, "number?", 1, 0, 0, 
             (SCM x),
            "Return @code{#t} if @var{x} is a number, @code{#f}\n"
@@ -3223,8 +6460,8 @@ SCM_DEFINE (scm_real_p, "real?", 1, 0, 0,
            "fulfilled if @var{x} is an integer number.")
 #define FUNC_NAME s_scm_real_p
 {
-  /* we can't represent irrational numbers. */
-  return scm_rational_p (x);
+  return scm_from_bool
+    (SCM_I_INUMP (x) || SCM_REALP (x) || SCM_BIGP (x) || SCM_FRACTIONP (x));
 }
 #undef FUNC_NAME
 
@@ -3236,18 +6473,12 @@ SCM_DEFINE (scm_rational_p, "rational?", 1, 0, 0,
            "fulfilled if @var{x} is an integer number.")
 #define FUNC_NAME s_scm_rational_p
 {
-  if (SCM_I_INUMP (x))
-    return SCM_BOOL_T;
-  else if (SCM_IMP (x))
-    return SCM_BOOL_F;
-  else if (SCM_BIGP (x))
-    return SCM_BOOL_T;
-  else if (SCM_FRACTIONP (x))
+  if (SCM_I_INUMP (x) || SCM_BIGP (x) || SCM_FRACTIONP (x))
     return SCM_BOOL_T;
   else if (SCM_REALP (x))
-    /* due to their limited precision, all floating point numbers are
-       rational as well. */
-    return SCM_BOOL_T;
+    /* due to their limited precision, finite floating point numbers are
+       rational as well. (finite means neither infinity nor a NaN) */
+    return scm_from_bool (DOUBLE_IS_FINITE (SCM_REAL_VALUE (x)));
   else
     return SCM_BOOL_F;
 }
@@ -3259,37 +6490,15 @@ SCM_DEFINE (scm_integer_p, "integer?", 1, 0, 0,
            "else.")
 #define FUNC_NAME s_scm_integer_p
 {
-  double r;
-  if (SCM_I_INUMP (x))
-    return SCM_BOOL_T;
-  if (SCM_IMP (x))
-    return SCM_BOOL_F;
-  if (SCM_BIGP (x))
-    return SCM_BOOL_T;
-  if (!SCM_INEXACTP (x))
-    return SCM_BOOL_F;
-  if (SCM_COMPLEXP (x))
-    return SCM_BOOL_F;
-  r = SCM_REAL_VALUE (x);
-  /* +/-inf passes r==floor(r), making those #t */
-  if (r == floor (r))
-    return SCM_BOOL_T;
-  return SCM_BOOL_F;
-}
-#undef FUNC_NAME
-
-
-SCM_DEFINE (scm_inexact_p, "inexact?", 1, 0, 0, 
-            (SCM x),
-           "Return @code{#t} if @var{x} is an inexact number, @code{#f}\n"
-           "else.")
-#define FUNC_NAME s_scm_inexact_p
-{
-  if (SCM_INEXACTP (x))
+  if (SCM_I_INUMP (x) || SCM_BIGP (x))
     return SCM_BOOL_T;
-  if (SCM_NUMBERP (x))
+  else if (SCM_REALP (x))
+    {
+      double val = SCM_REAL_VALUE (x);
+      return scm_from_bool (!isinf (val) && (val == floor (val)));
+    }
+  else
     return SCM_BOOL_F;
-  SCM_WRONG_TYPE_ARG (1, x);
 }
 #undef FUNC_NAME
 
@@ -3319,10 +6528,10 @@ scm_num_eq_p (SCM x, SCM y)
  again:
   if (SCM_I_INUMP (x))
     {
-      long xx = SCM_I_INUM (x);
+      scm_t_signed_bits xx = SCM_I_INUM (x);
       if (SCM_I_INUMP (y))
        {
-         long yy = SCM_I_INUM (y);
+         scm_t_signed_bits yy = SCM_I_INUM (y);
          return scm_from_bool (xx == yy);
        }
       else if (SCM_BIGP (y))
@@ -3333,29 +6542,38 @@ scm_num_eq_p (SCM x, SCM y)
              to a double and compare.
 
              But on a 64-bit system an inum is bigger than a double and
-             casting it to a double (call that dxx) will round.  dxx is at
-             worst 1 bigger or smaller than xx, so if dxx==yy we know yy is
-             an integer and fits a long.  So we cast yy to a long and
+             casting it to a double (call that dxx) will round.
+             Although dxx will not in general be equal to xx, dxx will
+             always be an integer and within a factor of 2 of xx, so if
+             dxx==yy, we know that yy is an integer and fits in
+             scm_t_signed_bits.  So we cast yy to scm_t_signed_bits and
              compare with plain xx.
 
              An alternative (for any size system actually) would be to check
              yy is an integer (with floor) and is in range of an inum
              (compare against appropriate powers of 2) then test
-             xx==(long)yy.  It's just a matter of which casts/comparisons
-             might be fastest or easiest for the cpu.  */
+             xx==(scm_t_signed_bits)yy.  It's just a matter of which
+             casts/comparisons might be fastest or easiest for the cpu.  */
 
           double yy = SCM_REAL_VALUE (y);
           return scm_from_bool ((double) xx == yy
                                && (DBL_MANT_DIG >= SCM_I_FIXNUM_BIT-1
-                                   || xx == (long) yy));
+                                   || xx == (scm_t_signed_bits) yy));
         }
       else if (SCM_COMPLEXP (y))
-       return scm_from_bool (((double) xx == SCM_COMPLEX_REAL (y))
-                        && (0.0 == SCM_COMPLEX_IMAG (y)));
+        {
+          /* see comments with inum/real above */
+          double ry = SCM_COMPLEX_REAL (y);
+          return scm_from_bool ((double) xx == ry
+                                && 0.0 == SCM_COMPLEX_IMAG (y)
+                                && (DBL_MANT_DIG >= SCM_I_FIXNUM_BIT-1
+                                    || xx == (scm_t_signed_bits) ry));
+        }
       else if (SCM_FRACTIONP (y))
        return SCM_BOOL_F;
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn, s_scm_i_num_eq_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_eq_p);
     }
   else if (SCM_BIGP (x))
     {
@@ -3390,7 +6608,8 @@ scm_num_eq_p (SCM x, SCM y)
       else if (SCM_FRACTIONP (y))
        return SCM_BOOL_F;
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn, s_scm_i_num_eq_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_eq_p);
     }
   else if (SCM_REALP (x))
     {
@@ -3398,43 +6617,48 @@ scm_num_eq_p (SCM x, SCM y)
       if (SCM_I_INUMP (y))
         {
           /* see comments with inum/real above */
-          long yy = SCM_I_INUM (y);
+          scm_t_signed_bits yy = SCM_I_INUM (y);
           return scm_from_bool (xx == (double) yy
                                && (DBL_MANT_DIG >= SCM_I_FIXNUM_BIT-1
-                                   || (long) xx == yy));
+                                   || (scm_t_signed_bits) xx == yy));
         }
       else if (SCM_BIGP (y))
        {
          int cmp;
-         if (isnan (SCM_REAL_VALUE (x)))
+         if (isnan (xx))
            return SCM_BOOL_F;
-         cmp = xmpz_cmp_d (SCM_I_BIG_MPZ (y), SCM_REAL_VALUE (x));
+         cmp = xmpz_cmp_d (SCM_I_BIG_MPZ (y), xx);
          scm_remember_upto_here_1 (y);
          return scm_from_bool (0 == cmp);
        }
       else if (SCM_REALP (y))
-       return scm_from_bool (SCM_REAL_VALUE (x) == SCM_REAL_VALUE (y));
+       return scm_from_bool (xx == SCM_REAL_VALUE (y));
       else if (SCM_COMPLEXP (y))
-       return scm_from_bool ((SCM_REAL_VALUE (x) == SCM_COMPLEX_REAL (y))
-                        && (0.0 == SCM_COMPLEX_IMAG (y)));
+       return scm_from_bool ((xx == SCM_COMPLEX_REAL (y))
+                              && (0.0 == SCM_COMPLEX_IMAG (y)));
       else if (SCM_FRACTIONP (y))
         {
-          double  xx = SCM_REAL_VALUE (x);
-          if (isnan (xx))
+          if (isnan (xx) || isinf (xx))
             return SCM_BOOL_F;
-          if (isinf (xx))
-            return scm_from_bool (xx < 0.0);
           x = scm_inexact_to_exact (x);  /* with x as frac or int */
           goto again;
         }
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn, s_scm_i_num_eq_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_eq_p);
     }
   else if (SCM_COMPLEXP (x))
     {
       if (SCM_I_INUMP (y))
-       return scm_from_bool ((SCM_COMPLEX_REAL (x) == (double) SCM_I_INUM (y))
-                        && (SCM_COMPLEX_IMAG (x) == 0.0));
+        {
+          /* see comments with inum/real above */
+          double rx = SCM_COMPLEX_REAL (x);
+          scm_t_signed_bits yy = SCM_I_INUM (y);
+          return scm_from_bool (rx == (double) yy
+                                && 0.0 == SCM_COMPLEX_IMAG (x)
+                                && (DBL_MANT_DIG >= SCM_I_FIXNUM_BIT-1
+                                    || (scm_t_signed_bits) rx == yy));
+        }
       else if (SCM_BIGP (y))
        {
          int cmp;
@@ -3448,25 +6672,24 @@ scm_num_eq_p (SCM x, SCM y)
        }
       else if (SCM_REALP (y))
        return scm_from_bool ((SCM_COMPLEX_REAL (x) == SCM_REAL_VALUE (y))
-                        && (SCM_COMPLEX_IMAG (x) == 0.0));
+                              && (SCM_COMPLEX_IMAG (x) == 0.0));
       else if (SCM_COMPLEXP (y))
        return scm_from_bool ((SCM_COMPLEX_REAL (x) == SCM_COMPLEX_REAL (y))
-                        && (SCM_COMPLEX_IMAG (x) == SCM_COMPLEX_IMAG (y)));
+                              && (SCM_COMPLEX_IMAG (x) == SCM_COMPLEX_IMAG (y)));
       else if (SCM_FRACTIONP (y))
         {
           double  xx;
           if (SCM_COMPLEX_IMAG (x) != 0.0)
             return SCM_BOOL_F;
           xx = SCM_COMPLEX_REAL (x);
-          if (isnan (xx))
+          if (isnan (xx) || isinf (xx))
             return SCM_BOOL_F;
-          if (isinf (xx))
-            return scm_from_bool (xx < 0.0);
           x = scm_inexact_to_exact (x);  /* with x as frac or int */
           goto again;
         }
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn, s_scm_i_num_eq_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_eq_p);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -3477,10 +6700,8 @@ scm_num_eq_p (SCM x, SCM y)
       else if (SCM_REALP (y))
         {
           double yy = SCM_REAL_VALUE (y);
-          if (isnan (yy))
+          if (isnan (yy) || isinf (yy))
             return SCM_BOOL_F;
-          if (isinf (yy))
-            return scm_from_bool (0.0 < yy);
           y = scm_inexact_to_exact (y);  /* with y as frac or int */
           goto again;
         }
@@ -3490,20 +6711,20 @@ scm_num_eq_p (SCM x, SCM y)
           if (SCM_COMPLEX_IMAG (y) != 0.0)
             return SCM_BOOL_F;
           yy = SCM_COMPLEX_REAL (y);
-          if (isnan (yy))
+          if (isnan (yy) || isinf(yy))
             return SCM_BOOL_F;
-          if (isinf (yy))
-            return scm_from_bool (0.0 < yy);
           y = scm_inexact_to_exact (y);  /* with y as frac or int */
           goto again;
         }
       else if (SCM_FRACTIONP (y))
        return scm_i_fraction_equalp (x, y);
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn, s_scm_i_num_eq_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_eq_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_eq_p);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_eq_p, x, y, SCM_ARG1, s_scm_i_num_eq_p);
+    return scm_wta_dispatch_2 (g_scm_i_num_eq_p, x, y, SCM_ARG1,
+                               s_scm_i_num_eq_p);
 }
 
 
@@ -3539,10 +6760,10 @@ scm_less_p (SCM x, SCM y)
  again:
   if (SCM_I_INUMP (x))
     {
-      long xx = SCM_I_INUM (x);
+      scm_t_inum xx = SCM_I_INUM (x);
       if (SCM_I_INUMP (y))
        {
-         long yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
          return scm_from_bool (xx < yy);
        }
       else if (SCM_BIGP (y))
@@ -3552,7 +6773,25 @@ scm_less_p (SCM x, SCM y)
          return scm_from_bool (sgn > 0);
        }
       else if (SCM_REALP (y))
-       return scm_from_bool ((double) xx < SCM_REAL_VALUE (y));
+        {
+          /* We can safely take the ceiling of y without changing the
+             result of x<y, given that x is an integer. */
+          double yy = ceil (SCM_REAL_VALUE (y));
+
+          /* In the following comparisons, it's important that the right
+             hand side always be a power of 2, so that it can be
+             losslessly converted to a double even on 64-bit
+             machines. */
+          if (yy >= (double) (SCM_MOST_POSITIVE_FIXNUM+1))
+            return SCM_BOOL_T;
+          else if (!(yy > (double) SCM_MOST_NEGATIVE_FIXNUM))
+            /* The condition above is carefully written to include the
+               case where yy==NaN. */
+            return SCM_BOOL_F;
+          else
+            /* yy is a finite integer that fits in an inum. */
+            return scm_from_bool (xx < (scm_t_inum) yy);
+        }
       else if (SCM_FRACTIONP (y))
         {
           /* "x < a/b" becomes "x*b < a" */
@@ -3562,7 +6801,8 @@ scm_less_p (SCM x, SCM y)
           goto again;
         }
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_less_p, x, y, SCM_ARGn, s_scm_i_num_less_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_less_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_less_p);
     }
   else if (SCM_BIGP (x))
     {
@@ -3590,12 +6830,31 @@ scm_less_p (SCM x, SCM y)
       else if (SCM_FRACTIONP (y))
         goto int_frac;
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_less_p, x, y, SCM_ARGn, s_scm_i_num_less_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_less_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_less_p);
     }
   else if (SCM_REALP (x))
     {
       if (SCM_I_INUMP (y))
-       return scm_from_bool (SCM_REAL_VALUE (x) < (double) SCM_I_INUM (y));
+        {
+          /* We can safely take the floor of x without changing the
+             result of x<y, given that y is an integer. */
+          double xx = floor (SCM_REAL_VALUE (x));
+
+          /* In the following comparisons, it's important that the right
+             hand side always be a power of 2, so that it can be
+             losslessly converted to a double even on 64-bit
+             machines. */
+          if (xx < (double) SCM_MOST_NEGATIVE_FIXNUM)
+            return SCM_BOOL_T;
+          else if (!(xx < (double) (SCM_MOST_POSITIVE_FIXNUM+1)))
+            /* The condition above is carefully written to include the
+               case where xx==NaN. */
+            return SCM_BOOL_F;
+          else
+            /* xx is a finite integer that fits in an inum. */
+            return scm_from_bool ((scm_t_inum) xx < SCM_I_INUM (y));
+        }
       else if (SCM_BIGP (y))
        {
          int cmp;
@@ -3618,7 +6877,8 @@ scm_less_p (SCM x, SCM y)
           goto again;
         }
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_less_p, x, y, SCM_ARGn, s_scm_i_num_less_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_less_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_less_p);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -3651,10 +6911,12 @@ scm_less_p (SCM x, SCM y)
           goto again;
         }
       else
-       SCM_WTA_DISPATCH_2 (g_scm_i_num_less_p, x, y, SCM_ARGn, s_scm_i_num_less_p);
+       return scm_wta_dispatch_2 (g_scm_i_num_less_p, x, y, SCM_ARGn,
+                                   s_scm_i_num_less_p);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_less_p, x, y, SCM_ARG1, s_scm_i_num_less_p);
+    return scm_wta_dispatch_2 (g_scm_i_num_less_p, x, y, SCM_ARG1,
+                               s_scm_i_num_less_p);
 }
 
 
@@ -3683,9 +6945,9 @@ SCM
 scm_gr_p (SCM x, SCM y)
 {
   if (!SCM_NUMBERP (x))
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_gr_p, x, y, SCM_ARG1, FUNC_NAME);
+    return scm_wta_dispatch_2 (g_scm_i_num_gr_p, x, y, SCM_ARG1, FUNC_NAME);
   else if (!SCM_NUMBERP (y))
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_gr_p, x, y, SCM_ARG2, FUNC_NAME);
+    return scm_wta_dispatch_2 (g_scm_i_num_gr_p, x, y, SCM_ARG2, FUNC_NAME);
   else
     return scm_less_p (y, x);
 }
@@ -3717,9 +6979,9 @@ SCM
 scm_leq_p (SCM x, SCM y)
 {
   if (!SCM_NUMBERP (x))
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_leq_p, x, y, SCM_ARG1, FUNC_NAME);
+    return scm_wta_dispatch_2 (g_scm_i_num_leq_p, x, y, SCM_ARG1, FUNC_NAME);
   else if (!SCM_NUMBERP (y))
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_leq_p, x, y, SCM_ARG2, FUNC_NAME);
+    return scm_wta_dispatch_2 (g_scm_i_num_leq_p, x, y, SCM_ARG2, FUNC_NAME);
   else if (scm_is_true (scm_nan_p (x)) || scm_is_true (scm_nan_p (y)))
     return SCM_BOOL_F;
   else
@@ -3753,9 +7015,9 @@ SCM
 scm_geq_p (SCM x, SCM y)
 {
   if (!SCM_NUMBERP (x))
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_geq_p, x, y, SCM_ARG1, FUNC_NAME);
+    return scm_wta_dispatch_2 (g_scm_i_num_geq_p, x, y, SCM_ARG1, FUNC_NAME);
   else if (!SCM_NUMBERP (y))
-    SCM_WTA_DISPATCH_2 (g_scm_i_num_geq_p, x, y, SCM_ARG2, FUNC_NAME);
+    return scm_wta_dispatch_2 (g_scm_i_num_geq_p, x, y, SCM_ARG2, FUNC_NAME);
   else if (scm_is_true (scm_nan_p (x)) || scm_is_true (scm_nan_p (y)))
     return SCM_BOOL_F;
   else
@@ -3764,12 +7026,11 @@ scm_geq_p (SCM x, SCM y)
 #undef FUNC_NAME
 
 
-SCM_GPROC (s_zero_p, "zero?", 1, 0, 0, scm_zero_p, g_zero_p);
-/* "Return @code{#t} if @var{z} is an exact or inexact number equal to\n"
- * "zero."
- */
-SCM
-scm_zero_p (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_zero_p, "zero?", 1, 0, 0,
+                      (SCM z),
+       "Return @code{#t} if @var{z} is an exact or inexact number equal to\n"
+       "zero.")
+#define FUNC_NAME s_scm_zero_p
 {
   if (SCM_I_INUMP (z))
     return scm_from_bool (scm_is_eq (z, SCM_INUM0));
@@ -3783,16 +7044,16 @@ scm_zero_p (SCM z)
   else if (SCM_FRACTIONP (z))
     return SCM_BOOL_F;
   else
-    SCM_WTA_DISPATCH_1 (g_zero_p, z, SCM_ARG1, s_zero_p);
+    return scm_wta_dispatch_1 (g_scm_zero_p, z, SCM_ARG1, s_scm_zero_p);
 }
+#undef FUNC_NAME
 
 
-SCM_GPROC (s_positive_p, "positive?", 1, 0, 0, scm_positive_p, g_positive_p);
-/* "Return @code{#t} if @var{x} is an exact or inexact number greater than\n"
- * "zero."
- */
-SCM
-scm_positive_p (SCM x)
+SCM_PRIMITIVE_GENERIC (scm_positive_p, "positive?", 1, 0, 0,
+                      (SCM x),
+       "Return @code{#t} if @var{x} is an exact or inexact number greater than\n"
+       "zero.")
+#define FUNC_NAME s_scm_positive_p
 {
   if (SCM_I_INUMP (x))
     return scm_from_bool (SCM_I_INUM (x) > 0);
@@ -3807,16 +7068,16 @@ scm_positive_p (SCM x)
   else if (SCM_FRACTIONP (x))
     return scm_positive_p (SCM_FRACTION_NUMERATOR (x));
   else
-    SCM_WTA_DISPATCH_1 (g_positive_p, x, SCM_ARG1, s_positive_p);
+    return scm_wta_dispatch_1 (g_scm_positive_p, x, SCM_ARG1, s_scm_positive_p);
 }
+#undef FUNC_NAME
 
 
-SCM_GPROC (s_negative_p, "negative?", 1, 0, 0, scm_negative_p, g_negative_p);
-/* "Return @code{#t} if @var{x} is an exact or inexact number less than\n"
- * "zero."
- */
-SCM
-scm_negative_p (SCM x)
+SCM_PRIMITIVE_GENERIC (scm_negative_p, "negative?", 1, 0, 0,
+                      (SCM x),
+       "Return @code{#t} if @var{x} is an exact or inexact number less than\n"
+       "zero.")
+#define FUNC_NAME s_scm_negative_p
 {
   if (SCM_I_INUMP (x))
     return scm_from_bool (SCM_I_INUM (x) < 0);
@@ -3831,8 +7092,9 @@ scm_negative_p (SCM x)
   else if (SCM_FRACTIONP (x))
     return scm_negative_p (SCM_FRACTION_NUMERATOR (x));
   else
-    SCM_WTA_DISPATCH_1 (g_negative_p, x, SCM_ARG1, s_negative_p);
+    return scm_wta_dispatch_1 (g_scm_negative_p, x, SCM_ARG1, s_scm_negative_p);
 }
+#undef FUNC_NAME
 
 
 /* scm_min and scm_max return an inexact when either argument is inexact, as
@@ -3864,19 +7126,19 @@ scm_max (SCM x, SCM y)
   if (SCM_UNBNDP (y))
     {
       if (SCM_UNBNDP (x))
-       SCM_WTA_DISPATCH_0 (g_max, s_max);
+       return scm_wta_dispatch_0 (g_max, s_max);
       else if (SCM_I_INUMP(x) || SCM_BIGP(x) || SCM_REALP(x) || SCM_FRACTIONP(x))
        return x;
       else
-       SCM_WTA_DISPATCH_1 (g_max, x, SCM_ARG1, s_max);
+       return scm_wta_dispatch_1 (g_max, x, SCM_ARG1, s_max);
     }
   
   if (SCM_I_INUMP (x))
     {
-      long xx = SCM_I_INUM (x);
+      scm_t_inum xx = SCM_I_INUM (x);
       if (SCM_I_INUMP (y))
        {
-         long yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
          return (xx < yy) ? y : x;
        }
       else if (SCM_BIGP (y))
@@ -3887,9 +7149,19 @@ scm_max (SCM x, SCM y)
        }
       else if (SCM_REALP (y))
        {
-         double z = xx;
-         /* if y==NaN then ">" is false and we return NaN */
-         return (z > SCM_REAL_VALUE (y)) ? scm_from_double (z) : y;
+         double xxd = xx;
+         double yyd = SCM_REAL_VALUE (y);
+
+         if (xxd > yyd)
+           return scm_from_double (xxd);
+         /* If y is a NaN, then "==" is false and we return the NaN */
+         else if (SCM_LIKELY (!(xxd == yyd)))
+           return y;
+         /* Handle signed zeroes properly */
+         else if (xx == 0)
+           return flo0;
+         else
+           return y;
        }
       else if (SCM_FRACTIONP (y))
        {
@@ -3897,7 +7169,7 @@ scm_max (SCM x, SCM y)
           return (scm_is_false (scm_less_p (x, y)) ? x : y);
        }
       else
-       SCM_WTA_DISPATCH_2 (g_max, x, y, SCM_ARGn, s_max);
+       return scm_wta_dispatch_2 (g_max, x, y, SCM_ARGn, s_max);
     }
   else if (SCM_BIGP (x))
     {
@@ -3927,15 +7199,26 @@ scm_max (SCM x, SCM y)
           goto use_less;
        }
       else
-       SCM_WTA_DISPATCH_2 (g_max, x, y, SCM_ARGn, s_max);
+       return scm_wta_dispatch_2 (g_max, x, y, SCM_ARGn, s_max);
     }
   else if (SCM_REALP (x))
     {
       if (SCM_I_INUMP (y))
        {
-         double z = SCM_I_INUM (y);
-         /* if x==NaN then "<" is false and we return NaN */
-         return (SCM_REAL_VALUE (x) < z) ? scm_from_double (z) : x;
+         scm_t_inum yy = SCM_I_INUM (y);
+         double xxd = SCM_REAL_VALUE (x);
+         double yyd = yy;
+
+         if (yyd > xxd)
+           return scm_from_double (yyd);
+         /* If x is a NaN, then "==" is false and we return the NaN */
+         else if (SCM_LIKELY (!(xxd == yyd)))
+           return x;
+         /* Handle signed zeroes properly */
+         else if (yy == 0)
+           return flo0;
+         else
+           return x;
        }
       else if (SCM_BIGP (y))
        {
@@ -3944,12 +7227,25 @@ scm_max (SCM x, SCM y)
        }
       else if (SCM_REALP (y))
        {
-         /* if x==NaN then our explicit check means we return NaN
-            if y==NaN then ">" is false and we return NaN
-            calling isnan is unavoidable, since it's the only way to know
-            which of x or y causes any compares to be false */
          double xx = SCM_REAL_VALUE (x);
-         return (isnan (xx) || xx > SCM_REAL_VALUE (y)) ? x : y;
+         double yy = SCM_REAL_VALUE (y);
+
+         /* For purposes of max: +inf.0 > nan > everything else, per R6RS */
+         if (xx > yy)
+           return x;
+         else if (SCM_LIKELY (xx < yy))
+           return y;
+         /* If neither (xx > yy) nor (xx < yy), then
+            either they're equal or one is a NaN */
+         else if (SCM_UNLIKELY (isnan (xx)))
+           return DOUBLE_IS_POSITIVE_INFINITY (yy) ? y : x;
+         else if (SCM_UNLIKELY (isnan (yy)))
+           return DOUBLE_IS_POSITIVE_INFINITY (xx) ? x : y;
+         /* xx == yy, but handle signed zeroes properly */
+         else if (double_is_non_negative_zero (yy))
+           return y;
+         else
+           return x;
        }
       else if (SCM_FRACTIONP (y))
        {
@@ -3958,7 +7254,7 @@ scm_max (SCM x, SCM y)
          return (xx < yy) ? scm_from_double (yy) : x;
        }
       else
-       SCM_WTA_DISPATCH_2 (g_max, x, y, SCM_ARGn, s_max);
+       return scm_wta_dispatch_2 (g_max, x, y, SCM_ARGn, s_max);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -3973,17 +7269,18 @@ scm_max (SCM x, SCM y)
       else if (SCM_REALP (y))
        {
          double xx = scm_i_fraction2double (x);
-         return (xx < SCM_REAL_VALUE (y)) ? y : scm_from_double (xx);
+         /* if y==NaN then ">" is false, so we return the NaN y */
+         return (xx > SCM_REAL_VALUE (y)) ? scm_from_double (xx) : y;
        }
       else if (SCM_FRACTIONP (y))
        {
           goto use_less;
        }
       else
-       SCM_WTA_DISPATCH_2 (g_max, x, y, SCM_ARGn, s_max);
+       return scm_wta_dispatch_2 (g_max, x, y, SCM_ARGn, s_max);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_max, x, y, SCM_ARG1, s_max);
+    return scm_wta_dispatch_2 (g_max, x, y, SCM_ARG1, s_max);
 }
 
 
@@ -4010,19 +7307,19 @@ scm_min (SCM x, SCM y)
   if (SCM_UNBNDP (y))
     {
       if (SCM_UNBNDP (x))
-       SCM_WTA_DISPATCH_0 (g_min, s_min);
+       return scm_wta_dispatch_0 (g_min, s_min);
       else if (SCM_I_INUMP(x) || SCM_BIGP(x) || SCM_REALP(x) || SCM_FRACTIONP(x))
        return x;
       else
-       SCM_WTA_DISPATCH_1 (g_min, x, SCM_ARG1, s_min);
+       return scm_wta_dispatch_1 (g_min, x, SCM_ARG1, s_min);
     }
   
   if (SCM_I_INUMP (x))
     {
-      long xx = SCM_I_INUM (x);
+      scm_t_inum xx = SCM_I_INUM (x);
       if (SCM_I_INUMP (y))
        {
-         long yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
          return (xx < yy) ? x : y;
        }
       else if (SCM_BIGP (y))
@@ -4043,7 +7340,7 @@ scm_min (SCM x, SCM y)
           return (scm_is_false (scm_less_p (x, y)) ? y : x);
        }
       else
-       SCM_WTA_DISPATCH_2 (g_min, x, y, SCM_ARGn, s_min);
+       return scm_wta_dispatch_2 (g_min, x, y, SCM_ARGn, s_min);
     }
   else if (SCM_BIGP (x))
     {
@@ -4073,7 +7370,7 @@ scm_min (SCM x, SCM y)
           goto use_less;
        }
       else
-       SCM_WTA_DISPATCH_2 (g_min, x, y, SCM_ARGn, s_min);
+       return scm_wta_dispatch_2 (g_min, x, y, SCM_ARGn, s_min);
     }
   else if (SCM_REALP (x))
     {
@@ -4090,12 +7387,25 @@ scm_min (SCM x, SCM y)
        }
       else if (SCM_REALP (y))
        {
-         /* if x==NaN then our explicit check means we return NaN
-            if y==NaN then "<" is false and we return NaN
-            calling isnan is unavoidable, since it's the only way to know
-            which of x or y causes any compares to be false */
          double xx = SCM_REAL_VALUE (x);
-         return (isnan (xx) || xx < SCM_REAL_VALUE (y)) ? x : y;
+         double yy = SCM_REAL_VALUE (y);
+
+         /* For purposes of min: -inf.0 < nan < everything else, per R6RS */
+         if (xx < yy)
+           return x;
+         else if (SCM_LIKELY (xx > yy))
+           return y;
+         /* If neither (xx < yy) nor (xx > yy), then
+            either they're equal or one is a NaN */
+         else if (SCM_UNLIKELY (isnan (xx)))
+           return DOUBLE_IS_NEGATIVE_INFINITY (yy) ? y : x;
+         else if (SCM_UNLIKELY (isnan (yy)))
+           return DOUBLE_IS_NEGATIVE_INFINITY (xx) ? x : y;
+         /* xx == yy, but handle signed zeroes properly */
+         else if (double_is_non_negative_zero (xx))
+           return y;
+         else
+           return x;
        }
       else if (SCM_FRACTIONP (y))
        {
@@ -4104,7 +7414,7 @@ scm_min (SCM x, SCM y)
          return (yy < xx) ? scm_from_double (yy) : x;
        }
       else
-       SCM_WTA_DISPATCH_2 (g_min, x, y, SCM_ARGn, s_min);
+       return scm_wta_dispatch_2 (g_min, x, y, SCM_ARGn, s_min);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -4119,17 +7429,18 @@ scm_min (SCM x, SCM y)
       else if (SCM_REALP (y))
        {
          double xx = scm_i_fraction2double (x);
-         return (SCM_REAL_VALUE (y) < xx) ? y : scm_from_double (xx);
+         /* if y==NaN then "<" is false, so we return the NaN y */
+         return (xx < SCM_REAL_VALUE (y)) ? scm_from_double (xx) : y;
        }
       else if (SCM_FRACTIONP (y))
        {
           goto use_less;
        }
       else
-       SCM_WTA_DISPATCH_2 (g_min, x, y, SCM_ARGn, s_min);
+       return scm_wta_dispatch_2 (g_min, x, y, SCM_ARGn, s_min);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_min, x, y, SCM_ARG1, s_min);
+    return scm_wta_dispatch_2 (g_min, x, y, SCM_ARG1, s_min);
 }
 
 
@@ -4158,17 +7469,17 @@ scm_sum (SCM x, SCM y)
     {
       if (SCM_NUMBERP (x)) return x;
       if (SCM_UNBNDP (x)) return SCM_INUM0;
-      SCM_WTA_DISPATCH_1 (g_sum, x, SCM_ARG1, s_sum);
+      return scm_wta_dispatch_1 (g_sum, x, SCM_ARG1, s_sum);
     }
 
   if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
       if (SCM_LIKELY (SCM_I_INUMP (y)))
         {
-          long xx = SCM_I_INUM (x);
-          long yy = SCM_I_INUM (y);
-          long int z = xx + yy;
-          return SCM_FIXABLE (z) ? SCM_I_MAKINUM (z) : scm_i_long2big (z);
+          scm_t_inum xx = SCM_I_INUM (x);
+          scm_t_inum yy = SCM_I_INUM (y);
+          scm_t_inum z = xx + yy;
+          return SCM_FIXABLE (z) ? SCM_I_MAKINUM (z) : scm_i_inum2big (z);
         }
       else if (SCM_BIGP (y))
         {
@@ -4177,12 +7488,12 @@ scm_sum (SCM x, SCM y)
         }
       else if (SCM_REALP (y))
         {
-          long int xx = SCM_I_INUM (x);
+          scm_t_inum xx = SCM_I_INUM (x);
           return scm_from_double (xx + SCM_REAL_VALUE (y));
         }
       else if (SCM_COMPLEXP (y))
         {
-          long int xx = SCM_I_INUM (x);
+          scm_t_inum xx = SCM_I_INUM (x);
           return scm_c_make_rectangular (xx + SCM_COMPLEX_REAL (y),
                                    SCM_COMPLEX_IMAG (y));
         }
@@ -4191,12 +7502,12 @@ scm_sum (SCM x, SCM y)
                                        scm_product (x, SCM_FRACTION_DENOMINATOR (y))),
                               SCM_FRACTION_DENOMINATOR (y));
       else
-        SCM_WTA_DISPATCH_2 (g_sum, x, y, SCM_ARGn, s_sum);
+        return scm_wta_dispatch_2 (g_sum, x, y, SCM_ARGn, s_sum);
     } else if (SCM_BIGP (x))
       {
        if (SCM_I_INUMP (y))
          {
-           long int inum;
+           scm_t_inum inum;
            int bigsgn;
          add_big_inum:
            inum = SCM_I_INUM (y);      
@@ -4256,7 +7567,7 @@ scm_sum (SCM x, SCM y)
                                          scm_product (x, SCM_FRACTION_DENOMINATOR (y))),
                                 SCM_FRACTION_DENOMINATOR (y));
        else
-         SCM_WTA_DISPATCH_2 (g_sum, x, y, SCM_ARGn, s_sum);
+         return scm_wta_dispatch_2 (g_sum, x, y, SCM_ARGn, s_sum);
       }
   else if (SCM_REALP (x))
     {
@@ -4276,7 +7587,7 @@ scm_sum (SCM x, SCM y)
       else if (SCM_FRACTIONP (y))
        return scm_from_double (SCM_REAL_VALUE (x) + scm_i_fraction2double (y));
       else
-       SCM_WTA_DISPATCH_2 (g_sum, x, y, SCM_ARGn, s_sum);
+       return scm_wta_dispatch_2 (g_sum, x, y, SCM_ARGn, s_sum);
     }
   else if (SCM_COMPLEXP (x))
     {
@@ -4300,7 +7611,7 @@ scm_sum (SCM x, SCM y)
        return scm_c_make_rectangular (SCM_COMPLEX_REAL (x) + scm_i_fraction2double (y),
                                 SCM_COMPLEX_IMAG (x));
       else
-       SCM_WTA_DISPATCH_2 (g_sum, x, y, SCM_ARGn, s_sum);
+       return scm_wta_dispatch_2 (g_sum, x, y, SCM_ARGn, s_sum);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -4323,10 +7634,10 @@ scm_sum (SCM x, SCM y)
                                        scm_product (SCM_FRACTION_NUMERATOR (y), SCM_FRACTION_DENOMINATOR (x))),
                               scm_product (SCM_FRACTION_DENOMINATOR (x), SCM_FRACTION_DENOMINATOR (y)));
       else
-       SCM_WTA_DISPATCH_2 (g_sum, x, y, SCM_ARGn, s_sum);
+       return scm_wta_dispatch_2 (g_sum, x, y, SCM_ARGn, s_sum);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_sum, x, y, SCM_ARG1, s_sum);
+    return scm_wta_dispatch_2 (g_sum, x, y, SCM_ARG1, s_sum);
 }
 
 
@@ -4335,7 +7646,7 @@ SCM_DEFINE (scm_oneplus, "1+", 1, 0, 0,
            "Return @math{@var{x}+1}.")
 #define FUNC_NAME s_scm_oneplus
 {
-  return scm_sum (x, SCM_I_MAKINUM (1));
+  return scm_sum (x, SCM_INUM1);
 }
 #undef FUNC_NAME
 
@@ -4366,15 +7677,15 @@ scm_difference (SCM x, SCM y)
   if (SCM_UNLIKELY (SCM_UNBNDP (y)))
     {
       if (SCM_UNBNDP (x))
-        SCM_WTA_DISPATCH_0 (g_difference, s_difference);
+        return scm_wta_dispatch_0 (g_difference, s_difference);
       else 
         if (SCM_I_INUMP (x))
           {
-            long xx = -SCM_I_INUM (x);
+            scm_t_inum xx = -SCM_I_INUM (x);
             if (SCM_FIXABLE (xx))
               return SCM_I_MAKINUM (xx);
             else
-              return scm_i_long2big (xx);
+              return scm_i_inum2big (xx);
           }
         else if (SCM_BIGP (x))
           /* Must scm_i_normbig here because -SCM_MOST_NEGATIVE_FIXNUM is a
@@ -4386,31 +7697,36 @@ scm_difference (SCM x, SCM y)
           return scm_c_make_rectangular (-SCM_COMPLEX_REAL (x),
                                    -SCM_COMPLEX_IMAG (x));
        else if (SCM_FRACTIONP (x))
-         return scm_i_make_ratio (scm_difference (SCM_FRACTION_NUMERATOR (x), SCM_UNDEFINED),
-                                SCM_FRACTION_DENOMINATOR (x));
+         return scm_i_make_ratio_already_reduced
+           (scm_difference (SCM_FRACTION_NUMERATOR (x), SCM_UNDEFINED),
+            SCM_FRACTION_DENOMINATOR (x));
         else
-          SCM_WTA_DISPATCH_1 (g_difference, x, SCM_ARG1, s_difference);
+          return scm_wta_dispatch_1 (g_difference, x, SCM_ARG1, s_difference);
     }
   
   if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
       if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long int xx = SCM_I_INUM (x);
-         long int yy = SCM_I_INUM (y);
-         long int z = xx - yy;
+         scm_t_inum xx = SCM_I_INUM (x);
+         scm_t_inum yy = SCM_I_INUM (y);
+         scm_t_inum z = xx - yy;
          if (SCM_FIXABLE (z))
            return SCM_I_MAKINUM (z);
          else
-           return scm_i_long2big (z);
+           return scm_i_inum2big (z);
        }
       else if (SCM_BIGP (y))
        {
          /* inum-x - big-y */
-         long xx = SCM_I_INUM (x);
+         scm_t_inum xx = SCM_I_INUM (x);
 
          if (xx == 0)
-           return scm_i_clonebig (y, 0);
+           {
+             /* Must scm_i_normbig here because -SCM_MOST_NEGATIVE_FIXNUM is a
+                bignum, but negating that gives a fixnum.  */
+             return scm_i_normbig (scm_i_clonebig (y, 0));
+           }
          else
            {
              int sgn_y = mpz_sgn (SCM_I_BIG_MPZ (y));
@@ -4435,14 +7751,36 @@ scm_difference (SCM x, SCM y)
        }
       else if (SCM_REALP (y))
        {
-         long int xx = SCM_I_INUM (x);
-         return scm_from_double (xx - SCM_REAL_VALUE (y));
+         scm_t_inum xx = SCM_I_INUM (x);
+
+         /*
+          * We need to handle x == exact 0
+          * specially because R6RS states that:
+          *   (- 0.0)     ==> -0.0  and
+          *   (- 0.0 0.0) ==>  0.0
+          * and the scheme compiler changes
+          *   (- 0.0) into (- 0 0.0)
+          * So we need to treat (- 0 0.0) like (- 0.0).
+          * At the C level, (-x) is different than (0.0 - x).
+          * (0.0 - 0.0) ==> 0.0, but (- 0.0) ==> -0.0.
+          */
+         if (xx == 0)
+           return scm_from_double (- SCM_REAL_VALUE (y));
+         else
+           return scm_from_double (xx - SCM_REAL_VALUE (y));
        }
       else if (SCM_COMPLEXP (y))
        {
-         long int xx = SCM_I_INUM (x);
-         return scm_c_make_rectangular (xx - SCM_COMPLEX_REAL (y),
-                                  - SCM_COMPLEX_IMAG (y));
+         scm_t_inum xx = SCM_I_INUM (x);
+
+         /* We need to handle x == exact 0 specially.
+            See the comment above (for SCM_REALP (y)) */
+         if (xx == 0)
+           return scm_c_make_rectangular (- SCM_COMPLEX_REAL (y),
+                                          - SCM_COMPLEX_IMAG (y));
+         else
+           return scm_c_make_rectangular (xx - SCM_COMPLEX_REAL (y),
+                                             - SCM_COMPLEX_IMAG (y));
        }
       else if (SCM_FRACTIONP (y))
        /* a - b/c = (ac - b) / c */
@@ -4450,20 +7788,20 @@ scm_difference (SCM x, SCM y)
                                               SCM_FRACTION_NUMERATOR (y)),
                               SCM_FRACTION_DENOMINATOR (y));
       else
-       SCM_WTA_DISPATCH_2 (g_difference, x, y, SCM_ARGn, s_difference);
+       return scm_wta_dispatch_2 (g_difference, x, y, SCM_ARGn, s_difference);
     }
   else if (SCM_BIGP (x))
     {
       if (SCM_I_INUMP (y))
        {
          /* big-x - inum-y */
-         long yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
          int sgn_x = mpz_sgn (SCM_I_BIG_MPZ (x));
 
          scm_remember_upto_here_1 (x);
          if (sgn_x == 0)
            return (SCM_FIXABLE (-yy) ?
-                   SCM_I_MAKINUM (-yy) : scm_from_long (-yy));
+                   SCM_I_MAKINUM (-yy) : scm_from_inum (-yy));
          else
            {
              SCM result = scm_i_mkbig ();
@@ -4514,7 +7852,8 @@ scm_difference (SCM x, SCM y)
        return scm_i_make_ratio (scm_difference (scm_product (x, SCM_FRACTION_DENOMINATOR (y)),
                                               SCM_FRACTION_NUMERATOR (y)),
                               SCM_FRACTION_DENOMINATOR (y));
-      else SCM_WTA_DISPATCH_2 (g_difference, x, y, SCM_ARGn, s_difference);
+      else
+        return scm_wta_dispatch_2 (g_difference, x, y, SCM_ARGn, s_difference);
     }
   else if (SCM_REALP (x))
     {
@@ -4534,7 +7873,7 @@ scm_difference (SCM x, SCM y)
       else if (SCM_FRACTIONP (y))
        return scm_from_double (SCM_REAL_VALUE (x) - scm_i_fraction2double (y));
       else
-       SCM_WTA_DISPATCH_2 (g_difference, x, y, SCM_ARGn, s_difference);
+       return scm_wta_dispatch_2 (g_difference, x, y, SCM_ARGn, s_difference);
     }
   else if (SCM_COMPLEXP (x))
     {
@@ -4558,7 +7897,7 @@ scm_difference (SCM x, SCM y)
        return scm_c_make_rectangular (SCM_COMPLEX_REAL (x) - scm_i_fraction2double (y),
                                 SCM_COMPLEX_IMAG (x));
       else
-       SCM_WTA_DISPATCH_2 (g_difference, x, y, SCM_ARGn, s_difference);
+       return scm_wta_dispatch_2 (g_difference, x, y, SCM_ARGn, s_difference);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -4582,10 +7921,10 @@ scm_difference (SCM x, SCM y)
                                               scm_product (SCM_FRACTION_NUMERATOR (y), SCM_FRACTION_DENOMINATOR (x))),
                               scm_product (SCM_FRACTION_DENOMINATOR (x), SCM_FRACTION_DENOMINATOR (y)));
       else
-       SCM_WTA_DISPATCH_2 (g_difference, x, y, SCM_ARGn, s_difference);
+       return scm_wta_dispatch_2 (g_difference, x, y, SCM_ARGn, s_difference);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_difference, x, y, SCM_ARG1, s_difference);
+    return scm_wta_dispatch_2 (g_difference, x, y, SCM_ARG1, s_difference);
 }
 #undef FUNC_NAME
 
@@ -4595,7 +7934,7 @@ SCM_DEFINE (scm_oneminus, "1-", 1, 0, 0,
            "Return @math{@var{x}-1}.")
 #define FUNC_NAME s_scm_oneminus
 {
-  return scm_difference (x, SCM_I_MAKINUM (1));
+  return scm_difference (x, SCM_INUM1);
 }
 #undef FUNC_NAME
 
@@ -4628,32 +7967,70 @@ scm_product (SCM x, SCM y)
       else if (SCM_NUMBERP (x))
        return x;
       else
-       SCM_WTA_DISPATCH_1 (g_product, x, SCM_ARG1, s_product);
+       return scm_wta_dispatch_1 (g_product, x, SCM_ARG1, s_product);
     }
   
   if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
-      long xx;
+      scm_t_inum xx;
 
-    intbig:
+    xinum:
       xx = SCM_I_INUM (x);
 
       switch (xx)
        {
-        case 0: return x; break;
-        case 1: return y; break;
+        case 1:
+         /* exact1 is the universal multiplicative identity */
+         return y;
+         break;
+        case 0:
+         /* exact0 times a fixnum is exact0: optimize this case */
+         if (SCM_LIKELY (SCM_I_INUMP (y)))
+           return SCM_INUM0;
+         /* if the other argument is inexact, the result is inexact,
+            and we must do the multiplication in order to handle
+            infinities and NaNs properly. */
+         else if (SCM_REALP (y))
+           return scm_from_double (0.0 * SCM_REAL_VALUE (y));
+         else if (SCM_COMPLEXP (y))
+           return scm_c_make_rectangular (0.0 * SCM_COMPLEX_REAL (y),
+                                          0.0 * SCM_COMPLEX_IMAG (y));
+         /* we've already handled inexact numbers,
+            so y must be exact, and we return exact0 */
+         else if (SCM_NUMP (y))
+           return SCM_INUM0;
+         else
+           return scm_wta_dispatch_2 (g_product, x, y, SCM_ARGn, s_product);
+         break;
+        case -1:
+         /*
+          * This case is important for more than just optimization.
+          * It handles the case of negating
+          * (+ 1 most-positive-fixnum) aka (- most-negative-fixnum),
+          * which is a bignum that must be changed back into a fixnum.
+          * Failure to do so will cause the following to return #f:
+          * (= most-negative-fixnum (* -1 (- most-negative-fixnum)))
+          */
+         return scm_difference(y, SCM_UNDEFINED);
+         break;
        }
 
       if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long yy = SCM_I_INUM (y);
-         long kk = xx * yy;
-         SCM k = SCM_I_MAKINUM (kk);
-         if ((kk == SCM_I_INUM (k)) && (kk / xx == yy))
-           return k;
+         scm_t_inum yy = SCM_I_INUM (y);
+#if SCM_I_FIXNUM_BIT < 32 && SCM_HAVE_T_INT64
+          scm_t_int64 kk = xx * (scm_t_int64) yy;
+          if (SCM_FIXABLE (kk))
+            return SCM_I_MAKINUM (kk);
+#else
+          scm_t_inum axx = (xx > 0) ? xx : -xx;
+          scm_t_inum ayy = (yy > 0) ? yy : -yy;
+          if (SCM_MOST_POSITIVE_FIXNUM / axx >= ayy)
+            return SCM_I_MAKINUM (xx * yy);
+#endif
          else
            {
-             SCM result = scm_i_long2big (xx);
+             SCM result = scm_i_inum2big (xx);
              mpz_mul_si (SCM_I_BIG_MPZ (result), SCM_I_BIG_MPZ (result), yy);
              return scm_i_normbig (result);
            }
@@ -4674,14 +8051,14 @@ scm_product (SCM x, SCM y)
        return scm_i_make_ratio (scm_product (x, SCM_FRACTION_NUMERATOR (y)),
                               SCM_FRACTION_DENOMINATOR (y));
       else
-       SCM_WTA_DISPATCH_2 (g_product, x, y, SCM_ARGn, s_product);
+       return scm_wta_dispatch_2 (g_product, x, y, SCM_ARGn, s_product);
     }
   else if (SCM_BIGP (x))
     {
       if (SCM_I_INUMP (y))
        {
          SCM_SWAP (x, y);
-         goto intbig;
+         goto xinum;
        }
       else if (SCM_BIGP (y))
        {
@@ -4709,17 +8086,15 @@ scm_product (SCM x, SCM y)
        return scm_i_make_ratio (scm_product (x, SCM_FRACTION_NUMERATOR (y)),
                               SCM_FRACTION_DENOMINATOR (y));
       else
-       SCM_WTA_DISPATCH_2 (g_product, x, y, SCM_ARGn, s_product);
+       return scm_wta_dispatch_2 (g_product, x, y, SCM_ARGn, s_product);
     }
   else if (SCM_REALP (x))
     {
       if (SCM_I_INUMP (y))
-        {
-          /* inexact*exact0 => exact 0, per R5RS "Exactness" section */
-          if (scm_is_eq (y, SCM_INUM0))
-            return y;
-          return scm_from_double (SCM_I_INUM (y) * SCM_REAL_VALUE (x));
-        }
+       {
+         SCM_SWAP (x, y);
+         goto xinum;
+       }
       else if (SCM_BIGP (y))
        {
          double result = mpz_get_d (SCM_I_BIG_MPZ (y)) * SCM_REAL_VALUE (x);
@@ -4734,18 +8109,15 @@ scm_product (SCM x, SCM y)
       else if (SCM_FRACTIONP (y))
        return scm_from_double (SCM_REAL_VALUE (x) * scm_i_fraction2double (y));
       else
-       SCM_WTA_DISPATCH_2 (g_product, x, y, SCM_ARGn, s_product);
+       return scm_wta_dispatch_2 (g_product, x, y, SCM_ARGn, s_product);
     }
   else if (SCM_COMPLEXP (x))
     {
       if (SCM_I_INUMP (y))
-        {
-          /* inexact*exact0 => exact 0, per R5RS "Exactness" section */
-          if (scm_is_eq (y, SCM_INUM0))
-            return y;
-          return scm_c_make_rectangular (SCM_I_INUM (y) * SCM_COMPLEX_REAL (x),
-                                         SCM_I_INUM (y) * SCM_COMPLEX_IMAG (x));
-        }
+       {
+         SCM_SWAP (x, y);
+         goto xinum;
+       }
       else if (SCM_BIGP (y))
        {
          double z = mpz_get_d (SCM_I_BIG_MPZ (y));
@@ -4770,7 +8142,7 @@ scm_product (SCM x, SCM y)
                                   yy * SCM_COMPLEX_IMAG (x));
        }
       else
-       SCM_WTA_DISPATCH_2 (g_product, x, y, SCM_ARGn, s_product);
+       return scm_wta_dispatch_2 (g_product, x, y, SCM_ARGn, s_product);
     }
   else if (SCM_FRACTIONP (x))
     {
@@ -4795,10 +8167,10 @@ scm_product (SCM x, SCM y)
                               scm_product (SCM_FRACTION_DENOMINATOR (x),
                                            SCM_FRACTION_DENOMINATOR (y)));
       else
-       SCM_WTA_DISPATCH_2 (g_product, x, y, SCM_ARGn, s_product);
+       return scm_wta_dispatch_2 (g_product, x, y, SCM_ARGn, s_product);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_product, x, y, SCM_ARG1, s_product);
+    return scm_wta_dispatch_2 (g_product, x, y, SCM_ARG1, s_product);
 }
 
 #if ((defined (HAVE_ISINF) && defined (HAVE_ISNAN)) \
@@ -4853,8 +8225,8 @@ SCM_PRIMITIVE_GENERIC (scm_i_divide, "/", 0, 2, 1,
 #define s_divide s_scm_i_divide
 #define g_divide g_scm_i_divide
 
-static SCM
-do_divide (SCM x, SCM y, int inexact)
+SCM
+scm_divide (SCM x, SCM y)
 #define FUNC_NAME s_divide
 {
   double a;
@@ -4862,10 +8234,10 @@ do_divide (SCM x, SCM y, int inexact)
   if (SCM_UNLIKELY (SCM_UNBNDP (y)))
     {
       if (SCM_UNBNDP (x))
-       SCM_WTA_DISPATCH_0 (g_divide, s_divide);
+       return scm_wta_dispatch_0 (g_divide, s_divide);
       else if (SCM_I_INUMP (x))
        {
-         long xx = SCM_I_INUM (x);
+         scm_t_inum xx = SCM_I_INUM (x);
          if (xx == 1 || xx == -1)
            return x;
 #ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
@@ -4873,18 +8245,10 @@ do_divide (SCM x, SCM y, int inexact)
            scm_num_overflow (s_divide);
 #endif
          else
-           {
-             if (inexact)
-               return scm_from_double (1.0 / (double) xx);
-             else return scm_i_make_ratio (SCM_I_MAKINUM(1), x);
-           }
+           return scm_i_make_ratio_already_reduced (SCM_INUM1, x);
        }
       else if (SCM_BIGP (x))
-       {
-         if (inexact)
-           return scm_from_double (1.0 / scm_i_big2dbl (x));
-         else return scm_i_make_ratio (SCM_I_MAKINUM(1), x);
-       }
+       return scm_i_make_ratio_already_reduced (SCM_INUM1, x);
       else if (SCM_REALP (x))
        {
          double xx = SCM_REAL_VALUE (x);
@@ -4913,18 +8277,18 @@ do_divide (SCM x, SCM y, int inexact)
            }
        }
       else if (SCM_FRACTIONP (x))
-       return scm_i_make_ratio (SCM_FRACTION_DENOMINATOR (x),
-                              SCM_FRACTION_NUMERATOR (x));
+       return scm_i_make_ratio_already_reduced (SCM_FRACTION_DENOMINATOR (x),
+                                                SCM_FRACTION_NUMERATOR (x));
       else
-       SCM_WTA_DISPATCH_1 (g_divide, x, SCM_ARG1, s_divide);
+       return scm_wta_dispatch_1 (g_divide, x, SCM_ARG1, s_divide);
     }
 
   if (SCM_LIKELY (SCM_I_INUMP (x)))
     {
-      long xx = SCM_I_INUM (x);
+      scm_t_inum xx = SCM_I_INUM (x);
       if (SCM_LIKELY (SCM_I_INUMP (y)))
        {
-         long yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
          if (yy == 0)
            {
 #ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
@@ -4934,26 +8298,18 @@ do_divide (SCM x, SCM y, int inexact)
 #endif
            }
          else if (xx % yy != 0)
-           {
-             if (inexact)
-               return scm_from_double ((double) xx / (double) yy);
-             else return scm_i_make_ratio (x, y);
-           }
+           return scm_i_make_ratio (x, y);
          else
            {
-             long z = xx / yy;
+             scm_t_inum z = xx / yy;
              if (SCM_FIXABLE (z))
                return SCM_I_MAKINUM (z);
              else
-               return scm_i_long2big (z);
+               return scm_i_inum2big (z);
            }
        }
       else if (SCM_BIGP (y))
-       {
-         if (inexact)
-           return scm_from_double ((double) xx / scm_i_big2dbl (y));
-         else return scm_i_make_ratio (x, y);
-       }
+       return scm_i_make_ratio (x, y);
       else if (SCM_REALP (y))
        {
          double yy = SCM_REAL_VALUE (y);
@@ -4962,6 +8318,9 @@ do_divide (SCM x, SCM y, int inexact)
            scm_num_overflow (s_divide);
          else
 #endif
+            /* FIXME: Precision may be lost here due to:
+               (1) The cast from 'scm_t_inum' to 'double'
+               (2) Double rounding */
            return scm_from_double ((double) xx / yy);
        }
       else if (SCM_COMPLEXP (y))
@@ -4988,15 +8347,15 @@ do_divide (SCM x, SCM y, int inexact)
       else if (SCM_FRACTIONP (y))
        /* a / b/c = ac / b */
        return scm_i_make_ratio (scm_product (x, SCM_FRACTION_DENOMINATOR (y)),
-                              SCM_FRACTION_NUMERATOR (y));
+                                 SCM_FRACTION_NUMERATOR (y));
       else
-       SCM_WTA_DISPATCH_2 (g_divide, x, y, SCM_ARGn, s_divide);
+       return scm_wta_dispatch_2 (g_divide, x, y, SCM_ARGn, s_divide);
     }
   else if (SCM_BIGP (x))
     {
       if (SCM_I_INUMP (y))
        {
-         long int yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
          if (yy == 0)
            {
 #ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
@@ -5019,7 +8378,7 @@ do_divide (SCM x, SCM y, int inexact)
                 middle ground: test, then if divisible, use the faster div
                 func. */
 
-             long abs_yy = yy < 0 ? -yy : yy;
+             scm_t_inum abs_yy = yy < 0 ? -yy : yy;
              int divisible_p = mpz_divisible_ui_p (SCM_I_BIG_MPZ (x), abs_yy);
 
              if (divisible_p)
@@ -5032,57 +8391,24 @@ do_divide (SCM x, SCM y, int inexact)
                  return scm_i_normbig (result);
                }
              else
-               {
-                 if (inexact)
-                   return scm_from_double (scm_i_big2dbl (x) / (double) yy);
-                 else return scm_i_make_ratio (x, y);
-               }
+               return scm_i_make_ratio (x, y);
            }
        }
       else if (SCM_BIGP (y))
        {
-         int y_is_zero = (mpz_sgn (SCM_I_BIG_MPZ (y)) == 0);
-         if (y_is_zero)
-           {
-#ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
-             scm_num_overflow (s_divide);
-#else
-             int sgn = mpz_sgn (SCM_I_BIG_MPZ (x));
-             scm_remember_upto_here_1 (x);
-             return (sgn == 0) ? scm_nan () : scm_inf ();
-#endif
-           }
-         else
-           {
-             /* big_x / big_y */
-              if (inexact)
-                {
-                  /* It's easily possible for the ratio x/y to fit a double
-                     but one or both x and y be too big to fit a double,
-                     hence the use of mpq_get_d rather than converting and
-                     dividing.  */
-                  mpq_t q;
-                  *mpq_numref(q) = *SCM_I_BIG_MPZ (x);
-                  *mpq_denref(q) = *SCM_I_BIG_MPZ (y);
-                  return scm_from_double (mpq_get_d (q));
-                }
-              else
-                {
-                  int divisible_p = mpz_divisible_p (SCM_I_BIG_MPZ (x),
-                                                     SCM_I_BIG_MPZ (y));
-                  if (divisible_p)
-                    {
-                      SCM result = scm_i_mkbig ();
-                      mpz_divexact (SCM_I_BIG_MPZ (result),
-                                    SCM_I_BIG_MPZ (x),
-                                    SCM_I_BIG_MPZ (y));
-                      scm_remember_upto_here_2 (x, y);
-                      return scm_i_normbig (result);
-                    }
-                  else
-                    return scm_i_make_ratio (x, y);
-                }
-           }
+          int divisible_p = mpz_divisible_p (SCM_I_BIG_MPZ (x),
+                                             SCM_I_BIG_MPZ (y));
+          if (divisible_p)
+            {
+              SCM result = scm_i_mkbig ();
+              mpz_divexact (SCM_I_BIG_MPZ (result),
+                            SCM_I_BIG_MPZ (x),
+                            SCM_I_BIG_MPZ (y));
+              scm_remember_upto_here_2 (x, y);
+              return scm_i_normbig (result);
+            }
+          else
+            return scm_i_make_ratio (x, y);
        }
       else if (SCM_REALP (y))
        {
@@ -5092,6 +8418,8 @@ do_divide (SCM x, SCM y, int inexact)
            scm_num_overflow (s_divide);
          else
 #endif
+            /* FIXME: Precision may be lost here due to:
+               (1) scm_i_big2dbl (2) Double rounding */
            return scm_from_double (scm_i_big2dbl (x) / yy);
        }
       else if (SCM_COMPLEXP (y))
@@ -5101,25 +8429,31 @@ do_divide (SCM x, SCM y, int inexact)
        }
       else if (SCM_FRACTIONP (y))
        return scm_i_make_ratio (scm_product (x, SCM_FRACTION_DENOMINATOR (y)),
-                              SCM_FRACTION_NUMERATOR (y));
+                                 SCM_FRACTION_NUMERATOR (y));
       else
-       SCM_WTA_DISPATCH_2 (g_divide, x, y, SCM_ARGn, s_divide);
+       return scm_wta_dispatch_2 (g_divide, x, y, SCM_ARGn, s_divide);
     }
   else if (SCM_REALP (x))
     {
       double rx = SCM_REAL_VALUE (x);
       if (SCM_I_INUMP (y))
        {
-         long int yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
 #ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
          if (yy == 0)
            scm_num_overflow (s_divide);
          else
 #endif
+            /* FIXME: Precision may be lost here due to:
+               (1) The cast from 'scm_t_inum' to 'double'
+               (2) Double rounding */
            return scm_from_double (rx / (double) yy);
        }
       else if (SCM_BIGP (y))
        {
+          /* FIXME: Precision may be lost here due to:
+             (1) The conversion from bignum to double
+             (2) Double rounding */
          double dby = mpz_get_d (SCM_I_BIG_MPZ (y));
          scm_remember_upto_here_1 (y);
          return scm_from_double (rx / dby);
@@ -5142,7 +8476,7 @@ do_divide (SCM x, SCM y, int inexact)
       else if (SCM_FRACTIONP (y))
        return scm_from_double (rx / scm_i_fraction2double (y));
       else
-       SCM_WTA_DISPATCH_2 (g_divide, x, y, SCM_ARGn, s_divide);
+       return scm_wta_dispatch_2 (g_divide, x, y, SCM_ARGn, s_divide);
     }
   else if (SCM_COMPLEXP (x))
     {
@@ -5150,19 +8484,25 @@ do_divide (SCM x, SCM y, int inexact)
       double ix = SCM_COMPLEX_IMAG (x);
       if (SCM_I_INUMP (y))
        {
-         long int yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
 #ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
          if (yy == 0)
            scm_num_overflow (s_divide);
          else
 #endif
            {
+              /* FIXME: Precision may be lost here due to:
+                 (1) The conversion from 'scm_t_inum' to double
+                 (2) Double rounding */
              double d = yy;
              return scm_c_make_rectangular (rx / d, ix / d);
            }
        }
       else if (SCM_BIGP (y))
        {
+          /* FIXME: Precision may be lost here due to:
+             (1) The conversion from bignum to double
+             (2) Double rounding */
          double dby = mpz_get_d (SCM_I_BIG_MPZ (y));
          scm_remember_upto_here_1 (y);
          return scm_c_make_rectangular (rx / dby, ix / dby);
@@ -5196,29 +8536,32 @@ do_divide (SCM x, SCM y, int inexact)
        }
       else if (SCM_FRACTIONP (y))
        {
+          /* FIXME: Precision may be lost here due to:
+             (1) The conversion from fraction to double
+             (2) Double rounding */
          double yy = scm_i_fraction2double (y);
          return scm_c_make_rectangular (rx / yy, ix / yy);
        }
       else
-       SCM_WTA_DISPATCH_2 (g_divide, x, y, SCM_ARGn, s_divide);
+       return scm_wta_dispatch_2 (g_divide, x, y, SCM_ARGn, s_divide);
     }
   else if (SCM_FRACTIONP (x))
     {
       if (SCM_I_INUMP (y)) 
        {
-         long int yy = SCM_I_INUM (y);
+         scm_t_inum yy = SCM_I_INUM (y);
 #ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
          if (yy == 0)
            scm_num_overflow (s_divide);
          else
 #endif
            return scm_i_make_ratio (SCM_FRACTION_NUMERATOR (x),
-                                  scm_product (SCM_FRACTION_DENOMINATOR (x), y));
+                                     scm_product (SCM_FRACTION_DENOMINATOR (x), y));
        } 
       else if (SCM_BIGP (y)) 
        {
          return scm_i_make_ratio (SCM_FRACTION_NUMERATOR (x),
-                                scm_product (SCM_FRACTION_DENOMINATOR (x), y));
+                                   scm_product (SCM_FRACTION_DENOMINATOR (x), y));
        } 
       else if (SCM_REALP (y)) 
        {
@@ -5228,32 +8571,27 @@ do_divide (SCM x, SCM y, int inexact)
            scm_num_overflow (s_divide);
          else
 #endif
+            /* FIXME: Precision may be lost here due to:
+               (1) The conversion from fraction to double
+               (2) Double rounding */
            return scm_from_double (scm_i_fraction2double (x) / yy);
        }
       else if (SCM_COMPLEXP (y)) 
        {
+          /* FIXME: Precision may be lost here due to:
+             (1) The conversion from fraction to double
+             (2) Double rounding */
          a = scm_i_fraction2double (x);
          goto complex_div;
        } 
       else if (SCM_FRACTIONP (y))
        return scm_i_make_ratio (scm_product (SCM_FRACTION_NUMERATOR (x), SCM_FRACTION_DENOMINATOR (y)),
-                              scm_product (SCM_FRACTION_NUMERATOR (y), SCM_FRACTION_DENOMINATOR (x)));
+                                 scm_product (SCM_FRACTION_NUMERATOR (y), SCM_FRACTION_DENOMINATOR (x)));
       else 
-       SCM_WTA_DISPATCH_2 (g_divide, x, y, SCM_ARGn, s_divide);
+       return scm_wta_dispatch_2 (g_divide, x, y, SCM_ARGn, s_divide);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_divide, x, y, SCM_ARG1, s_divide);
-}
-
-SCM
-scm_divide (SCM x, SCM y)
-{
-  return do_divide (x, y, 0);
-}
-
-static SCM scm_divide2real (SCM x, SCM y)
-{
-  return do_divide (x, y, 1);
+    return scm_wta_dispatch_2 (g_divide, x, y, SCM_ARG1, s_divide);
 }
 #undef FUNC_NAME
 
@@ -5261,13 +8599,7 @@ static SCM scm_divide2real (SCM x, SCM y)
 double
 scm_c_truncate (double x)
 {
-#if HAVE_TRUNC
   return trunc (x);
-#else
-  if (x < 0.0)
-    return -floor (-x);
-  return floor (x);
-#endif
 }
 
 /* scm_c_round is done using floor(x+0.5) to round to nearest and with
@@ -5312,45 +8644,41 @@ scm_c_round (double x)
          : result);
 }
 
-SCM_DEFINE (scm_truncate_number, "truncate", 1, 0, 0,
-           (SCM x),
-           "Round the number @var{x} towards zero.")
+SCM_PRIMITIVE_GENERIC (scm_truncate_number, "truncate", 1, 0, 0,
+                      (SCM x),
+                      "Round the number @var{x} towards zero.")
 #define FUNC_NAME s_scm_truncate_number
 {
-  if (scm_is_false (scm_negative_p (x)))
-    return scm_floor (x);
+  if (SCM_I_INUMP (x) || SCM_BIGP (x))
+    return x;
+  else if (SCM_REALP (x))
+    return scm_from_double (trunc (SCM_REAL_VALUE (x)));
+  else if (SCM_FRACTIONP (x))
+    return scm_truncate_quotient (SCM_FRACTION_NUMERATOR (x),
+                                 SCM_FRACTION_DENOMINATOR (x));
   else
-    return scm_ceiling (x);
+    return scm_wta_dispatch_1 (g_scm_truncate_number, x, SCM_ARG1,
+                       s_scm_truncate_number);
 }
 #undef FUNC_NAME
 
-static SCM exactly_one_half;
-
-SCM_DEFINE (scm_round_number, "round", 1, 0, 0,
-           (SCM x),
-           "Round the number @var{x} towards the nearest integer. "
-           "When it is exactly halfway between two integers, "
-           "round towards the even one.")
+SCM_PRIMITIVE_GENERIC (scm_round_number, "round", 1, 0, 0,
+                      (SCM x),
+                      "Round the number @var{x} towards the nearest integer. "
+                      "When it is exactly halfway between two integers, "
+                      "round towards the even one.")
 #define FUNC_NAME s_scm_round_number
 {
   if (SCM_I_INUMP (x) || SCM_BIGP (x))
     return x;
   else if (SCM_REALP (x))
     return scm_from_double (scm_c_round (SCM_REAL_VALUE (x)));
+  else if (SCM_FRACTIONP (x))
+    return scm_round_quotient (SCM_FRACTION_NUMERATOR (x),
+                              SCM_FRACTION_DENOMINATOR (x));
   else
-    {
-      /* OPTIMIZE-ME: Fraction case could be done more efficiently by a
-         single quotient+remainder division then examining to see which way
-         the rounding should go.  */
-      SCM plus_half = scm_sum (x, exactly_one_half);
-      SCM result = scm_floor (plus_half);
-      /* Adjust so that the rounding is towards even.  */
-      if (scm_is_true (scm_num_eq_p (plus_half, result))
-          && scm_is_true (scm_odd_p (result)))
-        return scm_difference (result, SCM_I_MAKINUM (1));
-      else
-        return result;
-    }
+    return scm_wta_dispatch_1 (g_scm_round_number, x, SCM_ARG1,
+                               s_scm_round_number);
 }
 #undef FUNC_NAME
 
@@ -5364,24 +8692,10 @@ SCM_PRIMITIVE_GENERIC (scm_floor, "floor", 1, 0, 0,
   else if (SCM_REALP (x))
     return scm_from_double (floor (SCM_REAL_VALUE (x)));
   else if (SCM_FRACTIONP (x))
-    {
-      SCM q = scm_quotient (SCM_FRACTION_NUMERATOR (x),
-                           SCM_FRACTION_DENOMINATOR (x));
-      if (scm_is_false (scm_negative_p (x)))
-       {
-         /* For positive x, rounding towards zero is correct. */
-         return q;
-       }
-      else
-       {
-         /* For negative x, we need to return q-1 unless x is an
-            integer.  But fractions are never integer, per our
-            assumptions. */
-         return scm_difference (q, SCM_I_MAKINUM (1));
-       }
-    }
+    return scm_floor_quotient (SCM_FRACTION_NUMERATOR (x),
+                              SCM_FRACTION_DENOMINATOR (x));
   else
-    SCM_WTA_DISPATCH_1 (g_scm_floor, x, 1, s_scm_floor);
+    return scm_wta_dispatch_1 (g_scm_floor, x, 1, s_scm_floor);
 }  
 #undef FUNC_NAME
 
@@ -5395,24 +8709,51 @@ SCM_PRIMITIVE_GENERIC (scm_ceiling, "ceiling", 1, 0, 0,
   else if (SCM_REALP (x))
     return scm_from_double (ceil (SCM_REAL_VALUE (x)));
   else if (SCM_FRACTIONP (x))
+    return scm_ceiling_quotient (SCM_FRACTION_NUMERATOR (x),
+                                SCM_FRACTION_DENOMINATOR (x));
+  else
+    return scm_wta_dispatch_1 (g_scm_ceiling, x, 1, s_scm_ceiling);
+}
+#undef FUNC_NAME
+
+SCM_PRIMITIVE_GENERIC (scm_expt, "expt", 2, 0, 0,
+                      (SCM x, SCM y),
+                      "Return @var{x} raised to the power of @var{y}.")
+#define FUNC_NAME s_scm_expt
+{
+  if (scm_is_integer (y))
     {
-      SCM q = scm_quotient (SCM_FRACTION_NUMERATOR (x),
-                           SCM_FRACTION_DENOMINATOR (x));
-      if (scm_is_false (scm_positive_p (x)))
-       {
-         /* For negative x, rounding towards zero is correct. */
-         return q;
-       }
+      if (scm_is_true (scm_exact_p (y)))
+       return scm_integer_expt (x, y);
       else
        {
-         /* For positive x, we need to return q+1 unless x is an
-            integer.  But fractions are never integer, per our
-            assumptions. */
-         return scm_sum (q, SCM_I_MAKINUM (1));
+         /* Here we handle the case where the exponent is an inexact
+            integer.  We make the exponent exact in order to use
+            scm_integer_expt, and thus avoid the spurious imaginary
+            parts that may result from round-off errors in the general
+            e^(y log x) method below (for example when squaring a large
+            negative number).  In this case, we must return an inexact
+            result for correctness.  We also make the base inexact so
+            that scm_integer_expt will use fast inexact arithmetic
+            internally.  Note that making the base inexact is not
+            sufficient to guarantee an inexact result, because
+            scm_integer_expt will return an exact 1 when the exponent
+            is 0, even if the base is inexact. */
+         return scm_exact_to_inexact
+           (scm_integer_expt (scm_exact_to_inexact (x),
+                              scm_inexact_to_exact (y)));
        }
     }
+  else if (scm_is_real (x) && scm_is_real (y) && scm_to_double (x) >= 0.0)
+    {
+      return scm_from_double (pow (scm_to_double (x), scm_to_double (y)));
+    }
+  else if (scm_is_complex (x) && scm_is_complex (y))
+    return scm_exp (scm_product (scm_log (x), y));
+  else if (scm_is_complex (x))
+    return scm_wta_dispatch_2 (g_scm_expt, x, y, SCM_ARG2, s_scm_expt);
   else
-    SCM_WTA_DISPATCH_1 (g_scm_ceiling, x, 1, s_scm_ceiling);
+    return scm_wta_dispatch_2 (g_scm_expt, x, y, SCM_ARG1, s_scm_expt);
 }
 #undef FUNC_NAME
 
@@ -5422,28 +8763,14 @@ SCM_PRIMITIVE_GENERIC (scm_ceiling, "ceiling", 1, 0, 0,
    Written by Jerry D. Hedden, (C) FSF.
    See the file `COPYING' for terms applying to this program. */
 
-SCM_DEFINE (scm_expt, "expt", 2, 0, 0,
-            (SCM x, SCM y),
-           "Return @var{x} raised to the power of @var{y}.") 
-#define FUNC_NAME s_scm_expt
-{
-  if (scm_is_true (scm_exact_p (x)) && scm_is_integer (y))
-    return scm_integer_expt (x, y);
-  else if (scm_is_real (x) && scm_is_real (y) && scm_to_double (x) >= 0.0)
-    {
-      return scm_from_double (pow (scm_to_double (x), scm_to_double (y)));
-    }
-  else
-    return scm_exp (scm_product (scm_log (x), y));
-}
-#undef FUNC_NAME
-
 SCM_PRIMITIVE_GENERIC (scm_sin, "sin", 1, 0, 0,
                        (SCM z),
                        "Compute the sine of @var{z}.")
 #define FUNC_NAME s_scm_sin
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* sin(exact0) = exact0 */
+  else if (scm_is_real (z))
     return scm_from_double (sin (scm_to_double (z)));
   else if (SCM_COMPLEXP (z))
     { double x, y;
@@ -5453,7 +8780,7 @@ SCM_PRIMITIVE_GENERIC (scm_sin, "sin", 1, 0, 0,
                                      cos (x) * sinh (y));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_sin, z, 1, s_scm_sin);
+    return scm_wta_dispatch_1 (g_scm_sin, z, 1, s_scm_sin);
 }
 #undef FUNC_NAME
 
@@ -5462,7 +8789,9 @@ SCM_PRIMITIVE_GENERIC (scm_cos, "cos", 1, 0, 0,
                        "Compute the cosine of @var{z}.")
 #define FUNC_NAME s_scm_cos
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return SCM_INUM1;  /* cos(exact0) = exact1 */
+  else if (scm_is_real (z))
     return scm_from_double (cos (scm_to_double (z)));
   else if (SCM_COMPLEXP (z))
     { double x, y;
@@ -5472,7 +8801,7 @@ SCM_PRIMITIVE_GENERIC (scm_cos, "cos", 1, 0, 0,
                                      -sin (x) * sinh (y));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_cos, z, 1, s_scm_cos);
+    return scm_wta_dispatch_1 (g_scm_cos, z, 1, s_scm_cos);
 }
 #undef FUNC_NAME
 
@@ -5481,7 +8810,9 @@ SCM_PRIMITIVE_GENERIC (scm_tan, "tan", 1, 0, 0,
                        "Compute the tangent of @var{z}.")
 #define FUNC_NAME s_scm_tan
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* tan(exact0) = exact0 */
+  else if (scm_is_real (z))
     return scm_from_double (tan (scm_to_double (z)));
   else if (SCM_COMPLEXP (z))
     { double x, y, w;
@@ -5495,7 +8826,7 @@ SCM_PRIMITIVE_GENERIC (scm_tan, "tan", 1, 0, 0,
       return scm_c_make_rectangular (sin (x) / w, sinh (y) / w);
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_tan, z, 1, s_scm_tan);
+    return scm_wta_dispatch_1 (g_scm_tan, z, 1, s_scm_tan);
 }
 #undef FUNC_NAME
 
@@ -5504,7 +8835,9 @@ SCM_PRIMITIVE_GENERIC (scm_sinh, "sinh", 1, 0, 0,
                        "Compute the hyperbolic sine of @var{z}.")
 #define FUNC_NAME s_scm_sinh
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* sinh(exact0) = exact0 */
+  else if (scm_is_real (z))
     return scm_from_double (sinh (scm_to_double (z)));
   else if (SCM_COMPLEXP (z))
     { double x, y;
@@ -5514,7 +8847,7 @@ SCM_PRIMITIVE_GENERIC (scm_sinh, "sinh", 1, 0, 0,
                                      cosh (x) * sin (y));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_sinh, z, 1, s_scm_sinh);
+    return scm_wta_dispatch_1 (g_scm_sinh, z, 1, s_scm_sinh);
 }
 #undef FUNC_NAME
 
@@ -5523,7 +8856,9 @@ SCM_PRIMITIVE_GENERIC (scm_cosh, "cosh", 1, 0, 0,
                        "Compute the hyperbolic cosine of @var{z}.")
 #define FUNC_NAME s_scm_cosh
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return SCM_INUM1;  /* cosh(exact0) = exact1 */
+  else if (scm_is_real (z))
     return scm_from_double (cosh (scm_to_double (z)));
   else if (SCM_COMPLEXP (z))
     { double x, y;
@@ -5533,7 +8868,7 @@ SCM_PRIMITIVE_GENERIC (scm_cosh, "cosh", 1, 0, 0,
                                      sinh (x) * sin (y));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_cosh, z, 1, s_scm_cosh);
+    return scm_wta_dispatch_1 (g_scm_cosh, z, 1, s_scm_cosh);
 }
 #undef FUNC_NAME
 
@@ -5542,7 +8877,9 @@ SCM_PRIMITIVE_GENERIC (scm_tanh, "tanh", 1, 0, 0,
                        "Compute the hyperbolic tangent of @var{z}.")
 #define FUNC_NAME s_scm_tanh
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* tanh(exact0) = exact0 */
+  else if (scm_is_real (z))
     return scm_from_double (tanh (scm_to_double (z)));
   else if (SCM_COMPLEXP (z))
     { double x, y, w;
@@ -5556,7 +8893,7 @@ SCM_PRIMITIVE_GENERIC (scm_tanh, "tanh", 1, 0, 0,
       return scm_c_make_rectangular (sinh (x) / w, sin (y) / w);
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_tanh, z, 1, s_scm_tanh);
+    return scm_wta_dispatch_1 (g_scm_tanh, z, 1, s_scm_tanh);
 }
 #undef FUNC_NAME
 
@@ -5565,7 +8902,9 @@ SCM_PRIMITIVE_GENERIC (scm_asin, "asin", 1, 0, 0,
                        "Compute the arc sine of @var{z}.")
 #define FUNC_NAME s_scm_asin
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* asin(exact0) = exact0 */
+  else if (scm_is_real (z))
     {
       double w = scm_to_double (z);
       if (w >= -1.0 && w <= 1.0)
@@ -5582,7 +8921,7 @@ SCM_PRIMITIVE_GENERIC (scm_asin, "asin", 1, 0, 0,
                           scm_sys_asinh (scm_c_make_rectangular (-y, x)));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_asin, z, 1, s_scm_asin);
+    return scm_wta_dispatch_1 (g_scm_asin, z, 1, s_scm_asin);
 }
 #undef FUNC_NAME
 
@@ -5591,7 +8930,9 @@ SCM_PRIMITIVE_GENERIC (scm_acos, "acos", 1, 0, 0,
                        "Compute the arc cosine of @var{z}.")
 #define FUNC_NAME s_scm_acos
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM1)))
+    return SCM_INUM0;  /* acos(exact1) = exact0 */
+  else if (scm_is_real (z))
     {
       double w = scm_to_double (z);
       if (w >= -1.0 && w <= 1.0)
@@ -5610,7 +8951,7 @@ SCM_PRIMITIVE_GENERIC (scm_acos, "acos", 1, 0, 0,
                                    scm_sys_asinh (scm_c_make_rectangular (-y, x))));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_scm_acos, z, 1, s_scm_acos);
+    return scm_wta_dispatch_1 (g_scm_acos, z, 1, s_scm_acos);
 }
 #undef FUNC_NAME
 
@@ -5623,7 +8964,9 @@ SCM_PRIMITIVE_GENERIC (scm_atan, "atan", 1, 1, 0,
 {
   if (SCM_UNBNDP (y))
     {
-      if (scm_is_real (z))
+      if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+       return z;  /* atan(exact0) = exact0 */
+      else if (scm_is_real (z))
         return scm_from_double (atan (scm_to_double (z)));
       else if (SCM_COMPLEXP (z))
         {
@@ -5635,17 +8978,17 @@ SCM_PRIMITIVE_GENERIC (scm_atan, "atan", 1, 1, 0,
                              scm_c_make_rectangular (0, 2));
         }
       else
-        SCM_WTA_DISPATCH_2 (g_scm_atan, z, y, SCM_ARG1, s_scm_atan);
+        return scm_wta_dispatch_1 (g_scm_atan, z, SCM_ARG1, s_scm_atan);
     }
   else if (scm_is_real (z))
     {
       if (scm_is_real (y))
         return scm_from_double (atan2 (scm_to_double (z), scm_to_double (y)));
       else
-        SCM_WTA_DISPATCH_2 (g_scm_atan, z, y, SCM_ARG2, s_scm_atan);
+        return scm_wta_dispatch_2 (g_scm_atan, z, y, SCM_ARG2, s_scm_atan);
     }
   else
-    SCM_WTA_DISPATCH_2 (g_scm_atan, z, y, SCM_ARG1, s_scm_atan);
+    return scm_wta_dispatch_2 (g_scm_atan, z, y, SCM_ARG1, s_scm_atan);
 }
 #undef FUNC_NAME
 
@@ -5654,14 +8997,16 @@ SCM_PRIMITIVE_GENERIC (scm_sys_asinh, "asinh", 1, 0, 0,
                        "Compute the inverse hyperbolic sine of @var{z}.")
 #define FUNC_NAME s_scm_sys_asinh
 {
-  if (scm_is_real (z))
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* asinh(exact0) = exact0 */
+  else if (scm_is_real (z))
     return scm_from_double (asinh (scm_to_double (z)));
   else if (scm_is_number (z))
     return scm_log (scm_sum (z,
                              scm_sqrt (scm_sum (scm_product (z, z),
-                                                SCM_I_MAKINUM (1)))));
+                                                SCM_INUM1))));
   else
-    SCM_WTA_DISPATCH_1 (g_scm_sys_asinh, z, 1, s_scm_sys_asinh);
+    return scm_wta_dispatch_1 (g_scm_sys_asinh, z, 1, s_scm_sys_asinh);
 }
 #undef FUNC_NAME
 
@@ -5670,14 +9015,16 @@ SCM_PRIMITIVE_GENERIC (scm_sys_acosh, "acosh", 1, 0, 0,
                        "Compute the inverse hyperbolic cosine of @var{z}.")
 #define FUNC_NAME s_scm_sys_acosh
 {
-  if (scm_is_real (z) && scm_to_double (z) >= 1.0)
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM1)))
+    return SCM_INUM0;  /* acosh(exact1) = exact0 */
+  else if (scm_is_real (z) && scm_to_double (z) >= 1.0)
     return scm_from_double (acosh (scm_to_double (z)));
   else if (scm_is_number (z))
     return scm_log (scm_sum (z,
                              scm_sqrt (scm_difference (scm_product (z, z),
-                                                       SCM_I_MAKINUM (1)))));
+                                                       SCM_INUM1))));
   else
-    SCM_WTA_DISPATCH_1 (g_scm_sys_acosh, z, 1, s_scm_sys_acosh);
+    return scm_wta_dispatch_1 (g_scm_sys_acosh, z, 1, s_scm_sys_acosh);
 }
 #undef FUNC_NAME
 
@@ -5686,46 +9033,49 @@ SCM_PRIMITIVE_GENERIC (scm_sys_atanh, "atanh", 1, 0, 0,
                        "Compute the inverse hyperbolic tangent of @var{z}.")
 #define FUNC_NAME s_scm_sys_atanh
 {
-  if (scm_is_real (z) && scm_to_double (z) >= -1.0 && scm_to_double (z) <= 1.0)
+  if (SCM_UNLIKELY (scm_is_eq (z, SCM_INUM0)))
+    return z;  /* atanh(exact0) = exact0 */
+  else if (scm_is_real (z) && scm_to_double (z) >= -1.0 && scm_to_double (z) <= 1.0)
     return scm_from_double (atanh (scm_to_double (z)));
   else if (scm_is_number (z))
-    return scm_divide (scm_log (scm_divide (scm_sum (SCM_I_MAKINUM (1), z),
-                                            scm_difference (SCM_I_MAKINUM (1), z))),
+    return scm_divide (scm_log (scm_divide (scm_sum (SCM_INUM1, z),
+                                            scm_difference (SCM_INUM1, z))),
                        SCM_I_MAKINUM (2));
   else
-    SCM_WTA_DISPATCH_1 (g_scm_sys_atanh, z, 1, s_scm_sys_atanh);
+    return scm_wta_dispatch_1 (g_scm_sys_atanh, z, 1, s_scm_sys_atanh);
 }
 #undef FUNC_NAME
 
 SCM
 scm_c_make_rectangular (double re, double im)
 {
-  if (im == 0.0)
-    return scm_from_double (re);
-  else
-    {
-      SCM z;
-      SCM_NEWSMOB (z, scm_tc16_complex,
-                  scm_gc_malloc_pointerless (sizeof (scm_t_complex),
-                                             "complex"));
-      SCM_COMPLEX_REAL (z) = re;
-      SCM_COMPLEX_IMAG (z) = im;
-      return z;
-    }
+  SCM z;
+
+  z = SCM_PACK_POINTER (scm_gc_malloc_pointerless (sizeof (scm_t_complex),
+                                         "complex"));
+  SCM_SET_CELL_TYPE (z, scm_tc16_complex);
+  SCM_COMPLEX_REAL (z) = re;
+  SCM_COMPLEX_IMAG (z) = im;
+  return z;
 }
 
 SCM_DEFINE (scm_make_rectangular, "make-rectangular", 2, 0, 0,
             (SCM real_part, SCM imaginary_part),
-           "Return a complex number constructed of the given @var{real-part} "
-           "and @var{imaginary-part} parts.")
+           "Return a complex number constructed of the given @var{real_part} "
+           "and @var{imaginary_part} parts.")
 #define FUNC_NAME s_scm_make_rectangular
 {
   SCM_ASSERT_TYPE (scm_is_real (real_part), real_part,
                    SCM_ARG1, FUNC_NAME, "real");
   SCM_ASSERT_TYPE (scm_is_real (imaginary_part), imaginary_part,
                    SCM_ARG2, FUNC_NAME, "real");
-  return scm_c_make_rectangular (scm_to_double (real_part),
-                                 scm_to_double (imaginary_part));
+
+  /* Return a real if and only if the imaginary_part is an _exact_ 0 */
+  if (scm_is_eq (imaginary_part, SCM_INUM0))
+    return real_part;
+  else
+    return scm_c_make_rectangular (scm_to_double (real_part),
+                                  scm_to_double (imaginary_part));
 }
 #undef FUNC_NAME
 
@@ -5744,115 +9094,124 @@ scm_c_make_polar (double mag, double ang)
   s = sin (ang);
   c = cos (ang);
 #endif
-  return scm_c_make_rectangular (mag * c, mag * s);
+
+  /* If s and c are NaNs, this indicates that the angle is a NaN,
+     infinite, or perhaps simply too large to determine its value
+     mod 2*pi.  However, we know something that the floating-point
+     implementation doesn't know:  We know that s and c are finite.
+     Therefore, if the magnitude is zero, return a complex zero.
+
+     The reason we check for the NaNs instead of using this case
+     whenever mag == 0.0 is because when the angle is known, we'd
+     like to return the correct kind of non-real complex zero:
+     +0.0+0.0i, -0.0+0.0i, -0.0-0.0i, or +0.0-0.0i, depending
+     on which quadrant the angle is in.
+  */
+  if (SCM_UNLIKELY (isnan(s)) && isnan(c) && (mag == 0.0))
+    return scm_c_make_rectangular (0.0, 0.0);
+  else
+    return scm_c_make_rectangular (mag * c, mag * s);
 }
 
 SCM_DEFINE (scm_make_polar, "make-polar", 2, 0, 0,
-            (SCM x, SCM y),
-           "Return the complex number @var{x} * e^(i * @var{y}).")
+            (SCM mag, SCM ang),
+           "Return the complex number @var{mag} * e^(i * @var{ang}).")
 #define FUNC_NAME s_scm_make_polar
 {
-  SCM_ASSERT_TYPE (scm_is_real (x), x, SCM_ARG1, FUNC_NAME, "real");
-  SCM_ASSERT_TYPE (scm_is_real (y), y, SCM_ARG2, FUNC_NAME, "real");
-  return scm_c_make_polar (scm_to_double (x), scm_to_double (y));
+  SCM_ASSERT_TYPE (scm_is_real (mag), mag, SCM_ARG1, FUNC_NAME, "real");
+  SCM_ASSERT_TYPE (scm_is_real (ang), ang, SCM_ARG2, FUNC_NAME, "real");
+
+  /* If mag is exact0, return exact0 */
+  if (scm_is_eq (mag, SCM_INUM0))
+    return SCM_INUM0;
+  /* Return a real if ang is exact0 */
+  else if (scm_is_eq (ang, SCM_INUM0))
+    return mag;
+  else
+    return scm_c_make_polar (scm_to_double (mag), scm_to_double (ang));
 }
 #undef FUNC_NAME
 
 
-SCM_GPROC (s_real_part, "real-part", 1, 0, 0, scm_real_part, g_real_part);
-/* "Return the real part of the number @var{z}."
- */
-SCM
-scm_real_part (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_real_part, "real-part", 1, 0, 0,
+                      (SCM z),
+                      "Return the real part of the number @var{z}.")
+#define FUNC_NAME s_scm_real_part
 {
-  if (SCM_I_INUMP (z))
-    return z;
-  else if (SCM_BIGP (z))
-    return z;
-  else if (SCM_REALP (z))
-    return z;
-  else if (SCM_COMPLEXP (z))
+  if (SCM_COMPLEXP (z))
     return scm_from_double (SCM_COMPLEX_REAL (z));
-  else if (SCM_FRACTIONP (z))
+  else if (SCM_I_INUMP (z) || SCM_BIGP (z) || SCM_REALP (z) || SCM_FRACTIONP (z))
     return z;
   else
-    SCM_WTA_DISPATCH_1 (g_real_part, z, SCM_ARG1, s_real_part);
+    return scm_wta_dispatch_1 (g_scm_real_part, z, SCM_ARG1, s_scm_real_part);
 }
+#undef FUNC_NAME
 
 
-SCM_GPROC (s_imag_part, "imag-part", 1, 0, 0, scm_imag_part, g_imag_part);
-/* "Return the imaginary part of the number @var{z}."
- */
-SCM
-scm_imag_part (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_imag_part, "imag-part", 1, 0, 0,
+                      (SCM z),
+                      "Return the imaginary part of the number @var{z}.")
+#define FUNC_NAME s_scm_imag_part
 {
-  if (SCM_I_INUMP (z))
-    return SCM_INUM0;
-  else if (SCM_BIGP (z))
-    return SCM_INUM0;
-  else if (SCM_REALP (z))
-    return flo0;
-  else if (SCM_COMPLEXP (z))
+  if (SCM_COMPLEXP (z))
     return scm_from_double (SCM_COMPLEX_IMAG (z));
-  else if (SCM_FRACTIONP (z))
+  else if (SCM_I_INUMP (z) || SCM_REALP (z) || SCM_BIGP (z) || SCM_FRACTIONP (z))
     return SCM_INUM0;
   else
-    SCM_WTA_DISPATCH_1 (g_imag_part, z, SCM_ARG1, s_imag_part);
+    return scm_wta_dispatch_1 (g_scm_imag_part, z, SCM_ARG1, s_scm_imag_part);
 }
+#undef FUNC_NAME
 
-SCM_GPROC (s_numerator, "numerator", 1, 0, 0, scm_numerator, g_numerator);
-/* "Return the numerator of the number @var{z}."
- */
-SCM
-scm_numerator (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_numerator, "numerator", 1, 0, 0,
+                      (SCM z),
+                      "Return the numerator of the number @var{z}.")
+#define FUNC_NAME s_scm_numerator
 {
-  if (SCM_I_INUMP (z))
-    return z;
-  else if (SCM_BIGP (z))
+  if (SCM_I_INUMP (z) || SCM_BIGP (z))
     return z;
   else if (SCM_FRACTIONP (z))
     return SCM_FRACTION_NUMERATOR (z);
   else if (SCM_REALP (z))
     return scm_exact_to_inexact (scm_numerator (scm_inexact_to_exact (z)));
   else
-    SCM_WTA_DISPATCH_1 (g_numerator, z, SCM_ARG1, s_numerator);
+    return scm_wta_dispatch_1 (g_scm_numerator, z, SCM_ARG1, s_scm_numerator);
 }
+#undef FUNC_NAME
 
 
-SCM_GPROC (s_denominator, "denominator", 1, 0, 0, scm_denominator, g_denominator);
-/* "Return the denominator of the number @var{z}."
- */
-SCM
-scm_denominator (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_denominator, "denominator", 1, 0, 0,
+                      (SCM z),
+                      "Return the denominator of the number @var{z}.")
+#define FUNC_NAME s_scm_denominator
 {
-  if (SCM_I_INUMP (z))
-    return SCM_I_MAKINUM (1);
-  else if (SCM_BIGP (z)) 
-    return SCM_I_MAKINUM (1);
+  if (SCM_I_INUMP (z) || SCM_BIGP (z)) 
+    return SCM_INUM1;
   else if (SCM_FRACTIONP (z))
     return SCM_FRACTION_DENOMINATOR (z);
   else if (SCM_REALP (z))
     return scm_exact_to_inexact (scm_denominator (scm_inexact_to_exact (z)));
   else
-    SCM_WTA_DISPATCH_1 (g_denominator, z, SCM_ARG1, s_denominator);
+    return scm_wta_dispatch_1 (g_scm_denominator, z, SCM_ARG1,
+                               s_scm_denominator);
 }
+#undef FUNC_NAME
 
-SCM_GPROC (s_magnitude, "magnitude", 1, 0, 0, scm_magnitude, g_magnitude);
-/* "Return the magnitude of the number @var{z}. This is the same as\n"
- * "@code{abs} for real arguments, but also allows complex numbers."
- */
-SCM
-scm_magnitude (SCM z)
+
+SCM_PRIMITIVE_GENERIC (scm_magnitude, "magnitude", 1, 0, 0,
+                      (SCM z),
+       "Return the magnitude of the number @var{z}. This is the same as\n"
+       "@code{abs} for real arguments, but also allows complex numbers.")
+#define FUNC_NAME s_scm_magnitude
 {
   if (SCM_I_INUMP (z))
     {
-      long int zz = SCM_I_INUM (z);
+      scm_t_inum zz = SCM_I_INUM (z);
       if (zz >= 0)
        return z;
       else if (SCM_POSFIXABLE (-zz))
        return SCM_I_MAKINUM (-zz);
       else
-       return scm_i_long2big (-zz);
+       return scm_i_inum2big (-zz);
     }
   else if (SCM_BIGP (z))
     {
@@ -5871,19 +9230,21 @@ scm_magnitude (SCM z)
     {
       if (scm_is_false (scm_negative_p (SCM_FRACTION_NUMERATOR (z))))
        return z;
-      return scm_i_make_ratio (scm_difference (SCM_FRACTION_NUMERATOR (z), SCM_UNDEFINED),
-                            SCM_FRACTION_DENOMINATOR (z));
+      return scm_i_make_ratio_already_reduced
+       (scm_difference (SCM_FRACTION_NUMERATOR (z), SCM_UNDEFINED),
+        SCM_FRACTION_DENOMINATOR (z));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_magnitude, z, SCM_ARG1, s_magnitude);
+    return scm_wta_dispatch_1 (g_scm_magnitude, z, SCM_ARG1,
+                               s_scm_magnitude);
 }
+#undef FUNC_NAME
 
 
-SCM_GPROC (s_angle, "angle", 1, 0, 0, scm_angle, g_angle);
-/* "Return the angle of the complex number @var{z}."
- */
-SCM
-scm_angle (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_angle, "angle", 1, 0, 0,
+                      (SCM z),
+                      "Return the angle of the complex number @var{z}.")
+#define FUNC_NAME s_scm_angle
 {
   /* atan(0,-1) is pi and it'd be possible to have that as a constant like
      flo0 to save allocating a new flonum with scm_from_double each time.
@@ -5907,7 +9268,8 @@ scm_angle (SCM z)
     }
   else if (SCM_REALP (z))
     {
-      if (SCM_REAL_VALUE (z) >= 0)
+      double x = SCM_REAL_VALUE (z);
+      if (x > 0.0 || double_is_non_negative_zero (x))
         return flo0;
       else
         return scm_from_double (atan2 (0.0, -1.0));
@@ -5921,15 +9283,15 @@ scm_angle (SCM z)
       else return scm_from_double (atan2 (0.0, -1.0));
     }
   else
-    SCM_WTA_DISPATCH_1 (g_angle, z, SCM_ARG1, s_angle);
+    return scm_wta_dispatch_1 (g_scm_angle, z, SCM_ARG1, s_scm_angle);
 }
+#undef FUNC_NAME
 
 
-SCM_GPROC (s_exact_to_inexact, "exact->inexact", 1, 0, 0, scm_exact_to_inexact, g_exact_to_inexact);
-/* Convert the number @var{x} to its inexact representation.\n" 
- */
-SCM
-scm_exact_to_inexact (SCM z)
+SCM_PRIMITIVE_GENERIC (scm_exact_to_inexact, "exact->inexact", 1, 0, 0,
+                      (SCM z),
+       "Convert the number @var{z} to its inexact representation.\n")
+#define FUNC_NAME s_scm_exact_to_inexact
 {
   if (SCM_I_INUMP (z))
     return scm_from_double ((double) SCM_I_INUM (z));
@@ -5940,44 +9302,64 @@ scm_exact_to_inexact (SCM z)
   else if (SCM_INEXACTP (z))
     return z;
   else
-    SCM_WTA_DISPATCH_1 (g_exact_to_inexact, z, 1, s_exact_to_inexact);
+    return scm_wta_dispatch_1 (g_scm_exact_to_inexact, z, 1,
+                               s_scm_exact_to_inexact);
 }
+#undef FUNC_NAME
 
 
-SCM_DEFINE (scm_inexact_to_exact, "inexact->exact", 1, 0, 0, 
-            (SCM z),
-           "Return an exact number that is numerically closest to @var{z}.")
+SCM_PRIMITIVE_GENERIC (scm_inexact_to_exact, "inexact->exact", 1, 0, 0, 
+                      (SCM z),
+       "Return an exact number that is numerically closest to @var{z}.")
 #define FUNC_NAME s_scm_inexact_to_exact
 {
-  if (SCM_I_INUMP (z))
+  if (SCM_I_INUMP (z) || SCM_BIGP (z) || SCM_FRACTIONP (z))
     return z;
-  else if (SCM_BIGP (z))
-    return z;
-  else if (SCM_REALP (z))
+  else
     {
-      if (isinf (SCM_REAL_VALUE (z)) || isnan (SCM_REAL_VALUE (z)))
+      double val;
+
+      if (SCM_REALP (z))
+       val = SCM_REAL_VALUE (z);
+      else if (SCM_COMPLEXP (z) && SCM_COMPLEX_IMAG (z) == 0.0)
+       val = SCM_COMPLEX_REAL (z);
+      else
+       return scm_wta_dispatch_1 (g_scm_inexact_to_exact, z, 1,
+                                   s_scm_inexact_to_exact);
+
+      if (!SCM_LIKELY (DOUBLE_IS_FINITE (val)))
        SCM_OUT_OF_RANGE (1, z);
+      else if (val == 0.0)
+        return SCM_INUM0;
       else
        {
-         mpq_t frac;
-         SCM q;
-         
-         mpq_init (frac);
-         mpq_set_d (frac, SCM_REAL_VALUE (z));
-         q = scm_i_make_ratio (scm_i_mpz2num (mpq_numref (frac)),
-                             scm_i_mpz2num (mpq_denref (frac)));
+          int expon;
+          SCM numerator;
 
-         /* When scm_i_make_ratio throws, we leak the memory allocated
-            for frac...
-          */
-         mpq_clear (frac);
-         return q;
+          numerator = scm_i_dbl2big (ldexp (frexp (val, &expon),
+                                            DBL_MANT_DIG));
+          expon -= DBL_MANT_DIG;
+          if (expon < 0)
+            {
+              int shift = mpz_scan1 (SCM_I_BIG_MPZ (numerator), 0);
+
+              if (shift > -expon)
+                shift = -expon;
+              mpz_fdiv_q_2exp (SCM_I_BIG_MPZ (numerator),
+                               SCM_I_BIG_MPZ (numerator),
+                               shift);
+              expon += shift;
+            }
+          numerator = scm_i_normbig (numerator);
+          if (expon < 0)
+            return scm_i_make_ratio_already_reduced
+              (numerator, left_shift_exact_integer (SCM_INUM1, -expon));
+          else if (expon > 0)
+            return left_shift_exact_integer (numerator, expon);
+          else
+            return numerator;
        }
     }
-  else if (SCM_FRACTIONP (z))
-    return z;
-  else
-    SCM_WRONG_TYPE_ARG (1, z);
 }
 #undef FUNC_NAME
 
@@ -5996,11 +9378,46 @@ SCM_DEFINE (scm_rationalize, "rationalize", 2, 0, 0,
            "@end lisp")
 #define FUNC_NAME s_scm_rationalize
 {
-  if (SCM_I_INUMP (x))
-    return x;
-  else if (SCM_BIGP (x))
+  SCM_ASSERT_TYPE (scm_is_real (x), x, SCM_ARG1, FUNC_NAME, "real");
+  SCM_ASSERT_TYPE (scm_is_real (eps), eps, SCM_ARG2, FUNC_NAME, "real");
+  eps = scm_abs (eps);
+  if (scm_is_false (scm_positive_p (eps)))
+    {
+      /* eps is either zero or a NaN */
+      if (scm_is_true (scm_nan_p (eps)))
+       return scm_nan ();
+      else if (SCM_INEXACTP (eps))
+       return scm_exact_to_inexact (x);
+      else
+       return x;
+    }
+  else if (scm_is_false (scm_finite_p (eps)))
+    {
+      if (scm_is_true (scm_finite_p (x)))
+       return flo0;
+      else
+       return scm_nan ();
+    }
+  else if (scm_is_false (scm_finite_p (x))) /* checks for both inf and nan */
     return x;
-  else if ((SCM_REALP (x)) || SCM_FRACTIONP (x)) 
+  else if (scm_is_false (scm_less_p (scm_floor (scm_sum (x, eps)),
+                                    scm_ceiling (scm_difference (x, eps)))))
+    {
+      /* There's an integer within range; we want the one closest to zero */
+      if (scm_is_false (scm_less_p (eps, scm_abs (x))))
+       {
+         /* zero is within range */
+         if (SCM_INEXACTP (x) || SCM_INEXACTP (eps))
+           return flo0;
+         else
+           return SCM_INUM0;
+       }
+      else if (scm_is_true (scm_positive_p (x)))
+       return scm_ceiling (scm_difference (x, eps));
+      else
+       return scm_floor (scm_sum (x, eps));
+    }
+  else
     {
       /* Use continued fractions to find closest ratio.  All
         arithmetic is done with exact numbers.
@@ -6008,15 +9425,12 @@ SCM_DEFINE (scm_rationalize, "rationalize", 2, 0, 0,
 
       SCM ex = scm_inexact_to_exact (x);
       SCM int_part = scm_floor (ex);
-      SCM tt = SCM_I_MAKINUM (1);
-      SCM a1 = SCM_I_MAKINUM (0), a2 = SCM_I_MAKINUM (1), a = SCM_I_MAKINUM (0);
-      SCM b1 = SCM_I_MAKINUM (1), b2 = SCM_I_MAKINUM (0), b = SCM_I_MAKINUM (0);
+      SCM tt = SCM_INUM1;
+      SCM a1 = SCM_INUM0, a2 = SCM_INUM1, a = SCM_INUM0;
+      SCM b1 = SCM_INUM1, b2 = SCM_INUM0, b = SCM_INUM0;
       SCM rx;
       int i = 0;
 
-      if (scm_is_true (scm_num_eq_p (ex, int_part)))
-       return ex;
-      
       ex = scm_difference (ex, int_part);            /* x = x-int_part */
       rx = scm_divide (ex, SCM_UNDEFINED);            /* rx = 1/x */
 
@@ -6025,7 +9439,6 @@ SCM_DEFINE (scm_rationalize, "rationalize", 2, 0, 0,
         converges after less than a dozen iterations.
       */
 
-      eps = scm_abs (eps);
       while (++i < 1000000)
        {
          a = scm_sum (scm_product (a1, tt), a2);    /* a = a1*tt + a2 */
@@ -6036,8 +9449,7 @@ SCM_DEFINE (scm_rationalize, "rationalize", 2, 0, 0,
                         eps)))                      /* abs(x-a/b) <= eps */
            {
              SCM res = scm_sum (int_part, scm_divide (a, b));
-             if (scm_is_false (scm_exact_p (x))
-                 || scm_is_false (scm_exact_p (eps)))
+             if (SCM_INEXACTP (x) || SCM_INEXACTP (eps))
                return scm_exact_to_inexact (res);
              else
                return res;
@@ -6052,8 +9464,6 @@ SCM_DEFINE (scm_rationalize, "rationalize", 2, 0, 0,
        }
       scm_num_overflow (s_scm_rationalize);
     }
-  else
-    SCM_WRONG_TYPE_ARG (1, x);
 }
 #undef FUNC_NAME
 
@@ -6307,51 +9717,16 @@ scm_to_double (SCM val)
 SCM
 scm_from_double (double val)
 {
-  SCM z = scm_double_cell (scm_tc16_real, 0, 0, 0);
-  SCM_REAL_VALUE (z) = val;
-  return z;
-}
-
-#if SCM_ENABLE_DEPRECATED == 1
-
-float
-scm_num2float (SCM num, unsigned long int pos, const char *s_caller)
-{
-  scm_c_issue_deprecation_warning
-    ("`scm_num2float' is deprecated. Use scm_to_double instead.");
+  SCM z;
 
-  if (SCM_BIGP (num))
-    {
-      float res = mpz_get_d (SCM_I_BIG_MPZ (num));
-      if (!isinf (res))
-       return res;
-      else
-       scm_out_of_range (NULL, num);
-    }
-  else
-    return scm_to_double (num);
-}
+  z = SCM_PACK_POINTER (scm_gc_malloc_pointerless (sizeof (scm_t_double), "real"));
 
-double
-scm_num2double (SCM num, unsigned long int pos, const char *s_caller)
-{
-  scm_c_issue_deprecation_warning
-    ("`scm_num2double' is deprecated. Use scm_to_double instead.");
+  SCM_SET_CELL_TYPE (z, scm_tc16_real);
+  SCM_REAL_VALUE (z) = val;
 
-  if (SCM_BIGP (num))
-    {
-      double res = mpz_get_d (SCM_I_BIG_MPZ (num));
-      if (!isinf (res))
-       return res;
-      else
-       scm_out_of_range (NULL, num);
-    }
-  else
-    return scm_to_double (num);
+  return z;
 }
 
-#endif
-
 int
 scm_is_complex (SCM val)
 {
@@ -6406,20 +9781,70 @@ scm_is_number (SCM z)
 }
 
 
+/* Returns log(x * 2^shift) */
+static SCM
+log_of_shifted_double (double x, long shift)
+{
+  double ans = log (fabs (x)) + shift * M_LN2;
+
+  if (x > 0.0 || double_is_non_negative_zero (x))
+    return scm_from_double (ans);
+  else
+    return scm_c_make_rectangular (ans, M_PI);
+}
+
+/* Returns log(n), for exact integer n */
+static SCM
+log_of_exact_integer (SCM n)
+{
+  if (SCM_I_INUMP (n))
+    return log_of_shifted_double (SCM_I_INUM (n), 0);
+  else if (SCM_BIGP (n))
+    {
+      long expon;
+      double signif = scm_i_big2dbl_2exp (n, &expon);
+      return log_of_shifted_double (signif, expon);
+    }
+  else
+    scm_wrong_type_arg ("log_of_exact_integer", SCM_ARG1, n);
+}
+
+/* Returns log(n/d), for exact non-zero integers n and d */
+static SCM
+log_of_fraction (SCM n, SCM d)
+{
+  long n_size = scm_to_long (scm_integer_length (n));
+  long d_size = scm_to_long (scm_integer_length (d));
+
+  if (abs (n_size - d_size) > 1)
+    return (scm_difference (log_of_exact_integer (n),
+                           log_of_exact_integer (d)));
+  else if (scm_is_false (scm_negative_p (n)))
+    return scm_from_double
+      (log1p (scm_i_divide2double (scm_difference (n, d), d)));
+  else
+    return scm_c_make_rectangular
+      (log1p (scm_i_divide2double (scm_difference (scm_abs (n), d),
+                                   d)),
+       M_PI);
+}
+
+
 /* In the following functions we dispatch to the real-arg funcs like log()
    when we know the arg is real, instead of just handing everything to
    clog() for instance.  This is in case clog() doesn't optimize for a
    real-only case, and because we have to test SCM_COMPLEXP anyway so may as
    well use it to go straight to the applicable C func.  */
 
-SCM_DEFINE (scm_log, "log", 1, 0, 0,
-            (SCM z),
-           "Return the natural logarithm of @var{z}.")
+SCM_PRIMITIVE_GENERIC (scm_log, "log", 1, 0, 0,
+                      (SCM z),
+                      "Return the natural logarithm of @var{z}.")
 #define FUNC_NAME s_scm_log
 {
   if (SCM_COMPLEXP (z))
     {
-#if HAVE_COMPLEX_DOUBLE && HAVE_CLOG && defined (SCM_COMPLEX_VALUE)
+#if defined HAVE_COMPLEX_DOUBLE && defined HAVE_CLOG \
+  && defined (SCM_COMPLEX_VALUE)
       return scm_from_complex_double (clog (SCM_COMPLEX_VALUE (z)));
 #else
       double re = SCM_COMPLEX_REAL (z);
@@ -6428,24 +9853,30 @@ SCM_DEFINE (scm_log, "log", 1, 0, 0,
                                      atan2 (im, re));
 #endif
     }
-  else
+  else if (SCM_REALP (z))
+    return log_of_shifted_double (SCM_REAL_VALUE (z), 0);
+  else if (SCM_I_INUMP (z))
     {
-      /* ENHANCE-ME: When z is a bignum the logarithm will fit a double
-         although the value itself overflows.  */
-      double re = scm_to_double (z);
-      double l = log (fabs (re));
-      if (re >= 0.0)
-        return scm_from_double (l);
-      else
-        return scm_c_make_rectangular (l, M_PI);
+#ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
+      if (scm_is_eq (z, SCM_INUM0))
+       scm_num_overflow (s_scm_log);
+#endif
+      return log_of_shifted_double (SCM_I_INUM (z), 0);
     }
+  else if (SCM_BIGP (z))
+    return log_of_exact_integer (z);
+  else if (SCM_FRACTIONP (z))
+    return log_of_fraction (SCM_FRACTION_NUMERATOR (z),
+                           SCM_FRACTION_DENOMINATOR (z));
+  else
+    return scm_wta_dispatch_1 (g_scm_log, z, 1, s_scm_log);
 }
 #undef FUNC_NAME
 
 
-SCM_DEFINE (scm_log10, "log10", 1, 0, 0,
-            (SCM z),
-           "Return the base 10 logarithm of @var{z}.")
+SCM_PRIMITIVE_GENERIC (scm_log10, "log10", 1, 0, 0,
+                      (SCM z),
+                      "Return the base 10 logarithm of @var{z}.")
 #define FUNC_NAME s_scm_log10
 {
   if (SCM_COMPLEXP (z))
@@ -6463,81 +9894,304 @@ SCM_DEFINE (scm_log10, "log10", 1, 0, 0,
                                      M_LOG10E * atan2 (im, re));
 #endif
     }
-  else
+  else if (SCM_REALP (z) || SCM_I_INUMP (z))
     {
-      /* ENHANCE-ME: When z is a bignum the logarithm will fit a double
-         although the value itself overflows.  */
-      double re = scm_to_double (z);
-      double l = log10 (fabs (re));
-      if (re >= 0.0)
-        return scm_from_double (l);
-      else
-        return scm_c_make_rectangular (l, M_LOG10E * M_PI);
+#ifndef ALLOW_DIVIDE_BY_EXACT_ZERO
+      if (scm_is_eq (z, SCM_INUM0))
+       scm_num_overflow (s_scm_log10);
+#endif
+      {
+       double re = scm_to_double (z);
+       double l = log10 (fabs (re));
+       if (re > 0.0 || double_is_non_negative_zero (re))
+         return scm_from_double (l);
+       else
+         return scm_c_make_rectangular (l, M_LOG10E * M_PI);
+      }
     }
+  else if (SCM_BIGP (z))
+    return scm_product (flo_log10e, log_of_exact_integer (z));
+  else if (SCM_FRACTIONP (z))
+    return scm_product (flo_log10e,
+                       log_of_fraction (SCM_FRACTION_NUMERATOR (z),
+                                        SCM_FRACTION_DENOMINATOR (z)));
+  else
+    return scm_wta_dispatch_1 (g_scm_log10, z, 1, s_scm_log10);
 }
 #undef FUNC_NAME
 
 
-SCM_DEFINE (scm_exp, "exp", 1, 0, 0,
-            (SCM z),
-           "Return @math{e} to the power of @var{z}, where @math{e} is the\n"
-           "base of natural logarithms (2.71828@dots{}).")
+SCM_PRIMITIVE_GENERIC (scm_exp, "exp", 1, 0, 0,
+                      (SCM z),
+       "Return @math{e} to the power of @var{z}, where @math{e} is the\n"
+       "base of natural logarithms (2.71828@dots{}).")
 #define FUNC_NAME s_scm_exp
 {
   if (SCM_COMPLEXP (z))
     {
-#if HAVE_COMPLEX_DOUBLE && HAVE_CEXP && defined (SCM_COMPLEX_VALUE)
+#if defined HAVE_COMPLEX_DOUBLE && defined HAVE_CEXP \
+  && defined (SCM_COMPLEX_VALUE)
       return scm_from_complex_double (cexp (SCM_COMPLEX_VALUE (z)));
 #else
       return scm_c_make_polar (exp (SCM_COMPLEX_REAL (z)),
                                SCM_COMPLEX_IMAG (z));
 #endif
     }
-  else
+  else if (SCM_NUMBERP (z))
     {
       /* When z is a negative bignum the conversion to double overflows,
          giving -infinity, but that's ok, the exp is still 0.0.  */
       return scm_from_double (exp (scm_to_double (z)));
     }
+  else
+    return scm_wta_dispatch_1 (g_scm_exp, z, 1, s_scm_exp);
 }
 #undef FUNC_NAME
 
 
-SCM_DEFINE (scm_sqrt, "sqrt", 1, 0, 0,
-            (SCM x),
-           "Return the square root of @var{z}.  Of the two possible roots\n"
-           "(positive and negative), the one with the a positive real part\n"
-           "is returned, or if that's zero then a positive imaginary part.\n"
-           "Thus,\n"
+SCM_DEFINE (scm_i_exact_integer_sqrt, "exact-integer-sqrt", 1, 0, 0,
+           (SCM k),
+           "Return two exact non-negative integers @var{s} and @var{r}\n"
+           "such that @math{@var{k} = @var{s}^2 + @var{r}} and\n"
+           "@math{@var{s}^2 <= @var{k} < (@var{s} + 1)^2}.\n"
+           "An error is raised if @var{k} is not an exact non-negative integer.\n"
            "\n"
-           "@example\n"
-           "(sqrt 9.0)       @result{} 3.0\n"
-           "(sqrt -9.0)      @result{} 0.0+3.0i\n"
-           "(sqrt 1.0+1.0i)  @result{} 1.09868411346781+0.455089860562227i\n"
-           "(sqrt -1.0-1.0i) @result{} 0.455089860562227-1.09868411346781i\n"
-           "@end example")
+           "@lisp\n"
+           "(exact-integer-sqrt 10) @result{} 3 and 1\n"
+           "@end lisp")
+#define FUNC_NAME s_scm_i_exact_integer_sqrt
+{
+  SCM s, r;
+
+  scm_exact_integer_sqrt (k, &s, &r);
+  return scm_values (scm_list_2 (s, r));
+}
+#undef FUNC_NAME
+
+void
+scm_exact_integer_sqrt (SCM k, SCM *sp, SCM *rp)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (k)))
+    {
+      mpz_t kk, ss, rr;
+
+      if (SCM_I_INUM (k) < 0)
+       scm_wrong_type_arg_msg ("exact-integer-sqrt", SCM_ARG1, k,
+                               "exact non-negative integer");
+      mpz_init_set_ui (kk, SCM_I_INUM (k));
+      mpz_inits (ss, rr, NULL);
+      mpz_sqrtrem (ss, rr, kk);
+      *sp = SCM_I_MAKINUM (mpz_get_ui (ss));
+      *rp = SCM_I_MAKINUM (mpz_get_ui (rr));
+      mpz_clears (kk, ss, rr, NULL);
+    }
+  else if (SCM_LIKELY (SCM_BIGP (k)))
+    {
+      SCM s, r;
+
+      if (mpz_sgn (SCM_I_BIG_MPZ (k)) < 0)
+       scm_wrong_type_arg_msg ("exact-integer-sqrt", SCM_ARG1, k,
+                               "exact non-negative integer");
+      s = scm_i_mkbig ();
+      r = scm_i_mkbig ();
+      mpz_sqrtrem (SCM_I_BIG_MPZ (s), SCM_I_BIG_MPZ (r), SCM_I_BIG_MPZ (k));
+      scm_remember_upto_here_1 (k);
+      *sp = scm_i_normbig (s);
+      *rp = scm_i_normbig (r);
+    }
+  else
+    scm_wrong_type_arg_msg ("exact-integer-sqrt", SCM_ARG1, k,
+                           "exact non-negative integer");
+}
+
+/* Return true iff K is a perfect square.
+   K must be an exact integer. */
+static int
+exact_integer_is_perfect_square (SCM k)
+{
+  int result;
+
+  if (SCM_LIKELY (SCM_I_INUMP (k)))
+    {
+      mpz_t kk;
+
+      mpz_init_set_si (kk, SCM_I_INUM (k));
+      result = mpz_perfect_square_p (kk);
+      mpz_clear (kk);
+    }
+  else
+    {
+      result = mpz_perfect_square_p (SCM_I_BIG_MPZ (k));
+      scm_remember_upto_here_1 (k);
+    }
+  return result;
+}
+
+/* Return the floor of the square root of K.
+   K must be an exact integer. */
+static SCM
+exact_integer_floor_square_root (SCM k)
+{
+  if (SCM_LIKELY (SCM_I_INUMP (k)))
+    {
+      mpz_t kk;
+      scm_t_inum ss;
+
+      mpz_init_set_ui (kk, SCM_I_INUM (k));
+      mpz_sqrt (kk, kk);
+      ss = mpz_get_ui (kk);
+      mpz_clear (kk);
+      return SCM_I_MAKINUM (ss);
+    }
+  else
+    {
+      SCM s;
+
+      s = scm_i_mkbig ();
+      mpz_sqrt (SCM_I_BIG_MPZ (s), SCM_I_BIG_MPZ (k));
+      scm_remember_upto_here_1 (k);
+      return scm_i_normbig (s);
+    }
+}
+
+
+SCM_PRIMITIVE_GENERIC (scm_sqrt, "sqrt", 1, 0, 0,
+                      (SCM z),
+       "Return the square root of @var{z}.  Of the two possible roots\n"
+       "(positive and negative), the one with positive real part\n"
+       "is returned, or if that's zero then a positive imaginary part.\n"
+       "Thus,\n"
+       "\n"
+       "@example\n"
+       "(sqrt 9.0)       @result{} 3.0\n"
+       "(sqrt -9.0)      @result{} 0.0+3.0i\n"
+       "(sqrt 1.0+1.0i)  @result{} 1.09868411346781+0.455089860562227i\n"
+       "(sqrt -1.0-1.0i) @result{} 0.455089860562227-1.09868411346781i\n"
+       "@end example")
 #define FUNC_NAME s_scm_sqrt
 {
-  if (SCM_COMPLEXP (x))
+  if (SCM_COMPLEXP (z))
     {
 #if defined HAVE_COMPLEX_DOUBLE && defined HAVE_USABLE_CSQRT   \
       && defined SCM_COMPLEX_VALUE
-      return scm_from_complex_double (csqrt (SCM_COMPLEX_VALUE (x)));
+      return scm_from_complex_double (csqrt (SCM_COMPLEX_VALUE (z)));
 #else
-      double re = SCM_COMPLEX_REAL (x);
-      double im = SCM_COMPLEX_IMAG (x);
+      double re = SCM_COMPLEX_REAL (z);
+      double im = SCM_COMPLEX_IMAG (z);
       return scm_c_make_polar (sqrt (hypot (re, im)),
                                0.5 * atan2 (im, re));
 #endif
     }
-  else
+  else if (SCM_NUMBERP (z))
     {
-      double xx = scm_to_double (x);
-      if (xx < 0)
-        return scm_c_make_rectangular (0.0, sqrt (-xx));
-      else
-        return scm_from_double (sqrt (xx));
+      if (SCM_I_INUMP (z))
+        {
+          scm_t_inum x = SCM_I_INUM (z);
+
+          if (SCM_LIKELY (x >= 0))
+            {
+              if (SCM_LIKELY (SCM_I_FIXNUM_BIT < DBL_MANT_DIG
+                              || x < (1L << (DBL_MANT_DIG - 1))))
+                {
+                  double root = sqrt (x);
+
+                  /* If 0 <= x < 2^(DBL_MANT_DIG-1) and sqrt(x) is an
+                     integer, then the result is exact. */
+                  if (root == floor (root))
+                    return SCM_I_MAKINUM ((scm_t_inum) root);
+                  else
+                    return scm_from_double (root);
+                }
+              else
+                {
+                  mpz_t xx;
+                  scm_t_inum root;
+
+                  mpz_init_set_ui (xx, x);
+                  if (mpz_perfect_square_p (xx))
+                    {
+                      mpz_sqrt (xx, xx);
+                      root = mpz_get_ui (xx);
+                      mpz_clear (xx);
+                      return SCM_I_MAKINUM (root);
+                    }
+                  else
+                    mpz_clear (xx);
+                }
+            }
+        }
+      else if (SCM_BIGP (z))
+        {
+          if (mpz_perfect_square_p (SCM_I_BIG_MPZ (z)))
+            {
+              SCM root = scm_i_mkbig ();
+
+              mpz_sqrt (SCM_I_BIG_MPZ (root), SCM_I_BIG_MPZ (z));
+              scm_remember_upto_here_1 (z);
+              return scm_i_normbig (root);
+            }
+          else
+            {
+              long expon;
+              double signif = scm_i_big2dbl_2exp (z, &expon);
+
+              if (expon & 1)
+                {
+                  signif *= 2;
+                  expon--;
+                }
+              if (signif < 0)
+                return scm_c_make_rectangular
+                  (0.0, ldexp (sqrt (-signif), expon / 2));
+              else
+                return scm_from_double (ldexp (sqrt (signif), expon / 2));
+            }
+        }
+      else if (SCM_FRACTIONP (z))
+        {
+          SCM n = SCM_FRACTION_NUMERATOR (z);
+          SCM d = SCM_FRACTION_DENOMINATOR (z);
+
+          if (exact_integer_is_perfect_square (n)
+              && exact_integer_is_perfect_square (d))
+            return scm_i_make_ratio_already_reduced
+              (exact_integer_floor_square_root (n),
+               exact_integer_floor_square_root (d));
+          else
+            {
+              double xx = scm_i_divide2double (n, d);
+              double abs_xx = fabs (xx);
+              long shift = 0;
+
+              if (SCM_UNLIKELY (abs_xx > DBL_MAX || abs_xx < DBL_MIN))
+                {
+                  shift = (scm_to_long (scm_integer_length (n))
+                           - scm_to_long (scm_integer_length (d))) / 2;
+                  if (shift > 0)
+                    d = left_shift_exact_integer (d, 2 * shift);
+                  else
+                    n = left_shift_exact_integer (n, -2 * shift);
+                  xx = scm_i_divide2double (n, d);
+                }
+
+              if (xx < 0)
+                return scm_c_make_rectangular (0.0, ldexp (sqrt (-xx), shift));
+              else
+                return scm_from_double (ldexp (sqrt (xx), shift));
+            }
+        }
+
+      /* Fallback method, when the cases above do not apply. */
+      {
+        double xx = scm_to_double (z);
+        if (xx < 0)
+          return scm_c_make_rectangular (0.0, sqrt (-xx));
+        else
+          return scm_from_double (sqrt (xx));
+      }
     }
+  else
+    return scm_wta_dispatch_1 (g_scm_sqrt, z, 1, s_scm_sqrt);
 }
 #undef FUNC_NAME
 
@@ -6546,7 +10200,10 @@ SCM_DEFINE (scm_sqrt, "sqrt", 1, 0, 0,
 void
 scm_init_numbers ()
 {
-  int i;
+  if (scm_install_gmp_memory_functions)
+    mp_set_memory_functions (custom_gmp_malloc,
+                             custom_gmp_realloc,
+                             custom_gmp_free);
 
   mpz_init_set_si (z_negative_one, -1);
 
@@ -6562,19 +10219,27 @@ scm_init_numbers ()
   scm_add_feature ("complex");
   scm_add_feature ("inexact");
   flo0 = scm_from_double (0.0);
+  flo_log10e = scm_from_double (M_LOG10E);
 
-  /* determine floating point precision */
-  for (i=2; i <= SCM_MAX_DBL_RADIX; ++i)
-    {
-      init_dblprec(&scm_dblprec[i-2],i);
-      init_fx_radix(fx_per_radix[i-2],i);
-    }
-#ifdef DBL_DIG
-  /* hard code precision for base 10 if the preprocessor tells us to... */
-  scm_dblprec[10-2] = (DBL_DIG > 20) ? 20 : DBL_DIG;
-#endif
+  exactly_one_half = scm_divide (SCM_INUM1, SCM_I_MAKINUM (2));
+
+  {
+    /* Set scm_i_divide2double_lo2b to (2 b^p - 1) */
+    mpz_init_set_ui (scm_i_divide2double_lo2b, 1);
+    mpz_mul_2exp (scm_i_divide2double_lo2b,
+                  scm_i_divide2double_lo2b,
+                  DBL_MANT_DIG + 1); /* 2 b^p */
+    mpz_sub_ui (scm_i_divide2double_lo2b, scm_i_divide2double_lo2b, 1);
+  }
+
+  {
+    /* Set dbl_minimum_normal_mantissa to b^{p-1} */
+    mpz_init_set_ui (dbl_minimum_normal_mantissa, 1);
+    mpz_mul_2exp (dbl_minimum_normal_mantissa,
+                  dbl_minimum_normal_mantissa,
+                  DBL_MANT_DIG - 1);
+  }
 
-  exactly_one_half = scm_divide (SCM_I_MAKINUM (1), SCM_I_MAKINUM (2));
 #include "libguile/numbers.x"
 }