【问题标题】:How Can I Get An Exact Character Representation of a Float in SQL Server?如何在 SQL Server 中获取浮点数的精确字符表示?
【发布时间】:2016-05-11 20:54:22
【问题描述】:

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

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

在这种特定情况下,我们尝试做的是验证某些比率值是否已正确传输。具体的数据验证规则之一是所有的比率都应该在小数点右边不超过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 中是否有任何方法可以直接从浮点数转换为字符,而无需经过中间转换为十进制的过程?我们还尝试了 CAST 和 CONVERT 函数,得到的结果与 STR 函数相似。

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

谢谢。

【问题讨论】:

  • 我猜这个值实际上是1.4666999999999999。当您在企业管理器中选择它时,它会为您四舍五入。
  • 假设这些数字根据 IEEE 754 标准在内部存储为 64 位浮点数,则 1.4667 将存储为(完全)对应于十进制数 1.466699999999999892708046900224871933460235595703125 的二进制表示。正确四舍五入到小数点后 16 位,即 1.4666999999999999,这正是你得到的。或许你可以四舍五入到更少的小数位,这样最后这些小错误就会被隐藏?
  • 谢谢大家。我相信你们俩都是正确的,问题的根源是 a) 没有可以表示为浮点数的精确数字 1.4667 和 b) 企业管理器实际上在显示这些数字之前对其进行了四舍五入。 (stackoverflow.com/questions/8099575/…) 提供了更多关于 SQL Server 管理工作室中舍入的背景知识。我们正在尝试使用链接文章中提到的其他 SQL 客户端(例如 SQLCMD)来验证我们获得的输出。

标签: sql sql-server string floating-point


【解决方案1】:

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

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

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

例如,您的 1.4667 没有精确的表示,而是存储为二进制浮点数,(完全)对应于十进制数 1.4666999999999999892708046900224871933460235595703125强>。正确四舍五入到小数点后 16 位,即 1.4666999999999999,这正是你得到的。

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

但是,您可以做的是将存储的数字四舍五入到更少的小数位,以便隐藏小数末尾的小错误。转换为四舍五入为 15 而不是 16 位的字符表示(还记得开头提到的“15 位小数”吗?)将为您提供 1.466700000000000,然后您可以检查前四位之后的所有小数是零。

【讨论】:

    【解决方案2】:

    您可以尝试使用castvarchar

    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
    

    【讨论】:

      【解决方案3】:

      对于这个特定的数字,不要使用 STR(),而是使用转换或强制转换为 varchar。但是,一般来说,在浮点数中存储时总是会遇到精度问题……这是该数据类型存储的本质。您可以做的最好的事情是标准化为 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))
      

      【讨论】:

        【解决方案4】:

        您可以尝试以下操作,而不是转换为 VarChar:转换为具有 4 个小数位的小数,并检查它是否与以前的值相同。

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

        【讨论】:

        • 虽然这是一件值得验证的有趣事情,但这并不是我要问的问题。我们已经看到,这种检查在相当多的情况下都会失败,这仅仅是因为浮点数在转换为十进制时的工作方式。那是一个单独的问题。对于这个问题,我只想知道如何获得 SQL Server 中浮点值的精确字符表示。
        • @magnum_pi:嗯,你知道 FLOAT 不精确,但想要一个精确的表示:) 你可以减去两个值并检查差异是否超出边距-
        • @dnoeth:好吧,公平地说,FLOAT 值精确的。只是并非所有数字都可以使用存在的确切值来精确表示。
        • 问题不是关于浮点数的精确抽象表示。问题是“为什么我不能得到似乎是存储在 SQL Server 中的浮点值的字符转换输出,即 '1.4667'?”问题原来是我的问题假设“1.4667”是存储在 SQL Server 内部的实际浮点值,但事实并非如此。我没有意识到这一点,因为我没有意识到 SQL Server Management Studio 正在舍入它的浮点数显示。
        【解决方案5】:

        您在这里遇到的问题是浮点数是一种近似数字数据类型,精度约为七位数。这意味着它在使用比十进制/数字更少的存储空间时接近该值。这就是为什么您不对需要精确精度的值使用浮点数的原因。 检查这个例子:

        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
        

        【讨论】:

        • 一个小细节:SQL Server中的数据类型FLOAT相当于FLOAT(53),尾数为53位,相当于很多编程语言所说的“double”,而不是“float” ”。这相当于大约 15 个十进制数字,而不是 7 个。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-09
        • 2023-04-09
        • 2019-09-26
        相关资源
        最近更新 更多