Sql server SQL Server数字舍入问题

Sql server SQL Server数字舍入问题,sql-server,numbers,rounding,precision,type-conversion,Sql Server,Numbers,Rounding,Precision,Type Conversion,这是对[]的后续问题。代码相同: 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, @re

这是对[]的后续问题。代码相同:

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);
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
执行时,您将得到以下结果:

5746
5746
关于这个问题下的两个答案,我做了更多的测试,发现自己仍然不清楚。首先,我想说,我已经阅读了msdn文档中关于
浮点和实数类型
数字和十进制类型
。我现在知道SQL Server如何在内部存储它们。对于
浮点和实数类型
,IEEE 754使用标准。对于
十进制和数字类型
,请参阅。我想知道在
浮点
情况下,哪个确切步骤导致精度损失。因此,我创建了如下表:

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);
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                            
从十六进制的解释来看,在我看来,在任何步骤中都没有精度损失,无论是
浮点
还是
实数
。那么精度损失的确切来源是什么?

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

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

(使用my验证:输入5.7456并选中“双精度”和“单精度”框,然后选择“十进制”和“原始十六进制”输出框。)

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

计算5.7456*1000的单精度为5745.6000975625,双精度为5745.599999994543031789362430572509765625

单精度0.4为0.4000000059604644775390625,双精度0.40000000002220446049250313080847263336181640625

5.7456*1000+0.4单精度为5746,双精度为5745.999999999090909055298270717620849609375

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

因此,这种差异是由两种精度的数值转换和计算四舍五入的组合造成的


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

如果你阅读了文档和优先顺序,答案就会变得清晰。好问题,我喜欢问为什么我们知道我们认为我们知道的。我不能告诉你为什么具体的浮动很糟糕(他们只是这么做),但我可以告诉你,虽然
转换(转换(5.7456为浮动)*转换(幂(10,3)为浮动)+0.4为浮动)
表示其值为5746,其实际值为
5745。9999999999999 1
编辑:明确地说,精度损失发生在乘以
@factor*@value
的点上(如果将其转换为
十进制(38,34)
)结果显示为
5745.5999999995
。将0.4添加到该值后,结果显示为
5745.9999999999 1
。我想这与浮点数的存储方式与其他数据类型不同以及由此导致的精度损失有关。我想我知道我现在错在哪里了。我使用下载的base转换器程序进行计算,然后重新报告ts 40b7dbf5仅为5.7456,这为我指明了错误的方向。您的转换器很棒!