Re: [BUGS] BUG #2846: inconsistent and confusing handling of - Mailing list pgsql-patches
From | Bruce Momjian |
---|---|
Subject | Re: [BUGS] BUG #2846: inconsistent and confusing handling of |
Date | |
Msg-id | 200612240650.kBO6oFh09463@momjian.us Whole thread Raw |
List | pgsql-patches |
Roman Kononov wrote: > > The following bug has been logged online: > > Bug reference: 2846 > Logged by: Roman Kononov > Email address: kononov195-pgsql@yahoo.com > PostgreSQL version: 8.2.0 and older > Operating system: linux 2.6.15-27-amd64 ubuntu > Description: inconsistent and confusing handling of underflows, NaNs > and INFs > Details: This is a very interesting bug report. It seems you have done some good analysis of PostgreSQL and how it handles certain corner cases, infinity, and NaN. I have researched your findings and will show some fixes below: > Please compare the results of the simple queries. > ============================================== > test=# select ('NaN'::float4)::int2; > int2 > ------ > 0 > (1 row) There certainly should be an isnan() test when converting to int2 because while float can represent NaN, int2 cannot. The fix shows: test=> select ('NaN'::float4)::int2; ERROR: smallint out of range > test=# select ('NaN'::float4)::int4; > int4 > ------------- > -2147483648 > (1 row) Same for int4: test=> select ('NaN'::float4)::int4; ERROR: integer out of range > test=# select ('NaN'::float4)::int8; > ERROR: bigint out of range This one was correct because it uses rint() internally. > test=# select ('nan'::numeric)::int4; > ERROR: cannot convert NaN to integer > ============================================== > test=# select abs('INF'::float4); > abs > ---------- > Infinity > (1 row) Correct. > test=# select abs('INF'::float8); > ERROR: type "double precision" value out of range: overflow This one was more complicated. float4/8 operations test for results > FLOAT[84]_MAX. This is because if you do this: test=> select (1e201::float8)*(1e200::float8); the result internally is Infinity, so they check for Inf as a check for overflow. The bottom line is that while the current code allows infinity to be entered, it does not allow the value to operate in many context because it is assumes Inf to be an overflow indicator. I have fixed this by passing a boolean to indicate if any of the operands were infinity, and if so, allow an infinite result, so this now works: test=> select abs('INF'::float8); abs ---------- Infinity (1 row) > ============================================== > test=# select -('INF'::float4); > ?column? > ----------- > -Infinity > (1 row) > > test=# select -('INF'::float8); > ERROR: type "double precision" value out of range: overflow And this now works too: test=> select -('INF'::float8); ?column? ----------- -Infinity (1 row) > ============================================== > test=# select (1e-37::float4)*(1e-22::float4); > ?column? > ---------- > 0 > (1 row) This one is quite complex. For overflow, there is a range of values that is represented as > FLOAT8_MAX, but for values very large, the result becomes Inf. The old code assumed an Inf result was an overflow, and threw an error, as I outlined above. The new code does a better job. Now, for underflow. For underflow, we again have a range slightly smaller than DBL_MIN where we can detect an underflow, and throw an error, but just like overflow, if the underflow is too small, the result becomes zero. The bad news is that unlike Inf, zero isn't a special value. With Inf, we could say if we got an infinite result from non-infinite arguments, we had an overflow, but for underflow, how do we know if zero is an underflow or just the correct result? For multiplication, we could say that a zero result for non-zero arguments is almost certainly an underflow, but I don't see how we can handle the other operations as simply. I was not able to fix the underflow problems you reported. > test=# select (1e-37::float4)*(1e-2::float4); > ERROR: type "real" value out of range: underflow > ============================================== > test=# select (1e-300::float8)*(1e-30::float8); > ?column? > ---------- > 0 > (1 row) > > test=# select (1e-300::float8)*(1e-20::float8); > ERROR: type "double precision" value out of range: underflow > ============================================== > test=# select ('INF'::float8-'INF'::float8); > ?column? > ---------- > NaN > (1 row) > > test=# select ('INF'::float8+'INF'::float8); > ERROR: type "double precision" value out of range: overflow This works fine now: test=> select ('INF'::float8+'INF'::float8); ?column? ---------- Infinity (1 row) > ============================================== > test=# select ('INF'::float4)::float8; > float8 > ---------- > Infinity > (1 row) > > test=# select ('INF'::float8)::float4; > ERROR: type "real" value out of range: overflow > ============================================== > test=# select cbrt('INF'::float4); > cbrt > ---------- > Infinity > (1 row) > > test=# select sqrt('INF'::float4); > ERROR: type "double precision" value out of range: overflow This works fine too: test=> select ('INF'::float8)::float4; float4 ---------- Infinity (1 row) > ============================================== > test=# select ((-32768::int8)::int2)%(-1::int2); > ?column? > ---------- > 0 > (1 row) > > test=# select ((-2147483648::int8)::int4)%(-1::int4); > ERROR: floating-point exception > DETAIL: An invalid floating-point operation was signaled. This probably > means an out-of-range result or an invalid operation, such > as division by zero. This was an interesting case. It turns out the value has to be INT_MIN, and the second value has to be -1. The exception happens, I think, because the CPU does the division first before getting the remainder, and INT_MIN / -1 is > INT_MAX, hence the error. I just special-cased it to return zero in the int4mod() code: test=> select ((-2147483648::int8)::int4)%(-1::int4); ?column? ---------- 0 (1 row) You can actually show the error without using int8: test=> select ((-2147483648)::int4) % (-1); ?column? ---------- 0 (1 row) The parentheses are required to make the value negative before the cast to int4. > ============================================== > test=# create table tt (ff float8); > CREATE TABLE > test=# insert into tt values (1e308),(1e308),(1e308); > INSERT 0 3 > test=# select * from tt; > ff > -------- > 1e+308 > 1e+308 > 1e+308 > (3 rows) > > test=# select avg(ff) from tt; > avg > ---------- > Infinity > (1 row) > > test=# select stddev(ff) from tt; > stddev > -------- > NaN > (1 row) I didn't study the aggregate cases. Does someone want to look those over? The attached patch fixes all the items I mentioned above. -- Bruce Momjian bruce@momjian.us EnterpriseDB http://www.enterprisedb.com + If your life is a hard drive, Christ can be your backup. + Index: src/backend/utils/adt/float.c =================================================================== RCS file: /cvsroot/pgsql/src/backend/utils/adt/float.c,v retrieving revision 1.131 diff -c -c -r1.131 float.c *** src/backend/utils/adt/float.c 23 Dec 2006 02:13:24 -0000 1.131 --- src/backend/utils/adt/float.c 24 Dec 2006 06:48:08 -0000 *************** *** 104,111 **** int extra_float_digits = 0; /* Added to DBL_DIG or FLT_DIG */ ! static void CheckFloat4Val(double val); ! static void CheckFloat8Val(double val); static int float4_cmp_internal(float4 a, float4 b); static int float8_cmp_internal(float8 a, float8 b); --- 104,111 ---- int extra_float_digits = 0; /* Added to DBL_DIG or FLT_DIG */ ! static void CheckFloat4Val(double val, bool isinf_args); ! static void CheckFloat8Val(double val, bool isinf_args); static int float4_cmp_internal(float4 a, float4 b); static int float8_cmp_internal(float8 a, float8 b); *************** *** 211,219 **** * raise an ereport() error if it is */ static void ! CheckFloat4Val(double val) { ! if (fabs(val) > FLOAT4_MAX) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("type \"real\" value out of range: overflow"))); --- 211,220 ---- * raise an ereport() error if it is */ static void ! CheckFloat4Val(double val, bool isinf_args) { ! /* If one of the input arguments was infinity, allow an infinite result */ ! if (fabs(val) > FLOAT4_MAX && (!isinf(val) || !isinf_args)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("type \"real\" value out of range: overflow"))); *************** *** 230,238 **** * raise an ereport() error if it is */ static void ! CheckFloat8Val(double val) { ! if (fabs(val) > FLOAT8_MAX) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("type \"double precision\" value out of range: overflow"))); --- 231,240 ---- * raise an ereport() error if it is */ static void ! CheckFloat8Val(double val, bool isinf_args) { ! /* If one of the input arguments was infinity, allow an infinite result */ ! if (fabs(val) > FLOAT8_MAX && (!isinf(val) || !isinf_args)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("type \"double precision\" value out of range: overflow"))); *************** *** 369,376 **** * if we get here, we have a legal double, still need to check to see if * it's a legal float4 */ ! if (!isinf(val)) ! CheckFloat4Val(val); PG_RETURN_FLOAT4((float4) val); } --- 371,377 ---- * if we get here, we have a legal double, still need to check to see if * it's a legal float4 */ ! CheckFloat4Val(val, true /* allow Inf */); PG_RETURN_FLOAT4((float4) val); } *************** *** 558,565 **** errmsg("invalid input syntax for type double precision: \"%s\"", orig_num))); ! if (!isinf(val)) ! CheckFloat8Val(val); PG_RETURN_FLOAT8(val); } --- 559,565 ---- errmsg("invalid input syntax for type double precision: \"%s\"", orig_num))); ! CheckFloat8Val(val, true /* allow Inf */); PG_RETURN_FLOAT8(val); } *************** *** 705,716 **** float8abs(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); - float8 result; - - result = fabs(arg1); ! CheckFloat8Val(result); ! PG_RETURN_FLOAT8(result); } --- 705,712 ---- float8abs(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); ! PG_RETURN_FLOAT8(fabs(arg1)); } *************** *** 725,731 **** result = ((arg1 != 0) ? -(arg1) : arg1); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 721,727 ---- result = ((arg1 != 0) ? -(arg1) : arg1); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 786,792 **** double result; result = arg1 + arg2; ! CheckFloat4Val(result); PG_RETURN_FLOAT4((float4) result); } --- 782,788 ---- double result; result = arg1 + arg2; ! CheckFloat4Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT4((float4) result); } *************** *** 798,804 **** double result; result = arg1 - arg2; ! CheckFloat4Val(result); PG_RETURN_FLOAT4((float4) result); } --- 794,800 ---- double result; result = arg1 - arg2; ! CheckFloat4Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT4((float4) result); } *************** *** 810,816 **** double result; result = arg1 * arg2; ! CheckFloat4Val(result); PG_RETURN_FLOAT4((float4) result); } --- 806,812 ---- double result; result = arg1 * arg2; ! CheckFloat4Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT4((float4) result); } *************** *** 829,835 **** /* Do division in float8, then check for overflow */ result = (float8) arg1 / (float8) arg2; ! CheckFloat4Val(result); PG_RETURN_FLOAT4((float4) result); } --- 825,831 ---- /* Do division in float8, then check for overflow */ result = (float8) arg1 / (float8) arg2; ! CheckFloat4Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT4((float4) result); } *************** *** 848,854 **** result = arg1 + arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 844,850 ---- result = arg1 + arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 861,867 **** result = arg1 - arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 857,863 ---- result = arg1 - arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 874,880 **** result = arg1 * arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 870,876 ---- result = arg1 * arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 892,898 **** result = arg1 / arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 888,894 ---- result = arg1 / arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 1142,1148 **** { float8 num = PG_GETARG_FLOAT8(0); ! CheckFloat4Val(num); PG_RETURN_FLOAT4((float4) num); } --- 1138,1144 ---- { float8 num = PG_GETARG_FLOAT8(0); ! CheckFloat4Val(num, isinf(num)); PG_RETURN_FLOAT4((float4) num); } *************** *** 1223,1229 **** float4 num = PG_GETARG_FLOAT4(0); int32 result; ! if (num < INT_MIN || num > INT_MAX) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); --- 1219,1225 ---- float4 num = PG_GETARG_FLOAT4(0); int32 result; ! if (num < INT_MIN || num > INT_MAX || isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); *************** *** 1242,1248 **** float4 num = PG_GETARG_FLOAT4(0); int16 result; ! if (num < SHRT_MIN || num > SHRT_MAX) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); --- 1238,1244 ---- float4 num = PG_GETARG_FLOAT4(0); int16 result; ! if (num < SHRT_MIN || num > SHRT_MAX || isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); *************** *** 1485,1491 **** result = sqrt(arg1); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1481,1487 ---- result = sqrt(arg1); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1539,1545 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("result is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1535,1541 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("result is out of range"))); ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 1569,1575 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("result is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1565,1571 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("result is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1598,1604 **** result = log(arg1); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1594,1600 ---- result = log(arg1); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1628,1634 **** result = log10(arg1); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1624,1630 ---- result = log10(arg1); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1653,1659 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1649,1655 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1678,1684 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1674,1680 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1703,1709 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1699,1705 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1729,1735 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1725,1731 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 1754,1760 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1750,1756 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1780,1786 **** errmsg("input is out of range"))); result = 1.0 / result; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1776,1782 ---- errmsg("input is out of range"))); result = 1.0 / result; ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1805,1811 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1801,1807 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1830,1836 **** (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1826,1832 ---- (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1846,1852 **** result = arg1 * (180.0 / M_PI); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1842,1848 ---- result = arg1 * (180.0 / M_PI); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 1872,1878 **** result = arg1 * (M_PI / 180.0); ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 1868,1874 ---- result = arg1 * (M_PI / 180.0); ! CheckFloat8Val(result, isinf(arg1)); PG_RETURN_FLOAT8(result); } *************** *** 2598,2604 **** float8 result; result = arg1 + arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2594,2600 ---- float8 result; result = arg1 + arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2610,2616 **** float8 result; result = arg1 - arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2606,2612 ---- float8 result; result = arg1 - arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2622,2628 **** float8 result; result = arg1 * arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2618,2624 ---- float8 result; result = arg1 * arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2639,2645 **** errmsg("division by zero"))); result = arg1 / arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2635,2641 ---- errmsg("division by zero"))); result = arg1 / arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2658,2664 **** result = arg1 + arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2654,2660 ---- result = arg1 + arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2671,2677 **** result = arg1 - arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2667,2673 ---- result = arg1 - arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2684,2690 **** result = arg1 * arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2680,2686 ---- result = arg1 * arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } *************** *** 2702,2708 **** result = arg1 / arg2; ! CheckFloat8Val(result); PG_RETURN_FLOAT8(result); } --- 2698,2704 ---- result = arg1 / arg2; ! CheckFloat8Val(result, isinf(arg1) || isinf(arg2)); PG_RETURN_FLOAT8(result); } Index: src/backend/utils/adt/int.c =================================================================== RCS file: /cvsroot/pgsql/src/backend/utils/adt/int.c,v retrieving revision 1.75 diff -c -c -r1.75 int.c *** src/backend/utils/adt/int.c 4 Oct 2006 00:29:59 -0000 1.75 --- src/backend/utils/adt/int.c 24 Dec 2006 06:48:09 -0000 *************** *** 1124,1129 **** --- 1124,1134 ---- ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); + + /* SELECT ((-2147483648)::int4) % (-1); causes a floating point exception */ + if (arg1 == INT_MIN && arg2 == -1) + PG_RETURN_INT32(0); + /* No overflow is possible */ PG_RETURN_INT32(arg1 % arg2);
pgsql-patches by date: