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。如果需要,对转换后的字段求和。
我有一段代码:
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。如果需要,对转换后的字段求和。