Extra parenthesis changing result of formula in SQL Server 2008
-
02-07-2021 - |
Question
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.
La solution
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!
Autres conseils
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)