aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2017-05-21 13:05:17 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2017-05-21 13:05:17 -0400
commitc101d83a3dd6d23f33bfbbbd696c0ea11abcbaf9 (patch)
treeb805961f8cb54619a815c124bbc21977eed20bdf /src
parentcb6a498e64374dbeb5688de02af2e020bad88305 (diff)
downloadpostgresql-c101d83a3dd6d23f33bfbbbd696c0ea11abcbaf9.tar.gz
postgresql-c101d83a3dd6d23f33bfbbbd696c0ea11abcbaf9.zip
Fix precision and rounding issues in money multiplication and division.
The cash_div_intX functions applied rint() to the result of the division. That's not merely useless (because the result is already an integer) but it causes precision loss for values larger than 2^52 or so, because of the forced conversion to float8. On the other hand, the cash_mul_fltX functions neglected to apply rint() to their multiplication results, thus possibly causing off-by-one outputs. Per C standard, arithmetic between any integral value and a float value is performed in float format. Thus, cash_mul_flt4 and cash_div_flt4 produced answers good to only about six digits, even when the float value is exact. We can improve matters noticeably by widening the float inputs to double. (It's tempting to consider using "long double" arithmetic if available, but that's probably too much of a stretch for a back-patched fix.) Also, document that cash_div_intX operators truncate rather than round. Per bug #14663 from Richard Pistole. Back-patch to all supported branches. Discussion: https://postgr.es/m/22403.1495223615@sss.pgh.pa.us
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/cash.c16
-rw-r--r--src/test/regress/expected/money.out38
-rw-r--r--src/test/regress/sql/money.sql10
3 files changed, 56 insertions, 8 deletions
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index b336185df7e..d329efaa9a8 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -621,7 +621,7 @@ cash_mul_flt8(PG_FUNCTION_ARGS)
float8 f = PG_GETARG_FLOAT8(1);
Cash result;
- result = c * f;
+ result = rint(c * f);
PG_RETURN_CASH(result);
}
@@ -636,7 +636,7 @@ flt8_mul_cash(PG_FUNCTION_ARGS)
Cash c = PG_GETARG_CASH(1);
Cash result;
- result = f * c;
+ result = rint(f * c);
PG_RETURN_CASH(result);
}
@@ -671,7 +671,7 @@ cash_mul_flt4(PG_FUNCTION_ARGS)
float4 f = PG_GETARG_FLOAT4(1);
Cash result;
- result = c * f;
+ result = rint(c * (float8) f);
PG_RETURN_CASH(result);
}
@@ -686,7 +686,7 @@ flt4_mul_cash(PG_FUNCTION_ARGS)
Cash c = PG_GETARG_CASH(1);
Cash result;
- result = f * c;
+ result = rint((float8) f * c);
PG_RETURN_CASH(result);
}
@@ -707,7 +707,7 @@ cash_div_flt4(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
- result = rint(c / f);
+ result = rint(c / (float8) f);
PG_RETURN_CASH(result);
}
@@ -756,7 +756,7 @@ cash_div_int8(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
- result = rint(c / i);
+ result = c / i;
PG_RETURN_CASH(result);
}
@@ -808,7 +808,7 @@ cash_div_int4(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
- result = rint(c / i);
+ result = c / i;
PG_RETURN_CASH(result);
}
@@ -858,7 +858,7 @@ cash_div_int2(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
- result = rint(c / s);
+ result = c / s;
PG_RETURN_CASH(result);
}
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index 538235c4cc2..efb48f1acd6 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -185,6 +185,44 @@ SELECT * FROM money_data;
$123.46
(1 row)
+-- rounding vs. truncation in division
+SELECT '878.08'::money / 11::float8;
+ ?column?
+----------
+ $79.83
+(1 row)
+
+SELECT '878.08'::money / 11::float4;
+ ?column?
+----------
+ $79.83
+(1 row)
+
+SELECT '878.08'::money / 11::int;
+ ?column?
+----------
+ $79.82
+(1 row)
+
+SELECT '878.08'::money / 11::smallint;
+ ?column?
+----------
+ $79.82
+(1 row)
+
+-- check for precision loss in division
+SELECT '90000000000000099.00'::money / 10::int;
+ ?column?
+---------------------------
+ $9,000,000,000,000,009.90
+(1 row)
+
+SELECT '90000000000000099.00'::money / 10::smallint;
+ ?column?
+---------------------------
+ $9,000,000,000,000,009.90
+(1 row)
+
-- Cast int4/int8 to money
SELECT 1234567890::money;
money
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 09b9476b706..f0db5fa4322 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -57,6 +57,16 @@ DELETE FROM money_data;
INSERT INTO money_data VALUES ('$123.459');
SELECT * FROM money_data;
+-- rounding vs. truncation in division
+SELECT '878.08'::money / 11::float8;
+SELECT '878.08'::money / 11::float4;
+SELECT '878.08'::money / 11::int;
+SELECT '878.08'::money / 11::smallint;
+
+-- check for precision loss in division
+SELECT '90000000000000099.00'::money / 10::int;
+SELECT '90000000000000099.00'::money / 10::smallint;
+
-- Cast int4/int8 to money
SELECT 1234567890::money;
SELECT 12345678901234567::money;