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

Nic*_*mas 16 sql-server-2008 sql-server datatypes t-sql type-conversion

我正在更新我的IDENTITY溢出检查脚本以考虑DECIMALNUMERIC IDENTITYcolumns

作为检查的一部分,我计算每一IDENTITY列的数据类型范围的大小;我用它来计算该范围的百分比已用完。因为DECIMALNUMERIC 该范围的大小2 * 10^p - 2p是精度在哪里。

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

SELECT POWER(10.0, precision)
FROM sys.columns
WHERE 
       is_identity = 1
   AND type_is_decimal_or_numeric
;
Run Code Online (Sandbox Code Playgroud)

这引发了以下错误:

Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric. 
Run Code Online (Sandbox Code Playgroud)

我将它缩小到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);
Run Code Online (Sandbox Code Playgroud)

也导致了同样的错误。

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

Nic*_*mas 24

与其进一步干预 Martin 的回答,我将在POWER()这里添加我的其他发现。

坚持你的内裤。

前言

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

句法

POWER ( float_expression , y )

参数

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

返回类型

一样float_expression

您可能会从阅读POWER()返回类型为 的最后一行得出结论FLOAT,但请再次阅读。float_expression是“浮点型或可以隐式转换为浮点型的类型”。因此,尽管它的名称,float_expression实际上可能是 a FLOAT、 aDECIMAL或 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;
Run Code Online (Sandbox Code Playgroud)

相关结果如下:

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
Run Code Online (Sandbox Code Playgroud)

似乎正在发生的是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
Run Code Online (Sandbox Code Playgroud)

10 亿(与第一个示例中的 1 万亿相比,它被强制转换为NUMERIC(38, 0))刚好小到可以放入INT. 然而,10 亿的三次幂对于 来说太大了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,它可以容纳大得离谱的数字

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

所以孩子们,既然你知道了这一点,你可能会继续前进并取得成功。


Mar*_*ith 19

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

然后将floatfrom的输出pow强制转换回左侧操作数的类型(numeric(3,1)当您使用字面值 10.0 时暗示是)。

float在您的情况下使用显式工作正常:

SELECT POWER(1e1, 38);
SELECT POWER(CAST(10 as float), 38.0);
Run Code Online (Sandbox Code Playgroud)

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