Similar to what
1f7cb5c30 did for the INT types, protect against
overflow when dividing the lowest possible money value by -1. This
cannot be represented on a two's complement machine.
Without this check, the result depends on the machine, and in the worst
case, could result in a crash. With the fix installed, this will now
result in:
ERROR: money out of range
Bug: #19585
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Michael Malis <malis@pgrust.com>
Reviewed-by: Tristan Partin <tristan@partin.io>
Reviewed-by: Rafia Sabih <rafia.pghackers@gmail.com>
Discussion: https://postgr.es/m/19586-
bb603bf5ad9934dd%40postgresql.org
Discussion: https://postgr.es/m/CAB8bMisnXJVXte6s3kUOpuuAY9%3D9kehG6MMX-%2BTQoFsSGan22Q%40mail.gmail.com
Backpatch-through: 14
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /*
+ * 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 (i == -1)
+ {
+ if (unlikely(c == PG_INT64_MIN))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("money out of range")));
+ return -c;
+ }
+
+ /* No overflow is possible */
return c / i;
}
ERROR: money out of range
SELECT '92233720368547758.07'::money * 2::int4;
ERROR: money out of range
+SELECT '-92233720368547758.08'::money * -1::int8;
+ERROR: money out of range
+SELECT '-92233720368547758.08'::money / -1::int8;
+ERROR: money out of range
+SELECT '-92233720368547758.08'::money / -1::int4;
+ERROR: money out of range
+SELECT '-92233720368547758.08'::money / -1::int2;
+ERROR: money out of range
SELECT '1'::money / 0::int2;
ERROR: division by zero
SELECT '42'::money * 'inf'::float8;
SELECT '92233720368547758.07'::money * 2::float8;
SELECT '-1'::money / 1.175494e-38::float4;
SELECT '92233720368547758.07'::money * 2::int4;
+SELECT '-92233720368547758.08'::money * -1::int8;
+SELECT '-92233720368547758.08'::money / -1::int8;
+SELECT '-92233720368547758.08'::money / -1::int4;
+SELECT '-92233720368547758.08'::money / -1::int2;
SELECT '1'::money / 0::int2;
SELECT '42'::money * 'inf'::float8;
SELECT '42'::money * '-inf'::float8;