SQL Server 2005数值精度损失

时间:2020-03-06 14:39:13  来源:igfitidea点击:

调试一些与财务相关的SQL代码时,发现了一个数字(24,8)数学精度的奇怪问题。

在MSSQL上运行以下查询,我们将获得A + B * C表达式结果为0.123457

选择一个,
B,
C,
A + B * C

(
SELECT CAST(0.12345678 AS NUMERIC(24,8))AS A,
CAST(0 AS NUMERIC(24,8))AS B,
CAST(500 AS NUMERIC(24,8))AS C
)T

因此,我们丢失了2个重要符号。尝试以不同的方式解决此问题,我得到了将中间乘法结果(为零!)转换为数值(24,8)的效果。

最后有一个解决方案。但是,我仍然有一个问题,为什么MSSQL会以这种方式运行,并且样本中实际上发生了哪种类型转换?

解决方案

正如浮点类型的加法不准确一样,如果超出精度,则十进制类型的乘积可能不准确(或者导致不准确)。请参阅数据类型转换以及十进制和数字。

由于我们将NUMERIC(24,8)和NUMERIC(24,8)相乘,并且SQL Server仅检查类型而不是内容,因此它可能会尝试保存潜在的16个非十进制数字(24 8)无法保存所有48位精度时(最大为38)。将它们中的两个结合起来,我们将获得32个非十进制数字,仅剩下6个十进制数字(38 32)。

因此,原始查询

SELECT A, B, C, A + B * C
FROM ( SELECT CAST(0.12345678 AS NUMERIC(24,8)) AS A,
  CAST(0 AS NUMERIC(24,8)) AS B,
  CAST(500 AS NUMERIC(24,8)) AS C ) T

减少到

SELECT A, B, C, A + D
FROM ( SELECT CAST(0.12345678 AS NUMERIC(24,8)) AS A,
  CAST(0 AS NUMERIC(24,8)) AS B,
  CAST(500 AS NUMERIC(24,8)) AS C,
  CAST(0 AS NUMERIC(38,6)) AS D ) T

同样,在NUMERIC(24,8)和NUMERIC(38,6)之间,SQL Server将尝试保存潜在的32位非十进制数字,因此A + D减小为

SELECT CAST(0.12345678 AS NUMERIC(38,6))

四舍五入后得到的值是0.123457.

按照eed3si9n指出的逻辑以及我们在问题中所说的,似乎进行数学运算时最好的方法是将它们提取到函数中,并在每次运算后指定精度,

在这种情况下,函数可能看起来像:

create function dbo.myMath(@a as numeric(24,8), @b as numeric(24,8), @c as numeric(24,8))
returns  numeric(24,8)
as
begin 
    declare @d as numeric(24,8)
    set @d = @b* @c
    return @a + @d
end