如何在SQL Server中获得Float的精确字符表示?

时间:2016-05-11 20:54:23

标签: sql sql-server string floating-point

我们正在对从一个SQL Server迁移到另一个SQL Server的数据进行一些验证。我们正在验证的一件事是,一些数字数据已正确传输。数值数据在新系统中存储为float数据类型。

我们知道float数据类型存在许多问题,无法保证精确的数值准确性,并且不能使用与float数据的精确相等比较。我们无法控制数据库模式或数据类型,这些都是单独的问题。

我们在这个特定情况下要做的是验证某些比率值是否正确传输。其中一个具体的数据验证规则是所有比率都应该在小数点右边不超过4位的情况下传输。

因此,例如,有效比率如下:

.7542
1.5423

无效比率为:

.12399794301
12.1209377

我们想要做的是计算小数点右边的位数,并查找浮点值在其右边有四位以上的所有情况。我们一直在使用SUBSTRING,LEN,STR和其他几个函数来实现这一点,我相信如果我们将数字字段输入为十进制数字,我们将其转换为char。 但是,我们在尝试将float转换为char值时发现的是SQL Server似乎总是在它们之间转换为十进制。例如,有问题的字段在SQL Server企业管理器中查询时显示此值:

1.4667

尝试使用推荐的SQL Server函数转换为字符串:

LTRIM(RTRIM(STR(field_name, 22, 17)))

返回此值:

1.4666999999999999

我期望的值如果 SQL Server直接从float转换为char(我们可以随后修剪尾随的零):

1.4667000000000000

SQL Server中是否有任何方法可以直接从float转换为char而不经历看似中间转换为十进制的中间转换?我们还尝试了CAST和CONVERT函数,并收到了与STR函数类似的结果。

涉及的SQL Server版本:SQL Server 2012 SP2

谢谢。

5 个答案:

答案 0 :(得分:1)

您可以尝试将cast用于varchar

select case when
len(
substring(cast(col as varchar(100))
          ,charindex('.',cast(col as varchar(100)))+1
          ,len(cast(col as varchar(100)))
         )
   ) = 4
then 'true' else 'false' end
from tablename
where charindex('.',cast(col as varchar(100))) > 0

答案 1 :(得分:1)

您的验证规则似乎被误导了。

根据IEEE 754标准,SQL Server FLOATFLOAT(53)在内部存储为64位浮点数,53位尾数(“值”)加上指数。这53个二进制数字对应大约15个十进制数字。

浮点数的精度有限,这并不意味着它们本身“模糊”或不精确,但并非所有数字都可以精确表示,而是必须使用另一个数字。

例如,您的 1.4667 没有确切的表示形式,而是存储为二进制浮点数(精确地)对应于十进制数 1.466699999999999892708046900224871933460235595703125 。正确舍入到16位小数,即 1.4666999999999999 ,这正是你得到的。

由于“SQL Server中浮点值的确切字符表示”为 1.466699999999999892708046900224871933460235595703125 ,验证规则“小数点右边不超过4位”显然存在缺陷,至少如果你将它应用于“确切的字符表示”。

但是,您可以执行的操作是将存储的数字四舍五入到更少的小数位,以便隐藏小数结尾处的小错误。转换为圆括号为15而不是16位的字符表示(记住开头提到的那些“15位小数”?)会给你 1.466700000000000 ,然后你可以检查前四位后的所有小数是零。

答案 2 :(得分:0)

对于此特定数字,请勿使用STR(),并使用转换或强制转换为varchar。但是,一般情况下,在float中存储时总是存在精度问题...它是该数据类型存储的本质。您可以做的最好的是标准化为NUMERIC类型并与阈值范围(例如+/- .0001)进行比较。请参阅以下内容,了解不同转化的工作原理:

declare @float float = 1.4667
select  @float,
        convert(numeric(18,4), @float),
        convert(nvarchar(20), @float),
        convert(nvarchar(20), convert(numeric(18,4), @float)),
        str(@float, 22, 17),
        str(convert(numeric(18,4), @float)),
        convert(nvarchar(20), convert(numeric(18,4), @float))

答案 3 :(得分:0)

您可以尝试这样做,而不是转换为VarChar:转换为带有4个小数位的小数,并检查它是否与之前的值相同。

case when field_name <> convert(numeric(38,4), field_name) 
     then 1 
     else 0 
end

答案 4 :(得分:0)

你在这里遇到的问题是float是一个近似数字数据类型,精度约为7位数。这意味着它使用的存储量小于十进制数/数字值时接近该值。这就是为什么不对需要精确精度的值使用float的原因。 检查此示例:

DECLARE @t TABLE (
col FLOAT
)

INSERT into @t (col)
VALUES (1.4666999999999999)
,(1.4667)
,(1.12399794301)
,(12.1209377);

SELECT col
, CONVERT(NVARCHAR(MAX),col) AS chr
, CAST(col as VARBINARY) AS bin
, LTRIM(RTRIM(STR(col, 22, 17))) AS rec
FROM @t

如您所见,浮动1.4666999999999999二进制等于1.4667。对于您声明的需求,我认为此查询适合:

SELECT col
, RIGHT(CONVERT(NVARCHAR(MAX),col), LEN(CONVERT(NVARCHAR(MAX),col)) - CHARINDEX('.',CONVERT(NVARCHAR(MAX),col))) AS prec
from @t