【问题标题】:SQL Server numbers rounding issueSQL Server 数字舍入问题
【发布时间】:2016-12-21 02:50:23
【问题描述】:

这是 [TSQL number rounding issue 的后续问题。这是相同的代码:

IF OBJECT_ID(N'dbo.rounding_testing') IS NOT NULL
    DROP FUNCTION dbo.rounding_testing;
GO
CREATE FUNCTION dbo.rounding_testing
(
    @value FLOAT,
    @digit INT
)
RETURNS FLOAT
BEGIN
    DECLARE
        @factor FLOAT,
        @result FLOAT;
    SELECT @factor = POWER(10, @digit);

    SELECT @result = FLOOR(@value * @factor + 0.4);

    RETURN @result;
END;
GO

SELECT dbo.rounding_testing(5.7456, 3);
SELECT FLOOR(5.7456 * 1000 + 0.4);

当你执行代码时你会得到:

5745
5746

但是,当您在这样的函数中将数据类型从 float 更改为 real 时:

IF OBJECT_ID(N'dbo.rounding_testing') IS NOT NULL
    DROP FUNCTION dbo.rounding_testing;
GO
CREATE FUNCTION dbo.rounding_testing
(
    @value REAL,
    @digit INT
)
RETURNS REAL
BEGIN
    DECLARE
        @factor REAL,
        @result REAL;
    SELECT @factor = POWER(10, @digit);
    SELECT @result = FLOOR(@value * @factor + 0.4);
    RETURN @result;
END;
GO

SELECT dbo.rounding_testing(5.7456, 3);
SELECT FLOOR(5.7456 * 1000 + 0.4);

你会在执行时得到这个:

5746
5746

关于那个问题下的两个答案,我又做了一些测试,发现自己还是不清楚。首先我想说我已经阅读了有关float and real typesnumeric and decimal types 的msdn 文档。而且我现在知道 SQL Server 如何在内部存储它们。对于float and real types,使用 IEEE 754 标准。对于decimal and numeric types,请参阅How does SQL Server store decimal type values internally?。我想知道在 float 案例中哪个 EXACT 步骤导致了精度损失。所以我创建了一个这样的表:

USE tempdb;
GO

IF OBJECT_ID('dbo.mytable') IS NOT NULL
    DROP TABLE dbo.mytable;
CREATE TABLE dbo.mytable
(
    a NUMERIC(5, 4),
    b FLOAT,
    c FLOAT,
    d FLOAT,
    e FLOAT,
    f REAL,
    g REAL,
    h REAL,
    i REAL
);
GO

比我手动将中间数据插入到这个表中。

INSERT INTO dbo.mytable
VALUES(
    5.7456,
    CAST(5.7456 AS FLOAT),
    CAST(POWER(10, 3) AS FLOAT),
    CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) AS FLOAT),
    CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) + 0.4 AS FLOAT),
    CAST(5.7456 AS REAL),
    CAST(POWER(10, 3) AS REAL),
    CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) AS REAL),
    CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) + 0.4 AS REAL));

之后我使用DBCC PAGE 调查我插入的行。下面是该行的原始数据:

0000000000000000:   10003900 0170e000 002497ff 907efb16 40000000  ..9..pà..$ÿ.~û.@...
0000000000000014:   0000408f 40999999 999971b6 40ffffff ffff71b6  ..@.@.....q¶@ÿÿÿÿÿq¶
0000000000000028:   40f5dbb7 4000007a 44cd8cb3 450090b3 45090000  @õÛ·@..zDͳE..³E  ..
000000000000003C:   00                                            .      

这是对原始数据的解释:

Column Stuff inserted                                                          Hex (little endian)     Interpretation
------ ----------------------------------------------------------------------- ----------------------- --------------                                                                                  
a      5.7456                                                                  01 70 E0 00 00          Decimal 57456, the decimal point position is stored in catalog view                                                                    
b      CAST(5.7456 AS FLOAT)                                                   24 97 FF 90 7E FB 16 40 IEEE 754 double precision format, 5.7456                                                                             
c      CAST(POWER(10, 3) AS FLOAT)                                             00 00 00 00 00 40 8F 40 IEEE 754 double precision format, 1000                                                                           
d      CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) AS FLOAT)      99 99 99 99 99 71 B6 40 IEEE 754 double precision format, 5745.6                                                                             
e      CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) + 0.4 AS FLOAT)FF FF FF FF FF 71 B6 40 IEEE 754 double precision format, 5746                                                                             
f      CAST(5.7456 AS REAL)                                                    F5 DB B7 40             IEEE 754 single precision format, 5.7456                                                                 
g      CAST(POWER(10, 3) AS REAL)                                              00 00 7A 44             IEEE 754 single precision format, 1000                                                                   
h      CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) AS REAL)         CD 8C B3 45             IEEE 754 single precision format, 5745.6                            
i      CAST(CAST(5.7456 AS REAL) * CAST(POWER(10, 3) AS REAL) + 0.4 AS REAL))  00 90 B3 45             IEEE 754 single precision format, 5746                            

从十六进制解释来看,在我看来,无论是float 还是real,任何步骤都没有精度损失。那么精度损失究竟来自哪里呢?

【问题讨论】:

  • 如果您阅读文档和优先顺序,答案就很清楚了。好问题,当我们质疑为什么我们知道我们认为我们知道的东西时,我喜欢它。
  • 我不能告诉你 为什么 浮动很糟糕(他们就是这样做的),但我可以告诉你,虽然 CAST(CAST(5.7456 AS FLOAT) * CAST(POWER(10, 3) AS FLOAT) + 0.4 AS FLOAT) 说它的值是 5746,但它的实际值是5745.9999999999991
  • 编辑:需要明确的是,精度损失发生在您乘以 @factor * @value(如果您将其转换为 decimal(38, 34))作为 5745.5999999999995 的点上。将 0.4 添加到此结果为 5745.9999999999991。我猜这与浮点数与其他数据类型的存储方式不同以及由此造成的精度损失有关。

标签: sql-server numbers rounding precision type-conversion


【解决方案1】:

最接近 5.7456 的实数(单精度)值是十六进制 40b7dbf5,即十进制的 5.745600223541259765625。

最接近 5.7456 的浮点(双精度)值是十六进制 4016fb7e90ff9724,即 5.745599999999999596411726088263094425201416015625 十进制。

(使用我的floating-point converter 进行验证:输入 5.7456 并检查“双精度”和“单精度”框,然后选择“十进制”和“原始十六进制”输出框。)

您可以看到双精度值小于 5.7456,这是您的问题的根源(即为什么您得到 5745 作为您的答案)。

单精度计算 5.7456 * 1000 为 5745.60009765625,双精度计算为 5745.5999999999994543031789362430572509765625。

0.4 在单精度下是 0.4000000059604644775390625,在双精度下是 0.40000000000000002220446049250313080847263336181640625。

5.7456 * 1000 + 0.4 在单精度下是 5746,在双精度下是 5745.9999999999990905052982270717620849609375。

(我使用 C 程序进行这些计算。)

因此差异是由于值的转换方式和计算在两种精度下四舍五入的组合。

(你说“从十六进制解释来看,在我看来,任何步骤都没有精度损失”......我不知道你的意思。)

【讨论】:

  • 我想我现在知道自己哪里错了。我使用下载的基本转换器程序进行计算,它报告 40b7dbf5 简单地为 5.7456,这将我指向错误的方向。你的转换器很棒!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多