Fix bugs when negating SCM_MOST_POSITIVE_FIXNUM+1
authorMark H Weaver <mhw@netris.org>
Wed, 26 Jan 2011 10:21:03 +0000 (05:21 -0500)
committerAndy Wingo <wingo@pobox.com>
Fri, 28 Jan 2011 12:52:46 +0000 (13:52 +0100)
* libguile/numbers.c (scm_difference, scm_product):
  Fix bugs when negating SCM_MOST_POSITIVE_FIXNUM+1,
  aka -SCM_MOST_NEGATIVE_FIXNUM.  Previously, these cases
  failed to normalize the result to a fixnum, causing
  `=', `eqv?' and `equal?' to fail, e.g.:
  (= most-negative-fixnum (- 0 (- most-negative-fixnum)))
  (= most-negative-fixnum (* -1 (- most-negative-fixnum)))
  (= most-negative-fixnum (* (- most-negative-fixnum) -1))

* test-suite/test/numbers.test: Add test cases to detect
  bugs when negating SCM_MOST_POSITIVE_FIXNUM+1 and
  SCM_MOST_NEGATIVE_FIXNUM by various methods.

libguile/numbers.c
test-suite/tests/numbers.test

index 21f8f58..9998ab7 100644 (file)
@@ -4488,7 +4488,11 @@ scm_difference (SCM x, SCM y)
          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));
@@ -4720,6 +4724,17 @@ scm_product (SCM x, SCM y)
        {
         case 0: return x; break;
         case 1: return y; break;
+         /*
+          * The following case (x = -1) 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)))
+          */
+        case -1:
+         return scm_difference(y, SCM_UNDEFINED);
+         break;
        }
 
       if (SCM_LIKELY (SCM_I_INUMP (y)))
index d2b8889..f53cb34 100644 (file)
 
 (with-test-prefix/c&e "-"
 
+  (pass-if "double-negation of fixnum-min: ="
+    (=      fixnum-min (- (- fixnum-min))))
+  (pass-if "double-negation of fixnum-min: eqv?"
+    (eqv?   fixnum-min (- (- fixnum-min))))
+  (pass-if "double-negation of fixnum-min: equal?"
+    (equal? fixnum-min (- (- fixnum-min))))
+
+  (pass-if "binary double-negation of fixnum-min: ="
+    (=      fixnum-min (- 0 (- 0 fixnum-min))))
+  (pass-if "binary double-negation of fixnum-min: eqv?"
+    (eqv?   fixnum-min (- 0 (- 0 fixnum-min))))
+  (pass-if "binary double-negation of fixnum-min: equal?"
+    (equal? fixnum-min (- 0 (- 0 fixnum-min))))
+
   (pass-if "-inum - +bignum"
     (= #x-100000000000000000000000000000001
        (- -1 #x100000000000000000000000000000000)))
 
 (with-test-prefix "*"
 
+  (with-test-prefix "double-negation of fixnum-min"
+    (pass-if (=      fixnum-min (* -1 (* -1 fixnum-min))))
+    (pass-if (eqv?   fixnum-min (* -1 (* -1 fixnum-min))))
+    (pass-if (equal? fixnum-min (* -1 (* -1 fixnum-min))))
+    (pass-if (=      fixnum-min (* (* fixnum-min -1) -1)))
+    (pass-if (eqv?   fixnum-min (* (* fixnum-min -1) -1)))
+    (pass-if (equal? fixnum-min (* (* fixnum-min -1) -1))))
+
   (with-test-prefix "inum * bignum"
 
     (pass-if "0 * 2^256 = 0"
 
 (with-test-prefix "/"
 
+  (with-test-prefix "double-negation of fixnum-min"
+    (pass-if (=      fixnum-min (/ (/ fixnum-min -1) -1)))
+    (pass-if (eqv?   fixnum-min (/ (/ fixnum-min -1) -1)))
+    (pass-if (equal? fixnum-min (/ (/ fixnum-min -1) -1))))
+
   (pass-if "documented?"
     (documented? /))