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


14

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

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

这样看来,以任意精度将其任意掉落是一个非常基本的操作。它只能正确计算出的最高值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.;实际上返回了正确的值。非常简洁。


Answers:


17

在线文档中

POWER ( float_expression , y )  

争论

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
| (无列名)|
| :--------------- |
| 数字|

dbfiddle 在这里

…并且乘法运算符返回优先级较高的参数的数据类型

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

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

dbfiddle 在这里

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

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

dbfiddle 在这里


1
因此,基本上,POWER函数对于需要超过17位精度的任何东西都没有用。这就是为什么它能产生正确结果POWER(2.,56.) = 72057594037927936但没有更高结果的原因。我想我必须编写自己的POWER函数,使其在循环中相乘,哈哈。
Triynko '17

14

2 64的结果可以准确地表示为float(并且real就此而言)。

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

在引入数据库兼容性级别130之前,SQL Server已将隐式转换四舍五入floatnumeric最大17位数字。

在兼容级别130下,转换过程中将保留尽可能高的精度。知识库文章中对此进行了记录:

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

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

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

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

SELECT POWER(10., 38);

...应该抛出一个错误,因为不能存储10 38numeric(最大精度为38)。在120兼容性下会导致溢出错误,但在130兼容性下会导致:

99999999999999997748809823456034029568 -- (38 digits)

2

通过一点数学,我们可以找到一种解决方法。奇怪的是n

2 ^ n 
= 2 ^ (2k + 1)
= 2 * (2 ^ 2k)
= 2 * (2 ^ k) * (2 ^ k)

甚至n

2 ^ n 
= 2 ^ (2k)
= 1 * (2 ^ 2k)
= 1 * (2 ^ k) * (2 ^ k)

用T-SQL编写的一种方式:

DECLARE @exponent INTEGER = 57;

SELECT (1 + @exponent % 2) * POWER(2., FLOOR(0.5 * @exponent)) * POWER(2., FLOOR(0.5 * @exponent));

在SQL Server 2008上测试,结果是144115188075855872,而不是144115188075855870。

一直到指数达到113为止。看起来NUMERIC(38,0)最多可以存储2 ^ 126,因此覆盖范围不大,但是如果需要,可以将公式分成更多部分。


0

只是为了好玩,递归CTE解决方案:

with 
  prm (p, e) as           -- parameters, to evaluate: p**e
    (select 2, 64),       -- (2 ** 64)  
  pow (power, exp) as 
    (select cast(p as numeric(30,0)), 
            e
     from prm 
     union all 
     select cast(power * power * (case when exp % 2 = 0 then 1 else p end) 
                 as numeric(30,0)), 
            exp / 2 
     from prm, pow 
     where exp > 1 
    ) 
select power 
from pow 
where exp = 1 ;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.