2016-12-16 83 views
0

我有一段代码:TSQL数四舍五入问题

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 

我期待2 5746.我试着调试功能,并发现了一些有趣的行为。以下是调试时我在Immediate Window中做的一些测试。

@factor 
1.000000000000000e+003 
@result 
5.745000000000000e+003 
@value 
5.745600000000000e+000 
@value*@factor 
5745.6 
@value*@factor+0.4 
5746 
floor(@value*@factor+0.4) 
5745 
floor(5746) 
5746 

任何人都可以帮忙解释结果吗?尤其是以下三行:

@value*@factor+0.4 
5746 
floor(@value*@factor+0.4) 
5745 
floor(5746) 
5746 

回答

1

在表达式FLOOR(5.7456 * 1000 + 0.4);是固定的,括号之间的部分首先计算。对于常量,数据类型是基于符号推断的;对于5.7456即decimal(5,4); 1000是int;而0.4是decimal(1,1)。 5.7456 * 1000的推断数据类型为decimal(10,4);并且为了充分表达,它是decimal(11,4)。这些都是精确的数字数据类型,因此您不会遇到任何舍入;确切地说,最终结果是5746.0000。所述FLOOR功能修剪部分和转换为decimal(11,0),返回5746.

在用户定义的函数,则存储输入参数和中间结果在float数据类型(浮点数据)。此数据类型旨在用于近似数据,例如测量数据,您从仪器中读取的数据已经是近似值。我已经在高中学到了尽可能多的数字,但把最后一个数字看作是微不足道的 - 我必须在所有计算中保留它,但根据我的测量精度将最终结果舍入到有效数字的位数。舍入确保最后数字中的不准确性不会影响最终结果。 浮点数据类型应该以相同的方式处理。

在内部,浮点数位以基数2数字系统表示。这意味着有一些数字在我们常用的base-10系统(如5.7456)中具有精确的表示形式,但是在base-2中有一个永无止境的小数部分。 (类似于例如三分之一,其可以完全以3为基数表示,具有以10为底的永不落幕小数部分:0.33333333333(等))。用于存储float号码的基数为2的数字是有限的,因此必须在最后切断 - 这会导致其上或下舍入一小部分。你可以看到这一点,如果你运行:

DECLARE @a float = 5.7456; 
SELECT CAST(@a AS decimal(19,16)); 

在这种情况下,很多基2位后切断的效果是存储的值比你把十进制值少0.0000000000000004如此之小。由于函数FLOOR函数的差异变成了一个巨大的效果,该函数完成它应该做的事情:向下舍入到最接近的整数。 (我见过很多人称这是一个错误,它不是,它是有目的和有记录的行为,而且这里的精确度损失既不差也不会比你存储第三个精度时的精度损失更好在DECIMAL(7,6);这只是不太明显,因为我们都长大了习惯于以10为基础工作)

+0

你能花时间格式化吗? –

+0

丹尼尔,我对这个频道很陌生。现在好点了吗? –

0

您的问题可以通过改变浮动到真实

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); 
+0

为什么浮动不起作用? –