T-SQL 十进制除法精度问题

29

有没有人知道为什么在使用SQLServer 2005时

SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,9),12499999.9999)

得到的结果为11.74438969709659,

但是当我将分母的小数位增加到15时,得到的答案却不够准确:

SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,15),12499999.9999)

给我 11.74438969


2
顺便提一下,Windows 计算器显示的结果是 11.744389697096595117576772760941。 - Ian G
6个回答

38

对于乘法,我们只需将每个参数中小数点的数量相加(使用纸笔计算),以确定输出小数点位数。

但是除法就像炸开了你的头一样。我现在要躺下了。

然而,在SQL术语中,它与预期完全相同。

--Precision = p1 - s1 + s2 + max(6, s1 + p2 + 1)
--Scale = max(6, s1 + p2 + 1)

--Scale = 15 + 38 + 1 = 54
--Precision = 30 - 15 + 9 + 54 = 72
--Max P = 38, P & S are linked, so (72,54) -> (38,20)
--So, we have 38,20 output (but we don use 20 d.p. for this sum) = 11.74438969709659
SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,9),12499999.9999)


--Scale = 15 + 38 + 1 = 54
--Precision = 30 - 15 + 15 + 54 = 84
--Max P = 38, P & S are linked, so (84,54) -> (38,8)
--So, we have 38,8 output = 11.74438969
SELECT CONVERT(DECIMAL(30,15),146804871.212533)/CONVERT(DECIMAL (38,15),12499999.9999)

如果您也遵循这个规则,您可以进行相同的数学计算,如果您将每个数字对视为

  • 146804871.212533000000000和12499999.999900000
  • 146804871.212533000000000和12499999.999900000000000

谢谢。说实话,对于我的特定问题,我使用了FLOAT(因为我不需要100%的准确性),但是当我使用小数时,我感到非常困惑。 - MT.
2
SQL Server在算术方面非常糟糕:( 因此,值得一提的是,如果您需要在T-SQL中进行准确的乘法和除法运算,最好的选择是:定义并使用CLR函数。请参见:http://www.skylinetechnologies.com/Insights/Skyline-Blog/March-2013/CLR-Functions-in-SQL-Server-A-Tutorial以获取良好的介绍。 - Veysel Ozdemir

19
简而言之,请使用DECIMAL(25,13),你将在所有计算中获得正确的精度:小数点前12位,小数点后13位。 规则是:p+s必须等于38,这样你就安全了! 为什么? 因为SQL Server中算术运算的实现非常糟糕!在他们修复它之前,请遵循这个规则。

有没有参考资料能够说明为什么这对所有计算都“没问题”? - MrEdmundo
1
选取 cast(1000000 as DECIMAL(38,19)) / cast(9223372036854775807 as DECIMAL(38,19)); << 正常 选取 cast(1000000 as DECIMAL(25,13)) / cast(9223372036854775807 as DECIMAL(25,13)); << 算术溢出 - Triynko

18

我注意到如果将除数转换为浮点数,那么它会给出正确的答案,例如:

select 49/30                   (result = 1)

将变成:

select 49/cast(30 as float)    (result = 1.63333333333333)

值得注意的是,您在使用此类型的转换时应该小心,不要盲目使用。十进制和浮点数以不同的方式存储数字表示,并且浮点数具有一些令人费解的影响,当您进行此操作时应该意识到这一点。例如:select cast(cast(0.1 as float) as decimal(38,18))输出结果为:0.100000000000000006这是因为0.1无法在二进制中准确表示,因此它存储了略微偏离的值。当您开始乘法或四舍五入时,这可能会产生影响,因为结果可能会略微偏离。 - Simon P Stevens

6
我们正在研究这个神奇的转换,
P&S是连接在一起的,所以:
(72,54) - >(38,29)
(84,54) - >(38,8)
假设(38,29)是打字错误,应该是(38,20),则以下是数学计算:
i. 72-38 = 34, ii. 54-34 = 20
i. 84-38 = 46, ii. 54-46 = 8
这就是推理:
i. 输出精度小于最大精度,则是要舍弃的数字。
ii. 然后,输出刻度减去我们要舍弃的内容,给出余下的输出刻度中的数字。
希望这可以帮助任何试图弄清楚这一点的人。

1
一个笔误(应该是 i. 84 - 38(不是58)= 46,ii. 54 - 46 = 8)。这对@Campey非常有用。 - George Joseph
谢谢@GeorgeJoseph,我在五月份就已经采纳了你的更正意见,但似乎忘记了感谢你! - Campey

4

转换表达式而不是参数。

select  CONVERT(DECIMAL(38,36),146804871.212533 / 12499999.9999)

2
以下内容可能有所帮助:

使用以下方法可能会有所帮助:

SELECT COL1 * 1.0 / COL2

你为什么这么认为呢? - default locale

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接