SQL服务器:使用数字文字进行计算
-
21-08-2019 - |
题
我用浮点计算做了一些测试,以尽量减少精度损失。我偶然发现了一个现象,我想在这里展示并希望得到解释。
当我写作时
print 1.0 / (1.0 / 60.0)
结果是
60.0024000960
当我编写相同的公式并进行显式转换时 float
print cast(1.0 as float) / (cast(1.0 as float) / cast(60.0 as float))
结果是
60
到目前为止,我认为带有小数位的数字文字会自动被视为 float
具有适当精度的值。投射到 real
显示与投射到相同的结果 float
.
- 是否有一些关于 SQL Server 如何计算数字文字的文档?
- 这些文字是什么数据类型?
- 我真的必须把他们投给
float
获得更好的精度(这对我来说听起来很讽刺:)? - 有没有比用强制转换弄乱我的公式更简单的方法?
解决方案
SQL Server 使用尽可能小的数据类型。
当你运行这个脚本时
SELECT SQL_VARIANT_PROPERTY(1.0, 'BaseType')
SELECT SQL_VARIANT_PROPERTY(1.0, 'Precision')
SELECT SQL_VARIANT_PROPERTY(1.0, 'Scale')
SELECT SQL_VARIANT_PROPERTY(1.0, 'TotalBytes')
您将看到 SQL Server 隐式使用 NUMERIC(2, 1) 数据类型。
除以 60.0 将结果转换为 NUMERIC(8, 6)。
最终计算将结果转换为 NUMERIC(17, 10)。
编辑
摘自 SQL Server 在线书籍 数据类型转换
在Transact-SQL语句中,使用最低精度和比例自动将具有小数点的常数自动转换为数字数据值。例如,将常数12.345转换为数字值,精度为5,比例为3。
其他提示
是的,您经常需要将它们投射到浮动以获得更好的精度。我对此的看法:
我认为应该了解一下幕后发生的事情,以便以后类似案件时参考。
带小数点(不包括科学记数法)的文字数值表示 Decimal 数据类型,它存储为尽可能小的 Decimal 类型。与 Lieven Keersmaekers 的引述相同:https://msdn.microsoft.com/en-us/library/ms191530%28SQL.90%29.aspx#_decimal
在Transact-SQL语句中,使用最低精度和比例自动将具有小数点的常数自动转换为数字数据值。例如,将常数12.345转换为数字值,精度为5,比例为3。
小数点右侧的尾随零指定比例。小数点左侧的前导零将被忽略。
一些例子:
1.0 -> Decimal(2,1)
60.0 -> Decimal(3,1)
1.00 -> Decimal(3,2)
01.0 -> Decimal (2,1)
另一点需要考虑的是 数据类型优先级。当运算符组合两个不同数据类型的表达式时,数据类型优先级规则指定优先级较低的数据类型转换为优先级较高的数据类型。还要考虑的另一点是,如果我们对 Decimal 类型进行算术运算,则得到的 Decimal 类型,即精度和小数位数取决于操作数和操作本身。这在文档中有描述 精度、比例和长度.
所以,括号中的表达式的一部分
( 1.0 / 60.0 ) is evaluated to 0.016666 and the resulting type is Decimal (8,6)
使用上述有关十进制表达式的精度和小数位数的规则。此外,还使用银行四舍五入或四舍五入到偶数。重要的是要注意 Decimal 和 float 类型使用不同的舍入。如果我们继续表达式
1.0 / 0.016666 is evaluated to 60.002400096 and the resulting type is Decimal (17,10)
因此,部分差异是由于十进制类型与浮点类型使用的舍入方式不同所致。
根据上述规则,只需在括号内使用一次强制转换就足够了。所有其他文字将根据数据类型优先级规则提升为浮点数。
1.0 / (1.0 / cast(60.0 as float))
还有一件重要的事情。即使这个浮点表达式也不能计算出精确的结果。这只是为了让前端(SSMS 或其他)将值四舍五入到(我猜)精度 6 位,然后截断尾随零。所以即1.000001 变为 1。
很简单,不是吗?
要编写常量浮点表达式,请尝试使用科学记数法:
select (1.0E0 / (1.0E0 / 60.0E0))
结果是 60。