在SQL Server中使用双强制转换数值数据类型的意外结果

时间:2018-07-10 12:22:07

标签: sql sql-server types

我最近在ETL流程中遇到了一个奇怪的案例,对我而言结果似乎不可预测。我读过Difference between numeric, float and decimal in SQL Server,但我认为这不是溢出或十进制精度问题。

场景:

SQL Server 2008 SP3中的源表“ test”,列a声明为数字(38,6)。

首先将结果转换为实数,然后转换为int。如果直接从数字转换为整数,则不会发生此问题。

结果:

SELECT a,CAST(a as real) as real_a,CAST(CAST(a as real) as int) as int_a FROM test;
  • a:778881838.810000
  • real_a:7.78819E + 08
  • int_a:778881856

在SQL Server 2017( sql fiddle )中运行的同一实验给出了以下内容: http://sqlfiddle.com/#!18/45aca/2

a: 778881838.81
real_a: 778881860
int_a: 778881856

我可以(模糊地)理解..19E + 08的情况,但是为什么双重转换情况下+18有所不同?这个数字对我来说似乎完全是任意的。

3 个答案:

答案 0 :(得分:2)

好的,首先,SQL Server 2017中real_a的结果为 not 778881860。确切地说,它是778881856,就像在SQL Server 2008中一样。 client 如何显示此浮点值是另一回事-Management Studio向我展示了7.788819E+08sqlcmd产生7.7888186E+8,显然SQL Fiddle完全使用了另一个库(我个人会遇到一个问题,因为它会掩盖大量数字!)

该值不是任意的。 REAL是单精度浮点类型,不能完全表示778881838.81。最接近的可表示值是778881856,因此是您的结果(下一个较低的可表示值是778881792)。无需强制转换为INT,您可以使用

查看此值
SELECT STR(CONVERT(REAL, CONVERT(NUMERIC(38, 6), 778881838.810000)), 40, 16)

778881856.0000000000000000

您对“ double”一词的使用使我认为您将FLOAT与它混淆了,FLOAT是双精度浮点类型。 SELECT STR(CONVERT(FLOAT, CONVERT(NUMERIC(38, 6), 778881838.810000)), 40, 16) 778881838.8099999400000000 也不能完全代表此值,但它更接近:

INT

将此值转换为778881838会产生(截断的)NUMERIC。 (此截断是documented,在转换为ROUND时不会发生;如果您更愿意使用778881839,则需要先{{1}}进行转换。)

答案 1 :(得分:1)

其他想要在本地进行测试的人的简单示例:

DECLARE  @test numeric (38,6)='778881838.810000'
SELECT @test as [Original],CAST(@test as real) as real_a,CAST(CAST(@test as real) as int) as int_a;

Original            real_a          int_a
778881838.810000    7.788819E+08    778881856

您可能需要Microsoft的人员来说明SQL引擎内部的工作方式(当然还要知道他们为什么做出此决定),但是我会在理由上作文章:

如果在第一次强制转换时以科学计数法输出,然后需要将其转换为整数,则将整数设置为将导致该科学计数法的最小值。它以6而不是5结尾,因为舍入5并不能始终对所有情况进行舍入(例如Alternating tie-breaking)。

但是,无论出于何种原因,如果精度很重要,则应该显式转换为具有定义精度的数字数据类型。

答案 2 :(得分:0)

当您想从float或real转换为字符数据时,使用STR字符串函数通常比CAST()更有用。这是因为STR支持对格式进行更多控制。有关更多信息,请参见STR(Transact-SQL)和函数(Transact-SQL)。

请找到以下链接

USE STR Instead of real

STR example 使用以下查询:-

SELECT a,STR(a ,38,6) as real_a,CAST(CAST(a as real) as int) as int_a FROM test;

如果发现任何问题,请告诉我。