Extra parenthesis changing result of formula in SQL Server 2008
-
02-07-2021 - |
سؤال
In all other languages (arithmetic engines in general) putting an extra set of parenthesis around operators of same priority does not impact results. But recently in a testing project I noticed that MS SQL server changes the results in those cases. Please take a look at the query below, and let me know if you have any idea (or a setting in SQL Server administration) or any links to MSDN article explaining the behavior.
select (0.55 * 287.61 / 0.66) calc_no_parens
,(0.55 * (287.61 / 0.66)) calc_parens
,round(0.55 * 287.61 / 0.66,2) no_paren_round
,round(0.55 * (287.61 / 0.66),2) paren_round;
Results
Column Record 1
calc_no_parens 239.6750000
calc_parens 239.67499985
no_paren_round 239.6800000
paren_round 239.67000000
To me, first two of them should return 239.675, and round should give 239.68.
المحلول
You will get the desired result if you declare each value as Float
.
DECLARE @Float1 float, @Float2 float, @Float3 float;
SET @Float1 = 0.55;
SET @Float2 = 287.61;
SET @Float3 = 0.66;
select (@Float1 * @Float2 / @Float3) calc_no_parens
,(@Float1* (@Float2/ @Float3)) calc_parens
,round(@Float1 * @Float2/ @Float3,2) no_paren_round
,round(@Float1* (@Float2/ @Float3),2) paren_round;
Output
calc_no_parens calc_parens no_paren_round paren_round
239.675 239.675 239.68 239.68
You may want to see this article: So-called "exact" numerics are not at all exact!
نصائح أخرى
I can see what is happening, but I don't think there is a fix.
SQL calculates and stores each part of the function as a SQL data type (in this case it's a floating point number).
287.61/0.66 produces 435.7727272727272727272727272... which SQL will store as a floating point number to some degree of accuracy, however it isn't exact (after all, it's a floating point number).
For more info on floating point numbers: How is floating point stored? When does it matter?
Habib's answer made me thinking this has to be with decimal data types my columns are using. After a bit of research, I found this Precision, Scale, and Length (Transact-SQL)
As you can see in that article, division operation significantly changes the both scale and precision of resulting decimal. Then I tried an variation of my query, this time adding extra parenthesis around Multiplication operation.
select distinct (0.55 * 287.61 / 0.66) calc_no_parens
,(0.55 * (287.61 / 0.66)) calc_parens_div
,((0.55 * 287.61) / 0.66) calc_parens_mult
,round(0.55 * 287.61 / 0.66,2) no_paren_round
,round(0.55 * (287.61 / 0.66),2) paren_round
,round((0.55 * 287.61) / 0.66,2) paren_round2;
Results
Column Record 1
calc_no_parens 239.6750000
calc_parens_div 239.67499985
calc_parens_mult 239.6750000
no_paren_round 239.6800000
paren_round 239.67000000
paren_round2 239.6800000
So as long as division is the last operator in the formula we get correct answers. Its not a fix to the problem, but a learning to self in any future testing projects.
When you use numbers SQL try to convert them dynamically:
{
SELECT
0.55*(287.61 / 0.66) PrecisionError,
0.55* (CONVERT(NUMERIC(24,12), 287.61) / CONVERT(NUMERIC(24,12), 0.66)) NotPrecisionError
DECLARE @V SQL_VARIANT
SET @V = 0.55*(287.61 / 0.66)
SELECT
Value = @V
,[TYPE] = CONVERT(SYSNAME, sql_variant_property(@V, 'BaseType')) + '(' +
CONVERT(VARCHAR(10), sql_variant_property(@V, 'Precision')) + ',' +
CONVERT(VARCHAR(10), sql_variant_property(@V, 'Scale')) + ')'
SET @V = 0.55 * (CONVERT(NUMERIC(24,14), 287.61) / CONVERT(NUMERIC(24,14), 0.66))
SELECT
Value = @V
,[TYPE] = CONVERT(SYSNAME, sql_variant_property(@V, 'BaseType')) + '(' +
CONVERT(VARCHAR(10), sql_variant_property(@V, 'Precision')) + ',' +
CONVERT(VARCHAR(10), sql_variant_property(@V, 'Scale')) + ')'
}
RESULTS
PrecisionError NotPrecisionError
239.67499985 239.6750000000000
Value TYPE
239.67499985 numeric(14,8)
Value TYPE
239.6750000000000 numeric(38,13)