SQL 数字舍入问题

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

我期待两个 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

您的问题可以通过将 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);

在表达式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 中却有永无止境的小数部分。 (类似于例如可以在 base-3 中精确表示的三分之一,在 base-10 中有一个永无止境的小数部分:0.33333333333(etc))。用于存储 float 数字的 base-2 数字的数量是有限的,因此它必须在末尾被截断 - 这导致它被向上或向下舍入一小部分。你可以看到这个如果你 运行:

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

在这种情况下,在许多 base-2 数字后截断的效果是存储的值比您输入的十进制值小 0.0000000000000004。由于 FLOOR 函数,它做的正是它应该做的:向下舍入到最接近的整数。

(我见过很多人称这是错误。它不是。它是有意记录的行为。这里的精度损失既不比存储DECIMAL(7,6) 中的第三个;它只是不太明显,因为我们都已经习惯了以 10 进制工作)

sum(convert(int,<your column) * .01) as 'Decimal Amount'

将列转换为整数,然后乘以 .01。如果需要,对转换后的字段求和。