【发布时间】: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 types 和numeric 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