Improve handling of INT_MIN / -1 and related cases.
authorTom Lane <[email protected]>
Tue, 20 Nov 2012 02:22:00 +0000 (21:22 -0500)
committerTom Lane <[email protected]>
Tue, 20 Nov 2012 02:22:00 +0000 (21:22 -0500)
Some platforms throw an exception for this division, rather than returning
a necessarily-overflowed result.  Since we were testing for overflow after
the fact, an exception isn't nice.  We can avoid the problem by treating
division by -1 as negation.

Add some regression tests so that we'll find out if any compilers try to
optimize away the overflow check conditions.

Back-patch of commit 1f7cb5c30983752ff8de833de30afcaee63536d0.

Per discussion with Xi Wang, though this is different from the patch he
submitted.

src/backend/utils/adt/int.c
src/backend/utils/adt/int8.c
src/test/regress/expected/int2.out
src/test/regress/expected/int4.out
src/test/regress/expected/int8-exp-three-digits.out
src/test/regress/expected/int8.out
src/test/regress/sql/int2.sql
src/test/regress/sql/int4.sql
src/test/regress/sql/int8.sql

index 134bdf5b35c9c2a3e52afb0b4ed7cec6a4861495..91067a98d532153075c2bd53caa7f02044e65dd9 100644 (file)
@@ -671,18 +671,6 @@ int4mul(PG_FUNCTION_ARGS)
    int32       arg2 = PG_GETARG_INT32(1);
    int32       result;
 
-#ifdef WIN32
-
-   /*
-    * Win32 doesn't throw a catchable exception for SELECT -2147483648 *
-    * (-1);  -- INT_MIN
-    */
-   if (arg2 == -1 && arg1 == INT_MIN)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("integer out of range")));
-#endif
-
    result = arg1 * arg2;
 
    /*
@@ -699,7 +687,8 @@ int4mul(PG_FUNCTION_ARGS)
    if (!(arg1 >= (int32) SHRT_MIN && arg1 <= (int32) SHRT_MAX &&
          arg2 >= (int32) SHRT_MIN && arg2 <= (int32) SHRT_MAX) &&
        arg2 != 0 &&
-       (result / arg2 != arg1 || (arg2 == -1 && arg1 < 0 && result < 0)))
+       ((arg2 == -1 && arg1 < 0 && result < 0) ||
+        result / arg2 != arg1))
        ereport(ERROR,
                (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
                 errmsg("integer out of range")));
@@ -722,29 +711,27 @@ int4div(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
    }
 
-#ifdef WIN32
-
    /*
-    * Win32 doesn't throw a catchable exception for SELECT -2147483648 /
-    * (-1); -- INT_MIN
+    * INT_MIN / -1 is problematic, since the result can't be represented on a
+    * two's-complement machine.  Some machines produce INT_MIN, some produce
+    * zero, some throw an exception.  We can dodge the problem by recognizing
+    * that division by -1 is the same as negation.
     */
-   if (arg2 == -1 && arg1 == INT_MIN)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("integer out of range")));
-#endif
+   if (arg2 == -1)
+   {
+       result = -arg1;
+       /* overflow check (needed for INT_MIN) */
+       if (arg1 != 0 && SAMESIGN(result, arg1))
+           ereport(ERROR,
+                   (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                    errmsg("integer out of range")));
+       PG_RETURN_INT32(result);
+   }
+
+   /* No overflow is possible */
 
    result = arg1 / arg2;
 
-   /*
-    * Overflow check.  The only possible overflow case is for arg1 = INT_MIN,
-    * arg2 = -1, where the correct result is -INT_MIN, which can't be
-    * represented on a two's-complement machine.
-    */
-   if (arg2 == -1 && arg1 < 0 && result < 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("integer out of range")));
    PG_RETURN_INT32(result);
 }
 
@@ -866,17 +853,27 @@ int2div(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
    }
 
-   result = arg1 / arg2;
-
    /*
-    * Overflow check.  The only possible overflow case is for arg1 =
-    * SHRT_MIN, arg2 = -1, where the correct result is -SHRT_MIN, which can't
-    * be represented on a two's-complement machine.
+    * SHRT_MIN / -1 is problematic, since the result can't be represented on
+    * a two's-complement machine.  Some machines produce SHRT_MIN, some
+    * produce zero, some throw an exception.  We can dodge the problem by
+    * recognizing that division by -1 is the same as negation.
     */
-   if (arg2 == -1 && arg1 < 0 && result < 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("smallint out of range")));
+   if (arg2 == -1)
+   {
+       result = -arg1;
+       /* overflow check (needed for SHRT_MIN) */
+       if (arg1 != 0 && SAMESIGN(result, arg1))
+           ereport(ERROR,
+                   (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                    errmsg("smallint out of range")));
+       PG_RETURN_INT16(result);
+   }
+
+   /* No overflow is possible */
+
+   result = arg1 / arg2;
+
    PG_RETURN_INT16(result);
 }
 
@@ -1054,17 +1051,27 @@ int42div(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
    }
 
-   result = arg1 / arg2;
-
    /*
-    * Overflow check.  The only possible overflow case is for arg1 = INT_MIN,
-    * arg2 = -1, where the correct result is -INT_MIN, which can't be
-    * represented on a two's-complement machine.
+    * INT_MIN / -1 is problematic, since the result can't be represented on a
+    * two's-complement machine.  Some machines produce INT_MIN, some produce
+    * zero, some throw an exception.  We can dodge the problem by recognizing
+    * that division by -1 is the same as negation.
     */
-   if (arg2 == -1 && arg1 < 0 && result < 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("integer out of range")));
+   if (arg2 == -1)
+   {
+       result = -arg1;
+       /* overflow check (needed for INT_MIN) */
+       if (arg1 != 0 && SAMESIGN(result, arg1))
+           ereport(ERROR,
+                   (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                    errmsg("integer out of range")));
+       PG_RETURN_INT32(result);
+   }
+
+   /* No overflow is possible */
+
+   result = arg1 / arg2;
+
    PG_RETURN_INT32(result);
 }
 
@@ -1160,6 +1167,14 @@ int42mod(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
    }
 
+   /*
+    * Some machines throw a floating-point exception for INT_MIN % -1, which
+    * is a bit silly since the correct answer is perfectly well-defined,
+    * namely zero.
+    */
+   if (arg2 == -1)
+       PG_RETURN_INT32(0);
+
    /* No overflow is possible */
 
    PG_RETURN_INT32(arg1 % arg2);
index 5657b89f5f6eb4d229819db52cbe9a07988d5793..45e023910d8f6cb83c0d96ba6fbfdb5edc94121d 100644 (file)
@@ -583,7 +583,8 @@ int8mul(PG_FUNCTION_ARGS)
 #endif
    {
        if (arg2 != 0 &&
-           (result / arg2 != arg1 || (arg2 == -1 && arg1 < 0 && result < 0)))
+           ((arg2 == -1 && arg1 < 0 && result < 0) ||
+            result / arg2 != arg1))
            ereport(ERROR,
                    (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
                     errmsg("bigint out of range")));
@@ -607,17 +608,27 @@ int8div(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
    }
 
-   result = arg1 / arg2;
-
    /*
-    * Overflow check.  The only possible overflow case is for arg1 =
-    * INT64_MIN, arg2 = -1, where the correct result is -INT64_MIN, which
-    * can't be represented on a two's-complement machine.
+    * INT64_MIN / -1 is problematic, since the result can't be represented on
+    * a two's-complement machine.  Some machines produce INT64_MIN, some
+    * produce zero, some throw an exception.  We can dodge the problem by
+    * recognizing that division by -1 is the same as negation.
     */
-   if (arg2 == -1 && arg1 < 0 && result < 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("bigint out of range")));
+   if (arg2 == -1)
+   {
+       result = -arg1;
+       /* overflow check (needed for INT64_MIN) */
+       if (arg1 != 0 && SAMESIGN(result, arg1))
+           ereport(ERROR,
+                   (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                    errmsg("bigint out of range")));
+       PG_RETURN_INT64(result);
+   }
+
+   /* No overflow is possible */
+
+   result = arg1 / arg2;
+
    PG_RETURN_INT64(result);
 }
 
@@ -846,17 +857,27 @@ int84div(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
    }
 
-   result = arg1 / arg2;
-
    /*
-    * Overflow check.  The only possible overflow case is for arg1 =
-    * INT64_MIN, arg2 = -1, where the correct result is -INT64_MIN, which
-    * can't be represented on a two's-complement machine.
+    * INT64_MIN / -1 is problematic, since the result can't be represented on
+    * a two's-complement machine.  Some machines produce INT64_MIN, some
+    * produce zero, some throw an exception.  We can dodge the problem by
+    * recognizing that division by -1 is the same as negation.
     */
-   if (arg2 == -1 && arg1 < 0 && result < 0)
-       ereport(ERROR,
-               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-                errmsg("bigint out of range")));
+   if (arg2 == -1)
+   {
+       result = -arg1;
+       /* overflow check (needed for INT64_MIN) */
+       if (arg1 != 0 && SAMESIGN(result, arg1))
+           ereport(ERROR,
+                   (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                    errmsg("bigint out of range")));
+       PG_RETURN_INT64(result);
+   }
+
+   /* No overflow is possible */
+
+   result = arg1 / arg2;
+
    PG_RETURN_INT64(result);
 }
 
index e34e85bc97133a06e5d8542625899cb858f7ee43..29e2d2faa85feafff62d9875b8b2107d37e0f581 100644 (file)
@@ -228,3 +228,14 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- check sane handling of INT16_MIN overflow cases
+SELECT (-32768)::int2 * (-1)::int2;
+ERROR:  smallint out of range
+SELECT (-32768)::int2 / (-1)::int2;
+ERROR:  smallint out of range
+SELECT (-32768)::int2 % (-1)::int2;
+ ?column? 
+----------
+        0
+(1 row)
+
index 3e0eff13a3122a890470e6eafe8d4eb5a1916fa1..eeae8bc5d4d27521971651ce08eb1483dcc25dd5 100644 (file)
@@ -315,3 +315,24 @@ SELECT (2 + 2) / 2 AS two;
    2
 (1 row)
 
+-- check sane handling of INT_MIN overflow cases
+SELECT (-2147483648)::int4 * (-1)::int4;
+ERROR:  integer out of range
+SELECT (-2147483648)::int4 / (-1)::int4;
+ERROR:  integer out of range
+SELECT (-2147483648)::int4 % (-1)::int4;
+ ?column? 
+----------
+        0
+(1 row)
+
+SELECT (-2147483648)::int4 * (-1)::int2;
+ERROR:  integer out of range
+SELECT (-2147483648)::int4 / (-1)::int2;
+ERROR:  integer out of range
+SELECT (-2147483648)::int4 % (-1)::int2;
+ ?column? 
+----------
+        0
+(1 row)
+
index e3697f8344f420b6b408db0a7d6d17b82abb706f..187bf8e4339538f29cebc2ad028558cd60d9630c 100644 (file)
@@ -317,3 +317,24 @@ select '9223372036854775807'::int8;
 
 select '9223372036854775808'::int8;
 ERROR:  value "9223372036854775808" is out of range for type bigint
+-- check sane handling of INT64_MIN overflow cases
+SELECT (-9223372036854775808)::int8 * (-1)::int8;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 / (-1)::int8;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 % (-1)::int8;
+ ?column? 
+----------
+        0
+(1 row)
+
+SELECT (-9223372036854775808)::int8 * (-1)::int4;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 / (-1)::int4;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 % (-1)::int4;
+ ?column? 
+----------
+        0
+(1 row)
+
index 52cdabab7499683953934a267a28525e78b5a8a7..467778c833a7a621efe69acbed9310077410187d 100644 (file)
@@ -317,3 +317,24 @@ select '9223372036854775807'::int8;
 
 select '9223372036854775808'::int8;
 ERROR:  value "9223372036854775808" is out of range for type bigint
+-- check sane handling of INT64_MIN overflow cases
+SELECT (-9223372036854775808)::int8 * (-1)::int8;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 / (-1)::int8;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 % (-1)::int8;
+ ?column? 
+----------
+        0
+(1 row)
+
+SELECT (-9223372036854775808)::int8 * (-1)::int4;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 / (-1)::int4;
+ERROR:  bigint out of range
+SELECT (-9223372036854775808)::int8 % (-1)::int4;
+ ?column? 
+----------
+        0
+(1 row)
+
index 65c89e4abdd0b03dd0c8eb093bfbbac88d6cb095..b185f16c0aa395904507b6dd517e7750a5af6399 100644 (file)
@@ -86,3 +86,7 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- check sane handling of INT16_MIN overflow cases
+SELECT (-32768)::int2 * (-1)::int2;
+SELECT (-32768)::int2 / (-1)::int2;
+SELECT (-32768)::int2 % (-1)::int2;
index 5212c687952724d26a19a25717a8fc7f7c75a1d2..9c24188dc0d74f57c672d3d03736bb488bb98859 100644 (file)
@@ -125,3 +125,11 @@ SELECT 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 AS ten;
 SELECT 2 + 2 / 2 AS three;
 
 SELECT (2 + 2) / 2 AS two;
+
+-- check sane handling of INT_MIN overflow cases
+SELECT (-2147483648)::int4 * (-1)::int4;
+SELECT (-2147483648)::int4 / (-1)::int4;
+SELECT (-2147483648)::int4 % (-1)::int4;
+SELECT (-2147483648)::int4 * (-1)::int2;
+SELECT (-2147483648)::int4 / (-1)::int2;
+SELECT (-2147483648)::int4 % (-1)::int2;
index 8ef92ba3f32570910fd79961ab6d3c83f7917bc2..d540b4643fbb6b6e919505ce65f2e5831c4d23d8 100644 (file)
@@ -69,3 +69,11 @@ select '-9223372036854775808'::int8;
 select '-9223372036854775809'::int8;
 select '9223372036854775807'::int8;
 select '9223372036854775808'::int8;
+
+-- check sane handling of INT64_MIN overflow cases
+SELECT (-9223372036854775808)::int8 * (-1)::int8;
+SELECT (-9223372036854775808)::int8 / (-1)::int8;
+SELECT (-9223372036854775808)::int8 % (-1)::int8;
+SELECT (-9223372036854775808)::int8 * (-1)::int4;
+SELECT (-9223372036854775808)::int8 / (-1)::int4;
+SELECT (-9223372036854775808)::int8 % (-1)::int4;