为什么“ SELECT POWER(10.0,38.0);”会引发算术溢出错误?


15

我正在更新我的IDENTITY溢出检查脚本以解决DECIMALNUMERIC IDENTITY

作为检查的一部分,我计算了每一IDENTITY列的数据类型范围的大小。我用它来计算该范围已用尽的百分比。对于DECIMALNUMERIC 该范围的大小2 * 10^p - 2,其中p是精度。

我用DECIMALand NUMERIC IDENTITY列创建了一堆测试表,并尝试如下计算它们的范围:

SELECT POWER(10.0, precision)
FROM sys.columns
WHERE 
       is_identity = 1
   AND type_is_decimal_or_numeric
;

这引发了以下错误:

Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric. 

我将其范围缩小到IDENTITY类型的列DECIMAL(38, 0)(即,具有最大精度),因此我POWER()直接尝试对该值进行计算。

以下所有查询

SELECT POWER(10.0, 38.0);
SELECT CONVERT(FLOAT, (POWER(10.0, 38.0)));
SELECT CAST(POWER(10.0, 38.0) AS FLOAT);

也导致了同样的错误。

  • 为什么SQL服务器尝试的输出转换POWER(),这是类型的FLOAT,以NUMERIC(尤其是FLOAT具有较高的优先级)?
  • 如何为所有可能的精度(当然包括)动态计算a DECIMALNUMERICcolumn 的范围p = 38

Answers:


18

POWER文档中

句法

POWER ( float_expression , y )

争论

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

y
是提高float_expression的能力y可以是精确数值或近似数值数据类型类别的表达式,比特数据类型除外。

返回类型

返回与float_expression中提交的类型相同的类型。例如,如果将十进制(2,0)作为float_expression提交,则返回的结果为十进制(2,0)。


float如有必要,将第一个输入隐式转换为。

float通过标准C运行时库(CRT)函数使用算术执行内部计算pow

然后,float来自的输出pow将转换回左操作数的类型(隐含为numeric(3,1)使用文字值10.0时的类型)。

float在您的情况下,使用显式的效果很好:

SELECT POWER(1e1, 38);
SELECT POWER(CAST(10 as float), 38.0);

无法将10 38的准确结果存储在SQL Server中,decimal/numeric因为它将需要39位精度(1后跟38个零)。最高精度为38。


23

我不会再干预Martin的回答,而是在此添加我的其余发现POWER()

坚持你的内裤。

前言

首先,我向您展示A 的MSDN文档POWER()

句法

POWER ( float_expression , y )

争论

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

返回类型

与相同float_expression

您可以通过阅读POWER()返回类型为的最后一行来得出结论FLOAT,但请再次阅读。float_expression是“ float类型或可以隐式转换为float的类型”。因此,尽管有其名称,float_expression但实际上可能是a FLOAT,a DECIMAL或an INT。由于的输出与的输出POWER()相同float_expression,因此它也可能是这些类型之一。

因此,我们有一个标量函数,其返回类型取决于输入。可以吗

观察结果

我向您展示了B,这是一个测试,POWER()证明了根据输入将其输出转换为不同数据类型的测试。

SELECT 
    POWER(10, 3)             AS int
  , POWER(1000000000000, 3)  AS numeric0     -- one trillion
  , POWER(10.0, 3)           AS numeric1
  , POWER(10.12305, 3)       AS numeric5
  , POWER(1e1, 3)            AS float
INTO power_test;

EXECUTE sp_help power_test;

DROP TABLE power_test;

相关结果是:

Column_name    Type      Length    Prec     Scale
-------------------------------------------------
int            int       4         10       0
numeric0       numeric   17        38       0
numeric1       numeric   17        38       1
numeric5       numeric   17        38       5
float          float     8         53       NULL

似乎正在发生的事情是POWER()转换float_expression为适合它的最小类型,不包括BIGINT

因此,SELECT POWER(10.0, 38);由于出现溢出错误而失败,因为10.0将其强制转换NUMERIC(38, 1)为不足以容纳10 38的结果。这是因为10 38扩展为小数点前有39位数字,而NUMERIC(38, 1)可以存储小数点前有37位数字和小数点后一位。因此,最大值NUMERIC(38, 1)可容纳10 37 - 0.1。

有了这种理解,我可以编写出另一个溢出失败,如下所示。

SELECT POWER(1000000000, 3);    -- one billion

10亿(相对于第一个示例中的1万亿,后者被强制转换为NUMERIC(38, 0))小到足以容纳INT。但是,将十亿加到三阶幂对于来说太大了INT,因此出现了溢出错误。

其他几个函数也表现出相似的行为,其中它们的输出类型取决于它们的输入:

  • 数学函数POWER()CEILING()FLOOR()RADIANS()DEGREES(),和ABS()
  • 系统功能表现形式NULLIF()ISNULL()COALESCE()IIF()CHOOSE(),和CASE表达式
  • 算术运算符:例如,当变量为指定的数据类型时,SELECT 2 * @MAX_INT;和和都会SELECT @MAX_SMALLINT + @MAX_SMALLINT;导致算术溢出。

结论

在这种情况下,解决方案是使用SELECT POWER(1e1, precision)...。由于1e1将其强制转换为FLOAT,因此可以适用于所有可能的精度,该精度可以容纳大量的数字

由于这些函数非常普遍,因此必须了解结果可能会被舍入或由于其行为而导致溢出错误,这一点很重要。如果您期望或依赖于特定的数据类型来输出,请根据需要显式转换相关输入。

因此,孩子们,既然您知道这一点,就可以继续前进并繁荣起来。

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.