diff --git a/Makefile b/Makefile index be3c424..312c323 100755 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ TESTS = $(wildcard test/sql/*.sql) REGRESS_BRIN := $(shell pg_config --version | grep -qE "XL 9\.[5-9]| 10\.0" && echo brin-xl) REGRESS_BRIN += $(shell pg_config --version | grep -E "9\.[5-9]| 10\.0" | grep -qEv "XL" && echo brin) REGRESS_VERSION_SPECIFIC := $(shell pg_config --version | grep -qE "XL" && echo index-xl || echo index) -REGRESS = $(shell echo aggregate cast comparison overflow $(REGRESS_BRIN) $(REGRESS_VERSION_SPECIFIC)) +REGRESS = $(shell echo aggregate cast comparison overflow round $(REGRESS_BRIN) $(REGRESS_VERSION_SPECIFIC)) REGRESS_OPTS = --inputdir=test --outputdir=test --load-extension=fixeddecimal diff --git a/README.md b/README.md index 0546608..75f6d21 100755 --- a/README.md +++ b/README.md @@ -6,6 +6,51 @@ Works with PostgreSQL 9.5 and Postgres-XL 9.5 Overview -------- +XXX Remove this paragraph if accepted upstream. This feature fork adds half even + rounding. The rounding works accurately when parsing a string and casting + from the numeric data type. During division, extra digits are used to detect + if the result might be on a midpoint. If so, a modulus is used to indicate + midpoint rounding if there are only 0's past the midpoint (5). I am not sure + if the modulus is able to accurately indicate that there is a remainder if + the remainder is a string of 0's past the midpoint, and past the scale of + this calculation, changes to non-0 digits. This is where base 2 can cause + problems for someone expecting base 10 results. + Regardless, this feature significantly removes the bias present in + truncation and has less bias than the more biased rounding types. This may + be good enough for statistics, but financial calculations may want to type + cast to numeric before performing division or multiplication until this code + is proven to match decimal (base 10) calculations with the standard half + even rounding. These changes should not impact the performance advantage of + this data type outside of the operations described above. + It is my opinion that an exact data type should not offer inexact operations + without requiring something explicit like a type cast per the principle of + least surprise. I expect PostgreSQL not to violate that principle. I expect + that truncation was originally chosen because it may produce consistent + results between base 10 and base 2 operations, but I cannot think of many + use cases where truncation would be preferable over a low bias rounding + method, even with the base 10 consistency that it offers. + Note that exact means the ability to represent base 10 and its rounding + rules when fraction data underflows. Base 2 is not inexact in itself, it is + just that there are fractions in base 10 that can be exactly represented as + a number while base 2 cannot exactly represent the same number. But this + rule is true the other way around. Consider this, where float produces an + exact answer and numeric does not: +SELECT (1 * (987654321.0 * 123456789.0) / (0.123456789 / 998877665544332211.0)) / (987654321.0 * 123456789.0) * (0.123456789 / 998877665544332211.0) AS "Should be 1"; +SELECT (1::FLOAT * (987654321.0 * 123456789.0) / (0.123456789 / 998877665544332211.0)) / (987654321.0 * 123456789.0) * (0.123456789 / 998877665544332211.0) AS "Should be 1"; + The expectation that base 2 is less exact is probably due to the fact that + we display base 2 numbers in decimal notation. If numbers were commonly + displayed in binary notation, we would call float exact and decimal inexact. + Likewise, decimal is inexact for dozenal. + +XXX Also, fix numeric and the round function so that it uses unbiased rounding + (someone might be working on this). For example, check the results of these + before and after this patch: +SELECT (54::fixeddecimal / 0.03::fixeddecimal) / 54::fixeddecimal * 0.03::fixeddecimal AS "Should be 1"; +SELECT (54::numeric(8,4) / 0.03::numeric(8,4)) / 54::numeric(8,4) * 0.03::numeric(8,4) AS "Should be 1"; + +XXX Fix capitalization inconsistencies: FixedDecimal, Fixeddecimal, + fixeddecimal, FIXEDDECIMAL. + FixedDecimal is a fixed precision decimal type which provides a subset of the features of PostgreSQL's builtin NUMERIC type, but with vastly increased performance. Fixeddecimal is targeted to cases where performance and disk space @@ -31,7 +76,11 @@ from NUMERIC. although the underlying type is unable to represent the full range of of the 17th significant digit. -2. FIXEDDECIMAL always rounds towards zero. +2. FIXEDDECIMAL uses base 2 instead of base 10 for operations. It is exact + until you multiply with a number that exceeds the scale or divide. Then, + numbers past the scale are subject to base 2 representation and may round + differently than a base 10 operation would. See the Caution section for + details. 3. FIXEDDECIMAL does not support NaN. @@ -71,57 +120,49 @@ FIXEDDECIMAL_MULTIPLIER should be set to 10000. Doing this will mean that the absolute limits of the type decrease to a range of -922337203685477.5808 to 922337203685477.5807. +The rounding is half even to reduce bias and to match the rounding expectations +set by various accounting and computer standards. + Caution ------- FIXEDDECIMAL is mainly intended as a fast and efficient data type which will suit a limited set numerical data storage and retrieval needs. Complex -arithmetic could be said to be one of fixeddecimal's limits. As stated above -division always rounds towards zero. Please observe the following example: - -``` -test=# select '2.00'::fixeddecimal / '3.00'::fixeddecimal; - ?column? ----------- - 0.66 -(1 row) -``` +arithmetic could be said to be one of FIXEDDECIMAL's limits. As stated above +FIXEDDECIMAL uses base 2 for operations. This means that when using division or +multiplication with a non-zero fraction, FIXEDDECIMAL may produce results that +are inconsistent with the same operations as performed in base 10. -A workaround of this would be to perform all calculations in NUMERIC, and -ROUND() the result into the maximum scale of FIXEDDECIMAL: +A workaround of this would be to perform calculations that are not exclusively +addition and subtraction in NUMERIC, and ROUND() the result into the maximum +scale of FIXEDDECIMAL: ``` -test=# select round('2.00'::numeric / '3.00'::numeric, 2)::fixeddecimal; +test=# select round('18.00'::numeric / '59.00'::numeric, 2)::fixeddecimal; ?column? ---------- - 0.67 + 0.31 (1 row) ``` -It should also be noted that excess precision is ignored by fixeddecimal. -With a FIXEDDECIMAL_PRECISION of 2, any value after the 2nd digit following -the decimal point is completely ignored rather than rounded. The following -example demonstrates this: - -``` -test=# select '1.239'::fixeddecimal; - fixeddecimal --------------- - 1.23 -(1 row) -``` - -It is especially important to remember that this truncation also occurs during -arithmetic. Notice in the following example the result is 1120 rather than -1129: - -``` -test=# select '1000'::fixeddecimal * '1.129'::fixeddecimal; - ?column? ----------- - 1120.00 -(1 row) -``` +FIXEDDECIMAL uses an additional set of decimal digits to perform unbiased +rounding. This set only exists when using 128bit for multiplication and +division. When this additional set begins with 5, the remaining are 0's, the +remainder of the division is checked for any non-zero value. This check may not +be accurate 100% of the time. It is used to remove bias like a IEEE 754 data +type, but until the math is proven, it cannot offer the same guarantee. + +XXX Operations that can produce a fraction that overflows the scale and causes a + problem for this logic should be removed, or at least operate with and + return the numeric data type instead of FIXEDDECIMAL. Based on the history + of other data types, there is a good argument for making FIXEDDECIMAL / + FIXEDDECIMAL return numeric instead of FIXEDDECIMAL. For example, money + divided by money does not produce a money result. It is a ratio, which means + that it looses the money unit. However, there is a case to be made that + because this might not be a unit type, but rather might be a performance + type, it may not be subject to unit rules. Unfortunately, this + interpretation may be application/user specific and thus not have a single + correct answer. Installation ------------ diff --git a/fixeddecimal--1.0.0_base.sql b/fixeddecimal--1.0.0_base.sql index d23e59a..99ef16c 100755 --- a/fixeddecimal--1.0.0_base.sql +++ b/fixeddecimal--1.0.0_base.sql @@ -309,7 +309,7 @@ AS 'fixeddecimal', 'int4fixeddecimalmul' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION int4fixeddecimaldiv(INT4, FIXEDDECIMAL) -RETURNS DOUBLE PRECISION +RETURNS FIXEDDECIMAL AS 'fixeddecimal', 'int4fixeddecimaldiv' LANGUAGE C IMMUTABLE STRICT; @@ -405,7 +405,7 @@ AS 'fixeddecimal', 'int2fixeddecimalmul' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION int2fixeddecimaldiv(INT2, FIXEDDECIMAL) -RETURNS DOUBLE PRECISION +RETURNS FIXEDDECIMAL AS 'fixeddecimal', 'int2fixeddecimaldiv' LANGUAGE C IMMUTABLE STRICT; diff --git a/fixeddecimal.c b/fixeddecimal.c index 0813708..cdfc430 100755 --- a/fixeddecimal.c +++ b/fixeddecimal.c @@ -64,6 +64,10 @@ */ #define FIXEDDECIMAL_MAX_PRECISION 19 - FIXEDDECIMAL_SCALE +// The fraction midpoints for half even rounding. +#define FIXEDDECIMAL_FRAC_MID_POS (FIXEDDECIMAL_MULTIPLIER / 2LL) +#define FIXEDDECIMAL_FRAC_MID_NEG (FIXEDDECIMAL_FRAC_MID_POS * -1LL) + /* Define this if your compiler has _builtin_add_overflow() */ /* #define HAVE_BUILTIN_OVERFLOW */ @@ -72,7 +76,7 @@ #endif /* HAVE_BUILTIN_OVERFLOW */ /* Compiler must have a working 128 int type */ -typedef __int128 int128; +typedef __int128_t int128; #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; @@ -93,7 +97,7 @@ PG_FUNCTION_INFO_V1(fixeddecimalge); PG_FUNCTION_INFO_V1(fixeddecimal_cmp); PG_FUNCTION_INFO_V1(fixeddecimal_hash); PG_FUNCTION_INFO_V1(fixeddecimalum); -PG_FUNCTION_INFO_V1(fixeddecimalup); +//PG_FUNCTION_INFO_V1(fixeddecimalup); PG_FUNCTION_INFO_V1(fixeddecimalpl); PG_FUNCTION_INFO_V1(fixeddecimalmi); PG_FUNCTION_INFO_V1(fixeddecimalmul); @@ -155,6 +159,76 @@ static int64 scanfixeddecimal(const char *str, int *precision, int *scale); static FixedDecimalAggState *makeFixedDecimalAggState(FunctionCallInfo fcinfo); static void fixeddecimal_accum(FixedDecimalAggState *state, int64 newval); +/* Debug with: +SELECT dividend::numeric/divisor::numeric as exact, + dividend * 100 * 100 / divisor as num_x_mul, + (dividend * 100 * 100 / divisor)::int / 100 as div_by_mul, + (dividend * 100 * 100 / divisor)::int - ((dividend * 100 * 100 / divisor)::int / 100)::int * 100 as frac +FROM (SELECT 1, 8) AS inputs (dividend, divisor) + Use FIXEDDECIMAL_MULTIPLIER for rounding because a whole number is + requested, or eliminate the need for a multiplication operation. +*/ +static int128 round_half_even_no_remainder(int128 num_x_mul) { + int128 div_by_mul; + int frac; + + div_by_mul = num_x_mul / FIXEDDECIMAL_MULTIPLIER; + frac = num_x_mul - div_by_mul * FIXEDDECIMAL_MULTIPLIER; + + if (frac > FIXEDDECIMAL_FRAC_MID_NEG && frac < FIXEDDECIMAL_FRAC_MID_POS) { + return div_by_mul; + } else if (frac > FIXEDDECIMAL_FRAC_MID_POS) { + return div_by_mul + 1; + } else if (frac < FIXEDDECIMAL_FRAC_MID_NEG) { + return div_by_mul - 1; + } if (frac == FIXEDDECIMAL_FRAC_MID_POS) { + return div_by_mul + (div_by_mul & 1); // Odd + } if (frac == FIXEDDECIMAL_FRAC_MID_NEG) { + return div_by_mul - (div_by_mul & 1); // Odd + } + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Bug in round_half_even code? Ones is %d. Maybe an overflow occurred.", frac))); +} + +/* The rounding multiplier should be increased for this function only to reduce + * the expensive add_if_no_remainder code path that is required when the + * rounding fraction equals FIXEDDECIMAL_FRAC_MID_*, but keeping the rounding + * multiplier at FIXEDDECIMAL_MULTIPLIER makes things a little simpler for now. + * For example, the FRAC_MID constants (as used in this function, not the one + * above) need to be adjusted if this multiplier is different. Also, keeping + * this multiplier low will help vet the add_if_no_remainder code path. Remember + * to update the tests to ensure code coverage. */ +static int64 round_half_even(int128 num_x_mul, int64 *add_if_no_remainder) { + int128 div_by_mul; + int frac; + + div_by_mul = num_x_mul / FIXEDDECIMAL_MULTIPLIER; + frac = num_x_mul - div_by_mul * FIXEDDECIMAL_MULTIPLIER; + + if (div_by_mul != ((int64) div_by_mul)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("fixeddecimal out of range"))); + + if (frac > FIXEDDECIMAL_FRAC_MID_NEG && frac < FIXEDDECIMAL_FRAC_MID_POS) { + return div_by_mul; + } else if (frac > FIXEDDECIMAL_FRAC_MID_POS) { + return div_by_mul + 1; + } else if (frac < FIXEDDECIMAL_FRAC_MID_NEG) { + return div_by_mul - 1; + } if (frac == FIXEDDECIMAL_FRAC_MID_POS) { + *add_if_no_remainder = -!(div_by_mul & 1); + return div_by_mul + 1; + } if (frac == FIXEDDECIMAL_FRAC_MID_NEG) { + *add_if_no_remainder = !(div_by_mul & 1); + return div_by_mul - 1; + } + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("Bug in round_half_even code? Ones is %d. Maybe an overflow occurred.", frac))); +} + /*********************************************************************** ** ** Routines for fixeddecimal @@ -418,11 +492,38 @@ scanfixeddecimal(const char *str, int *precision, int *scale) } /* - * Eat into any excess precision digits. - * XXX These are ignored, should we error instead? + * Be consistent with the behavior of the other exact type with regard to the loss of precision, numeric: + * SELECT '1.129'::numeric(3, 2) + * 1.13 + * However, being consistent with something similar does not always make it right. */ + if (isdigit((unsigned char) *ptr)) + { + int is_zero = 1; + switch (*ptr++) { + case '6': + case '7': + case '8': + case '9': + fractionalpart++; // The fractionalpart does not have a sign, so always add. + break; + case '5': + while (isdigit((unsigned char) *ptr)) { + if (*ptr++ != '0') + is_zero = 0; + } + if (is_zero) { + if (fractionalpart & 1) // Odd + fractionalpart++; + } else { + fractionalpart++; + } + break; + } + } + /* consume any remaining digits */ while (isdigit((unsigned char) *ptr)) - ptr++, vscale++; + ptr++; } /* consume any remaining space chars */ @@ -775,13 +876,13 @@ fixeddecimalum(PG_FUNCTION_ARGS) PG_RETURN_INT64(result); } -Datum +/*Datum fixeddecimalup(PG_FUNCTION_ARGS) { int64 arg = PG_GETARG_INT64(0); PG_RETURN_INT64(arg); -} +}*/ Datum fixeddecimalpl(PG_FUNCTION_ARGS) @@ -853,7 +954,7 @@ fixeddecimalmul(PG_FUNCTION_ARGS) * FIXEDDECIMAL_MULTIPLIER, we must divide the result by this to get * the correct result. */ - result = (int128) arg1 * arg2 / FIXEDDECIMAL_MULTIPLIER; + result = round_half_even_no_remainder((int128) arg1 * arg2); if (result != ((int64) result)) ereport(ERROR, @@ -868,7 +969,8 @@ fixeddecimaldiv(PG_FUNCTION_ARGS) { int64 dividend = PG_GETARG_INT64(0); int64 divisor = PG_GETARG_INT64(1); - int128 result; + int64 result; + int64 add_if_no_remainder = 0; if (divisor == 0) { @@ -888,14 +990,16 @@ fixeddecimaldiv(PG_FUNCTION_ARGS) * this can't overflow, but we can end up with a number that's too big for * int64 */ - result = (int128) dividend * FIXEDDECIMAL_MULTIPLIER / divisor; + result = round_half_even((int128) dividend * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER) / divisor, &add_if_no_remainder); - if (result != ((int64) result)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("fixeddecimal out of range"))); + if (add_if_no_remainder) { + // XXX Does this catch cases where the non-zero fraction is beyond the + // scale (.015000000000000000000000000000000001) if such a result is possible? + if (((int128) dividend * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER)) % divisor == 0) + result += add_if_no_remainder; + } - PG_RETURN_INT64((int64) result); + PG_RETURN_INT64(result); } /* fixeddecimalabs() @@ -1038,11 +1142,12 @@ fixeddecimalint4mul(PG_FUNCTION_ARGS) Datum fixeddecimalint4div(PG_FUNCTION_ARGS) { - int64 arg1 = PG_GETARG_INT64(0); - int32 arg2 = PG_GETARG_INT32(1); + int64 dividend = PG_GETARG_INT64(0); + int32 divisor = PG_GETARG_INT32(1); int64 result; + int64 add_if_no_remainder = 0; - if (arg2 == 0) + if (divisor == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), @@ -1057,18 +1162,18 @@ fixeddecimalint4div(PG_FUNCTION_ARGS) * 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) + if (divisor == -1) { #ifdef HAVE_BUILTIN_OVERFLOW int64 zero = 0; - if (__builtin_sub_overflow(zero, arg1, &result)) + if (__builtin_sub_overflow(zero, dividend, &result)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("fixeddecimal out of range"))); #else - result = -arg1; + result = -dividend; /* overflow check (needed for INT64_MIN) */ - if (arg1 != 0 && SAMESIGN(result, arg1)) + if (dividend != 0 && SAMESIGN(result, dividend)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("fixeddecimal out of range"))); @@ -1077,9 +1182,19 @@ fixeddecimalint4div(PG_FUNCTION_ARGS) PG_RETURN_INT64(result); } + // Maybe? + if (result != ((int64) result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("fixeddecimal out of range"))); + /* No overflow is possible */ + result = round_half_even((int128) dividend * FIXEDDECIMAL_MULTIPLIER / divisor, &add_if_no_remainder); - result = arg1 / arg2; + if (add_if_no_remainder) { + if (((int128) dividend * FIXEDDECIMAL_MULTIPLIER) % divisor == 0) + result += add_if_no_remainder; + } PG_RETURN_INT64(result); } @@ -1180,10 +1295,12 @@ int4fixeddecimalmul(PG_FUNCTION_ARGS) Datum int4fixeddecimaldiv(PG_FUNCTION_ARGS) { - int32 arg1 = PG_GETARG_INT32(0); - float8 arg2 = (float8) PG_GETARG_INT64(1) / (float8) FIXEDDECIMAL_MULTIPLIER; + int32 dividend = PG_GETARG_INT32(0); + int64 divisor = PG_GETARG_INT64(1); + int64 result; + int64 add_if_no_remainder = 0; - if (arg2 == 0) + if (divisor == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), @@ -1192,8 +1309,15 @@ int4fixeddecimaldiv(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + result = round_half_even((int128) dividend * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER) / divisor, &add_if_no_remainder); + + if (add_if_no_remainder) { + if ((int128) dividend * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER) % divisor == 0) + result += add_if_no_remainder; + } + /* No overflow is possible */ - PG_RETURN_FLOAT8((float8) arg1 / arg2); + PG_RETURN_INT64(result); } Datum @@ -1292,11 +1416,12 @@ fixeddecimalint2mul(PG_FUNCTION_ARGS) Datum fixeddecimalint2div(PG_FUNCTION_ARGS) { - int64 arg1 = PG_GETARG_INT64(0); - int16 arg2 = PG_GETARG_INT16(1); + int64 dividend = PG_GETARG_INT64(0); + int16 divisor = PG_GETARG_INT16(1); int64 result; + int64 add_if_no_remainder = 0; - if (arg2 == 0) + if (divisor == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), @@ -1311,18 +1436,18 @@ fixeddecimalint2div(PG_FUNCTION_ARGS) * 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) + if (divisor == -1) { #ifdef HAVE_BUILTIN_OVERFLOW int64 zero = 0; - if (__builtin_sub_overflow(zero, arg1, &result)) + if (__builtin_sub_overflow(zero, dividend, &result)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("fixeddecimal out of range"))); #else - result = -arg1; + result = -dividend; /* overflow check (needed for INT64_MIN) */ - if (arg1 != 0 && SAMESIGN(result, arg1)) + if (dividend != 0 && SAMESIGN(result, dividend)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("fixeddecimal out of range"))); @@ -1331,8 +1456,19 @@ fixeddecimalint2div(PG_FUNCTION_ARGS) PG_RETURN_INT64(result); } + // Maybe? + if (result != ((int64) result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("fixeddecimal out of range"))); + /* No overflow is possible */ - result = arg1 / arg2; + result = round_half_even((int128) dividend * FIXEDDECIMAL_MULTIPLIER / divisor, &add_if_no_remainder); + + if (add_if_no_remainder) { + if (((int128) dividend * FIXEDDECIMAL_MULTIPLIER) % divisor == 0) + result += add_if_no_remainder; + } PG_RETURN_INT64(result); } @@ -1433,10 +1569,12 @@ int2fixeddecimalmul(PG_FUNCTION_ARGS) Datum int2fixeddecimaldiv(PG_FUNCTION_ARGS) { - int16 arg1 = PG_GETARG_INT16(0); - float8 arg2 = PG_GETARG_INT64(1) / (float8) FIXEDDECIMAL_MULTIPLIER; + int16 dividend = PG_GETARG_INT16(0); + int64 divisor = PG_GETARG_INT64(1); + int64 result; + int64 add_if_no_remainder = 0; - if (arg2 == 0) + if (divisor == 0) { ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), @@ -1445,8 +1583,15 @@ int2fixeddecimaldiv(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + result = round_half_even((int128) dividend * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER) / divisor, &add_if_no_remainder); + + if (add_if_no_remainder) { + if (((int128) dividend * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER)) % divisor == 0) + result += add_if_no_remainder; + } + /* No overflow is possible */ - PG_RETURN_INT64((float8) arg1 / arg2); + PG_RETURN_INT64(result); } /*---------------------------------------------------------- @@ -1485,7 +1630,7 @@ int4fixeddecimal(PG_FUNCTION_ARGS) Datum fixeddecimalint4(PG_FUNCTION_ARGS) { - int64 arg = PG_GETARG_INT64(0) / FIXEDDECIMAL_MULTIPLIER; + int128 arg = round_half_even_no_remainder(PG_GETARG_INT64(0)); if ((int32) arg != arg) ereport(ERROR, @@ -1506,7 +1651,7 @@ int2fixeddecimal(PG_FUNCTION_ARGS) Datum fixeddecimalint2(PG_FUNCTION_ARGS) { - int64 arg = PG_GETARG_INT64(0) / FIXEDDECIMAL_MULTIPLIER; + int128 arg = round_half_even_no_remainder(PG_GETARG_INT64(0)); if ((int16) arg != arg) ereport(ERROR, @@ -1533,25 +1678,27 @@ fixeddecimaltod(PG_FUNCTION_ARGS) Datum dtofixeddecimal(PG_FUNCTION_ARGS) { - float8 arg = PG_GETARG_FLOAT8(0) * FIXEDDECIMAL_MULTIPLIER; - int64 result; + float8 arg = PG_GETARG_FLOAT8(0) * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER); + int128 result; + + if ((int128) arg != arg) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("fixeddecimal out of range"))); - /* Round arg to nearest integer (but it's still in float form) */ - arg = rint(arg); + result = round_half_even_no_remainder((int128) arg); /* * Does it fit in an int64? Avoid assuming that we have handy constants * defined for the range boundaries, instead test for overflow by * reverse-conversion. */ - result = (int64) arg; - - if ((float8) result != arg) + if ((int64) result != result) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("fixeddecimal out of range"))); - PG_RETURN_INT64(result); + PG_RETURN_INT64((int64) result); } Datum @@ -1571,26 +1718,27 @@ fixeddecimaltof(PG_FUNCTION_ARGS) Datum ftofixeddecimal(PG_FUNCTION_ARGS) { - float4 arg = PG_GETARG_FLOAT4(0) * FIXEDDECIMAL_MULTIPLIER; - int64 result; - float8 darg; + float4 arg = PG_GETARG_FLOAT4(0) * (FIXEDDECIMAL_MULTIPLIER * FIXEDDECIMAL_MULTIPLIER); + int128 result; - /* Round arg to nearest integer (but it's still in float form) */ - darg = rint(arg); + if ((int128) arg != arg) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("fixeddecimal out of range"))); + + result = round_half_even_no_remainder((int128) arg); /* * Does it fit in an int64? Avoid assuming that we have handy constants * defined for the range boundaries, instead test for overflow by * reverse-conversion. */ - result = (int64) darg; - - if ((float8) result != darg) + if ((int64) result != result) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("fixeddecimal out of range"))); - PG_RETURN_INT64(result); + PG_RETURN_INT64((int64) result); } diff --git a/test/expected/index.out b/test/expected/index.out index f02aaf9..cfddd3d 100644 --- a/test/expected/index.out +++ b/test/expected/index.out @@ -7,7 +7,7 @@ INSERT INTO fixdec (id,d) VALUES(5, 0.12); INSERT INTO fixdec (id,d) VALUES(6, 1.23); INSERT INTO fixdec (id,d) VALUES(7, 12.34); INSERT INTO fixdec (id,d) VALUES(8, 123.45); -INSERT INTO fixdec (id,d) VALUES(9, 123.456); +INSERT INTO fixdec (id,d) VALUES(9, 123.451); -- Should fail CREATE UNIQUE INDEX fixdec_d_idx ON fixdec (d); ERROR: could not create unique index "fixdec_d_idx" diff --git a/test/expected/round.out b/test/expected/round.out new file mode 100644 index 0000000..11dd455 --- /dev/null +++ b/test/expected/round.out @@ -0,0 +1,1389 @@ +SELECT '-0.026'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.03 +(1 row) + +SELECT '-0.025'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT '-0.024'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT '-0.016'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT '-0.015'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT '-0.014'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT '-0.006'::FIXEDDECIMAL; + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT '-0.005'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT '-0.004'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT '0.004'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT '0.005'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT '0.006'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT '0.014'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT '0.015'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT '0.016'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT '0.024'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT '0.025'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT '0.026'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.03 +(1 row) + +SELECT '0.145'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.14 +(1 row) + +SELECT '0.1450'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.14 +(1 row) + +SELECT '0.14509'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.15 +(1 row) + +SELECT '0.1451'::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.15 +(1 row) + +SELECT '0.145'::numeric::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.14 +(1 row) + +SELECT '0.1450'::numeric::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.14 +(1 row) + +SELECT '0.14509'::numeric::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.15 +(1 row) + +SELECT '0.1451'::numeric::FIXEDDECIMAL; + fixeddecimal +-------------- + 0.15 +(1 row) + +SELECT CAST('-0.026'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.03 +(1 row) + +SELECT CAST('-0.025'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.024'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.016'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.015'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.014'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT CAST('-0.006'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT CAST('-0.005'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('-0.004'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.004'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.005'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.006'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT CAST('0.014'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT CAST('0.015'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.016'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.024'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.025'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.026'::FLOAT AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.03 +(1 row) + +SELECT CAST('-0.026'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.03 +(1 row) + +SELECT CAST('-0.025'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.024'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.016'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.015'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.014'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT CAST('-0.006'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT CAST('-0.005'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('-0.004'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.004'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.005'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.006'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT CAST('0.014'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT CAST('0.015'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.016'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.024'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.025'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.026'::FLOAT4 AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.03 +(1 row) + +SELECT CAST('-0.026'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.03 +(1 row) + +SELECT CAST('-0.025'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.024'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.016'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.015'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.02 +(1 row) + +SELECT CAST('-0.014'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT CAST('-0.006'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + -0.01 +(1 row) + +SELECT CAST('-0.005'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('-0.004'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.004'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.005'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.00 +(1 row) + +SELECT CAST('0.006'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT CAST('0.014'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.01 +(1 row) + +SELECT CAST('0.015'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.016'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.024'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.025'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.02 +(1 row) + +SELECT CAST('0.026'::NUMERIC AS FIXEDDECIMAL); + fixeddecimal +-------------- + 0.03 +(1 row) + +SELECT CAST('-2.6'::FIXEDDECIMAL AS INT); + int4 +------ + -3 +(1 row) + +SELECT CAST('-2.5'::FIXEDDECIMAL AS INT); + int4 +------ + -2 +(1 row) + +SELECT CAST('-2.4'::FIXEDDECIMAL AS INT); + int4 +------ + -2 +(1 row) + +SELECT CAST('-1.6'::FIXEDDECIMAL AS INT); + int4 +------ + -2 +(1 row) + +SELECT CAST('-1.5'::FIXEDDECIMAL AS INT); + int4 +------ + -2 +(1 row) + +SELECT CAST('-1.4'::FIXEDDECIMAL AS INT); + int4 +------ + -1 +(1 row) + +SELECT CAST('-0.6'::FIXEDDECIMAL AS INT); + int4 +------ + -1 +(1 row) + +SELECT CAST('-0.5'::FIXEDDECIMAL AS INT); + int4 +------ + 0 +(1 row) + +SELECT CAST('-0.4'::FIXEDDECIMAL AS INT); + int4 +------ + 0 +(1 row) + +SELECT CAST('0.4'::FIXEDDECIMAL AS INT); + int4 +------ + 0 +(1 row) + +SELECT CAST('0.5'::FIXEDDECIMAL AS INT); + int4 +------ + 0 +(1 row) + +SELECT CAST('0.6'::FIXEDDECIMAL AS INT); + int4 +------ + 1 +(1 row) + +SELECT CAST('1.4'::FIXEDDECIMAL AS INT); + int4 +------ + 1 +(1 row) + +SELECT CAST('1.5'::FIXEDDECIMAL AS INT); + int4 +------ + 2 +(1 row) + +SELECT CAST('1.6'::FIXEDDECIMAL AS INT); + int4 +------ + 2 +(1 row) + +SELECT CAST('2.4'::FIXEDDECIMAL AS INT); + int4 +------ + 2 +(1 row) + +SELECT CAST('2.5'::FIXEDDECIMAL AS INT); + int4 +------ + 2 +(1 row) + +SELECT CAST('2.6'::FIXEDDECIMAL AS INT); + int4 +------ + 3 +(1 row) + +SELECT CAST('-2.6'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -3 +(1 row) + +SELECT CAST('-2.5'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -2 +(1 row) + +SELECT CAST('-2.4'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -2 +(1 row) + +SELECT CAST('-1.6'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -2 +(1 row) + +SELECT CAST('-1.5'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -2 +(1 row) + +SELECT CAST('-1.4'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -1 +(1 row) + +SELECT CAST('-0.6'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + -1 +(1 row) + +SELECT CAST('-0.5'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 0 +(1 row) + +SELECT CAST('-0.4'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 0 +(1 row) + +SELECT CAST('0.4'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 0 +(1 row) + +SELECT CAST('0.5'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 0 +(1 row) + +SELECT CAST('0.6'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 1 +(1 row) + +SELECT CAST('1.4'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 1 +(1 row) + +SELECT CAST('1.5'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 2 +(1 row) + +SELECT CAST('1.6'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 2 +(1 row) + +SELECT CAST('2.4'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 2 +(1 row) + +SELECT CAST('2.5'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 2 +(1 row) + +SELECT CAST('2.6'::FIXEDDECIMAL AS SMALLINT); + int2 +------ + 3 +(1 row) + +SELECT '-0.26'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.03 +(1 row) + +SELECT '-0.25'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.24'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.16'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.15'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.14'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.06'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.05'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '-0.04'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.04'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.05'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.06'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.14'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.15'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.16'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.24'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.25'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.26'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + ?column? +---------- + 0.03 +(1 row) + +SELECT '-0.26'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.03 +(1 row) + +SELECT '-0.25'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.24'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.16'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.15'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.14'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.06'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.05'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '-0.04'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.04'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.05'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.06'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.14'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.15'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.16'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.24'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.25'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.26'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + ?column? +---------- + 0.03 +(1 row) + +SELECT '-0.26'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.03 +(1 row) + +SELECT '-0.25'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.24'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.16'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.15'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.14'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.06'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.05'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.00 +(1 row) + +SELECT '-0.04'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.04'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.05'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.06'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.14'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.15'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.16'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.24'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.25'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.26'::FIXEDDECIMAL / '10'::INT4; + ?column? +---------- + 0.03 +(1 row) + +-- fixeddecimalint4mul and int4fixeddecimalmul do not need rounding. +SELECT '-0.26'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.03 +(1 row) + +SELECT '-0.25'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.24'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.16'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.15'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-0.14'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.06'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-0.05'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.00 +(1 row) + +SELECT '-0.04'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.04'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.05'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.00 +(1 row) + +SELECT '0.06'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.14'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.01 +(1 row) + +SELECT '0.15'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.16'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.24'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.25'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.02 +(1 row) + +SELECT '0.26'::FIXEDDECIMAL / '10'::SMALLINT; + ?column? +---------- + 0.03 +(1 row) + +-- fixeddecimalint2mul and int2fixeddecimalmul do not need rounding. +SELECT '-26'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.03 +(1 row) + +SELECT '-25'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-24'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-16'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-15'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-14'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-06'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-05'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '-04'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '04'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '05'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '06'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '14'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '15'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '16'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '24'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '25'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '26'::SMALLINT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.03 +(1 row) + +SELECT '-26'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.03 +(1 row) + +SELECT '-25'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-24'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-16'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-15'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.02 +(1 row) + +SELECT '-14'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-06'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + -0.01 +(1 row) + +SELECT '-05'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '-04'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '04'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '05'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.00 +(1 row) + +SELECT '06'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '14'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.01 +(1 row) + +SELECT '15'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '16'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '24'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '25'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.02 +(1 row) + +SELECT '26'::INT / '1000'::FIXEDDECIMAL; + ?column? +---------- + 0.03 +(1 row) + +SELECT (54::fixeddecimal / 0.03::fixeddecimal) / 54::fixeddecimal * 0.03::fixeddecimal AS "Should be 1"; + Should be 1 +------------- + 1.00 +(1 row) + +WITH sums AS ( + SELECT SUM(dividend / divisor) AS most_precise, + --SUM(round_half_even(dividend / divisor, 2)) AS half_even_rounding, + SUM(dividend::fixeddecimal / divisor::fixeddecimal) AS fixeddecimal_rounding, + SUM( trunc(dividend / divisor, 2) ) AS trunc_rounding, + SUM( (dividend / divisor)::numeric(5,2) ) AS numeric_rounding + FROM generate_series(.00::numeric,2::numeric,.01::numeric) AS dividend, + generate_series(.01::numeric,2::numeric,.01::numeric) AS divisor +) +SELECT --most_precise - half_even_rounding AS half_even_error, + most_precise - fixeddecimal_rounding AS fixeddecimal_error, + most_precise - trunc_rounding AS trunc_error, + most_precise - numeric_rounding AS numeric_error +FROM sums; + fixeddecimal_error | trunc_error | numeric_error +-------------------------+--------------------------+------------------------- + -0.24794275896603177978 | 179.98205724103396822022 | -3.52794275896603177978 +(1 row) + +-- These should return 0 rows: +/* +SELECT *, + round_half_even( dividend/divisor, 2), + dividend::fixeddecimal / divisor::fixeddecimal +FROM generate_series(0::numeric, 100::numeric, 1::numeric) AS dividend, + generate_series(1::numeric, 100::numeric, 1::numeric) AS divisor +WHERE round_half_even( dividend/divisor, 2) != + dividend::fixeddecimal / divisor::fixeddecimal; + +SELECT *, + round_half_even( dividend/divisor, 2), + dividend::fixeddecimal / divisor::fixeddecimal +FROM generate_series(.00::numeric, 2::numeric, .01::numeric) AS dividend, + generate_series(.01::numeric, 2::numeric, .01::numeric) AS divisor +WHERE round_half_even( dividend/divisor, 2) != + dividend::fixeddecimal / divisor::fixeddecimal; +*/ diff --git a/test/sql/index.sql b/test/sql/index.sql index 3782593..32528fe 100644 --- a/test/sql/index.sql +++ b/test/sql/index.sql @@ -8,7 +8,7 @@ INSERT INTO fixdec (id,d) VALUES(5, 0.12); INSERT INTO fixdec (id,d) VALUES(6, 1.23); INSERT INTO fixdec (id,d) VALUES(7, 12.34); INSERT INTO fixdec (id,d) VALUES(8, 123.45); -INSERT INTO fixdec (id,d) VALUES(9, 123.456); +INSERT INTO fixdec (id,d) VALUES(9, 123.451); -- Should fail CREATE UNIQUE INDEX fixdec_d_idx ON fixdec (d); diff --git a/test/sql/round.sql b/test/sql/round.sql new file mode 100644 index 0000000..3837f3a --- /dev/null +++ b/test/sql/round.sql @@ -0,0 +1,278 @@ +SELECT '-0.026'::FIXEDDECIMAL; +SELECT '-0.025'::FIXEDDECIMAL; +SELECT '-0.024'::FIXEDDECIMAL; +SELECT '-0.016'::FIXEDDECIMAL; +SELECT '-0.015'::FIXEDDECIMAL; +SELECT '-0.014'::FIXEDDECIMAL; +SELECT '-0.006'::FIXEDDECIMAL; +SELECT '-0.005'::FIXEDDECIMAL; +SELECT '-0.004'::FIXEDDECIMAL; +SELECT '0.004'::FIXEDDECIMAL; +SELECT '0.005'::FIXEDDECIMAL; +SELECT '0.006'::FIXEDDECIMAL; +SELECT '0.014'::FIXEDDECIMAL; +SELECT '0.015'::FIXEDDECIMAL; +SELECT '0.016'::FIXEDDECIMAL; +SELECT '0.024'::FIXEDDECIMAL; +SELECT '0.025'::FIXEDDECIMAL; +SELECT '0.026'::FIXEDDECIMAL; + +SELECT '0.145'::FIXEDDECIMAL; +SELECT '0.1450'::FIXEDDECIMAL; +SELECT '0.14509'::FIXEDDECIMAL; +SELECT '0.1451'::FIXEDDECIMAL; + +SELECT '0.145'::numeric::FIXEDDECIMAL; +SELECT '0.1450'::numeric::FIXEDDECIMAL; +SELECT '0.14509'::numeric::FIXEDDECIMAL; +SELECT '0.1451'::numeric::FIXEDDECIMAL; + +SELECT CAST('-0.026'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.025'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.024'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.016'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.015'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.014'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.006'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.005'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('-0.004'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.004'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.005'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.006'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.014'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.015'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.016'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.024'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.025'::FLOAT AS FIXEDDECIMAL); +SELECT CAST('0.026'::FLOAT AS FIXEDDECIMAL); + +SELECT CAST('-0.026'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.025'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.024'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.016'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.015'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.014'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.006'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.005'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('-0.004'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.004'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.005'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.006'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.014'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.015'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.016'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.024'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.025'::FLOAT4 AS FIXEDDECIMAL); +SELECT CAST('0.026'::FLOAT4 AS FIXEDDECIMAL); + +SELECT CAST('-0.026'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.025'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.024'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.016'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.015'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.014'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.006'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.005'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('-0.004'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.004'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.005'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.006'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.014'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.015'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.016'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.024'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.025'::NUMERIC AS FIXEDDECIMAL); +SELECT CAST('0.026'::NUMERIC AS FIXEDDECIMAL); + +SELECT CAST('-2.6'::FIXEDDECIMAL AS INT); +SELECT CAST('-2.5'::FIXEDDECIMAL AS INT); +SELECT CAST('-2.4'::FIXEDDECIMAL AS INT); +SELECT CAST('-1.6'::FIXEDDECIMAL AS INT); +SELECT CAST('-1.5'::FIXEDDECIMAL AS INT); +SELECT CAST('-1.4'::FIXEDDECIMAL AS INT); +SELECT CAST('-0.6'::FIXEDDECIMAL AS INT); +SELECT CAST('-0.5'::FIXEDDECIMAL AS INT); +SELECT CAST('-0.4'::FIXEDDECIMAL AS INT); +SELECT CAST('0.4'::FIXEDDECIMAL AS INT); +SELECT CAST('0.5'::FIXEDDECIMAL AS INT); +SELECT CAST('0.6'::FIXEDDECIMAL AS INT); +SELECT CAST('1.4'::FIXEDDECIMAL AS INT); +SELECT CAST('1.5'::FIXEDDECIMAL AS INT); +SELECT CAST('1.6'::FIXEDDECIMAL AS INT); +SELECT CAST('2.4'::FIXEDDECIMAL AS INT); +SELECT CAST('2.5'::FIXEDDECIMAL AS INT); +SELECT CAST('2.6'::FIXEDDECIMAL AS INT); + +SELECT CAST('-2.6'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-2.5'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-2.4'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-1.6'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-1.5'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-1.4'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-0.6'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-0.5'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('-0.4'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('0.4'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('0.5'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('0.6'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('1.4'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('1.5'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('1.6'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('2.4'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('2.5'::FIXEDDECIMAL AS SMALLINT); +SELECT CAST('2.6'::FIXEDDECIMAL AS SMALLINT); + +SELECT '-0.26'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.25'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.24'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.16'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.15'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.14'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.06'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.05'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '-0.04'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.04'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.05'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.06'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.14'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.15'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.16'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.24'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.25'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; +SELECT '0.26'::FIXEDDECIMAL / '10.00'::FIXEDDECIMAL; + +SELECT '-0.26'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.25'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.24'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.16'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.15'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.14'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.06'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.05'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '-0.04'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.04'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.05'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.06'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.14'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.15'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.16'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.24'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.25'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; +SELECT '0.26'::FIXEDDECIMAL * '0.10'::FIXEDDECIMAL; + +SELECT '-0.26'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.25'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.24'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.16'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.15'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.14'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.06'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.05'::FIXEDDECIMAL / '10'::INT4; +SELECT '-0.04'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.04'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.05'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.06'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.14'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.15'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.16'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.24'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.25'::FIXEDDECIMAL / '10'::INT4; +SELECT '0.26'::FIXEDDECIMAL / '10'::INT4; + +-- fixeddecimalint4mul and int4fixeddecimalmul do not need rounding. + +SELECT '-0.26'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.25'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.24'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.16'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.15'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.14'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.06'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.05'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '-0.04'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.04'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.05'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.06'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.14'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.15'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.16'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.24'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.25'::FIXEDDECIMAL / '10'::SMALLINT; +SELECT '0.26'::FIXEDDECIMAL / '10'::SMALLINT; + +-- fixeddecimalint2mul and int2fixeddecimalmul do not need rounding. + +SELECT '-26'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-25'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-24'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-16'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-15'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-14'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-06'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-05'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '-04'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '04'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '05'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '06'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '14'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '15'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '16'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '24'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '25'::SMALLINT / '1000'::FIXEDDECIMAL; +SELECT '26'::SMALLINT / '1000'::FIXEDDECIMAL; + +SELECT '-26'::INT / '1000'::FIXEDDECIMAL; +SELECT '-25'::INT / '1000'::FIXEDDECIMAL; +SELECT '-24'::INT / '1000'::FIXEDDECIMAL; +SELECT '-16'::INT / '1000'::FIXEDDECIMAL; +SELECT '-15'::INT / '1000'::FIXEDDECIMAL; +SELECT '-14'::INT / '1000'::FIXEDDECIMAL; +SELECT '-06'::INT / '1000'::FIXEDDECIMAL; +SELECT '-05'::INT / '1000'::FIXEDDECIMAL; +SELECT '-04'::INT / '1000'::FIXEDDECIMAL; +SELECT '04'::INT / '1000'::FIXEDDECIMAL; +SELECT '05'::INT / '1000'::FIXEDDECIMAL; +SELECT '06'::INT / '1000'::FIXEDDECIMAL; +SELECT '14'::INT / '1000'::FIXEDDECIMAL; +SELECT '15'::INT / '1000'::FIXEDDECIMAL; +SELECT '16'::INT / '1000'::FIXEDDECIMAL; +SELECT '24'::INT / '1000'::FIXEDDECIMAL; +SELECT '25'::INT / '1000'::FIXEDDECIMAL; +SELECT '26'::INT / '1000'::FIXEDDECIMAL; + +SELECT (54::fixeddecimal / 0.03::fixeddecimal) / 54::fixeddecimal * 0.03::fixeddecimal AS "Should be 1"; + +WITH sums AS ( + SELECT SUM(dividend / divisor) AS most_precise, + --SUM(round_half_even(dividend / divisor, 2)) AS half_even_rounding, + SUM(dividend::fixeddecimal / divisor::fixeddecimal) AS fixeddecimal_rounding, + SUM( trunc(dividend / divisor, 2) ) AS trunc_rounding, + SUM( (dividend / divisor)::numeric(5,2) ) AS numeric_rounding + FROM generate_series(.00::numeric,2::numeric,.01::numeric) AS dividend, + generate_series(.01::numeric,2::numeric,.01::numeric) AS divisor +) +SELECT --most_precise - half_even_rounding AS half_even_error, + most_precise - fixeddecimal_rounding AS fixeddecimal_error, + most_precise - trunc_rounding AS trunc_error, + most_precise - numeric_rounding AS numeric_error +FROM sums; + +-- These should return 0 rows: + +/* +SELECT *, + round_half_even( dividend/divisor, 2), + dividend::fixeddecimal / divisor::fixeddecimal +FROM generate_series(0::numeric, 100::numeric, 1::numeric) AS dividend, + generate_series(1::numeric, 100::numeric, 1::numeric) AS divisor +WHERE round_half_even( dividend/divisor, 2) != + dividend::fixeddecimal / divisor::fixeddecimal; + +SELECT *, + round_half_even( dividend/divisor, 2), + dividend::fixeddecimal / divisor::fixeddecimal +FROM generate_series(.00::numeric, 2::numeric, .01::numeric) AS dividend, + generate_series(.01::numeric, 2::numeric, .01::numeric) AS divisor +WHERE round_half_even( dividend/divisor, 2) != + dividend::fixeddecimal / divisor::fixeddecimal; +*/