Вопрос округления номера 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
Я ожидаю два 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
2 ответа
В выражении 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
тип данных (данные с плавающей запятой). Этот тип данных предназначен для использования для приблизительных данных, таких как измерения, когда данные, которые вы читаете из инструмента, уже являются приблизительными. В старших классах я научился читать столько цифр, сколько смогу, но относился к последним как к незначительным - мне приходилось сохранять его во всех вычислениях, но округлять конечный результат до числа значащих цифр, основанных на точности моих измерений, Округление гарантирует, что неточности в последних цифрах не повлияют на конечный результат. Типы данных с плавающей точкой должны обрабатываться одинаково.
Внутри цифры с плавающей запятой представлены в системе счисления base-2. Это означает, что есть числа, которые имеют точное представление в нашей обычно используемой системе base-10 (например, 5.7456), но не имеют конечной дробной части в base-2. (Подобно тому, как, например, одна треть, которая может быть представлена точно в base-3, имеет нескончаемую дробную часть в base-10: 0.33333333333(и т. Д.)). Количество цифр base-2, используемых для хранения float
число является конечным, поэтому оно должно быть обрезано в конце, что приводит к тому, что оно округляется либо в большую, либо в меньшую сторону. Вы можете увидеть это, если запустите:
DECLARE @a float = 5.7456;
SELECT CAST(@a AS decimal(19,16));
В этом случае эффект отсечения после большого количества цифр base-2 заключается в том, что сохраненное значение на 0,0000000000000004 меньше, чем введенное десятичное значение. Эта небольшая разница превращается в огромный эффект из-за FLOOR
функция, которая делает именно то, что должна делать: округлять до ближайшего целого числа.
(Я видел, что многие люди называют это ошибкой. Это не так. Это целенаправленное и задокументированное поведение. И потеря точности здесь не хуже и не лучше, чем потеря точности, которую вы получаете, сохраняя треть в DECIMAL(7,6)
; это немного менее очевидно, потому что мы все привыкли работать в base-10)
Вы можете исправить проблему, изменив значение 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);
sum(convert(int,<your column) * .01) as 'Decimal Amount'
Преобразуйте столбец в целое число, затем умножьте на 0,01. Поле суммы преобразования, если необходимо.