为什么 TSQL 为 POWER(2.,64.) 返回错误的值?

Tri*_*nko 14 sql-server azure-sql-database

select POWER(2.,64.)返回18446744073709552000而不是18446744073709551616. 它似乎只有 16 位精度(四舍五入第 17 位)。

即使使精度明确,select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))它仍然返回四舍五入的结果。

这似乎是一个非常基本的操作,因为它可以像这样以 16 位精度任意剥离。它可以正确计算的最高值仅为POWER(2.,56.),失败为POWER(2.,57.)。这里发生了什么?

真正可怕的是select 2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.;实际上返回了正确的值。简洁到此为止。

Jac*_*las 17

在线文档

POWER ( float_expression , y )  
Run Code Online (Sandbox Code Playgroud)

参数

float_expression 是 float 类型或可以隐式转换为 float类型的表达式

这意味着无论您作为第一个参数传递的任何内容都将float(53) 执行函数之前隐式转换为 a 。然而,情况并非(总是?)

如果是这样,它将解释精度的损失:

将使用科学记数法的浮点值转换为十进制或数字仅限于精度为 17 位的值。任何精度高于 17 的值都舍入为零。

另一方面,文字2.是类型numeric……:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
Run Code Online (Sandbox Code Playgroud)
| (无列名) |
| :--------------- |
| 数字 |

dbfiddle在这里

…乘法运算符返回具有更高优先级的参数的数据类型

似乎在 2016 (SP1) 上,保留了所有精度:

SELECT @@version;
GO
Run Code Online (Sandbox Code Playgroud)
| (无列名) |
| :------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 2016 年 10 月 28 日 18:17:30 <br> 版权所有 (c) Microsoft Corporation<br> Windows Server 上的 Express Edition(64 位) 2012 R2 标准 6.3 <X64>(内部版本 9600:)(管理程序)<br> |
SELECT POWER(2.,64.);
GO
Run Code Online (Sandbox Code Playgroud)
| (无列名) |
| :-------------------- |
| 18446744073709551616 |

dbfiddle在这里

…但在 2014 (SP2) 上,它们不是:

SELECT @@version;
GO
Run Code Online (Sandbox Code Playgroud)
| (无列名) |
| :------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------- |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 2016 年 6 月 17 日 19:14:09 <br> 版权所有 (c) Microsoft Corporation<br> Windows NT 上的 Express Edition(64 位) 6.3 <X64>(内部版本 9600:)(管理程序)<br> |
SELECT POWER(2.,64.);
GO
Run Code Online (Sandbox Code Playgroud)
| (无列名) |
| :-------------------- |
| 18446744073709552000 |

dbfiddle在这里


Pau*_*ite 14

2 64的结果完全可以表示float(并且real就此而言)。

当这个精确的结果被转换回numeric(第一个POWER操作数的类型)时,问题就出现了。

在引入数据库兼容性级别 130 之前,SQL Serverfloatnumeric隐式转换四舍五入到最多 17 位数字。

在兼容级别 130 下,转换过程中尽可能保留精度。知识库文章中记录了这一点:

SQL Server 2016 在处理某些数据类型和不常见操作方面的改进

要在 Azure SQL 数据库中利用此功能,您需要将 设置COMPATIBILITY_LEVEL为 130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;
Run Code Online (Sandbox Code Playgroud)

需要进行工作负载测试,因为新的安排不是万能的。例如:

SELECT POWER(10., 38);
Run Code Online (Sandbox Code Playgroud)

...应该抛出错误,因为 10 38不能存储numeric(最大精度为 38)。120兼容下会出现溢出错误,但130下的结果是:

99999999999999997748809823456034029568 -- (38 digits)
Run Code Online (Sandbox Code Playgroud)