TSQL округление VS C# округление

Эта проблема свела меня с ума. Я использую Microsoft SQLExpress 2016 для написания хранимой процедуры. Одним из требований является округление. Но время от времени, округление неправильно. Я обнаружил, что округление T-SQL не совсем то же самое с C#, но почему?

Сравните два округления ниже:

In T-SQL: ROUND(0.045, 2) --> this will produce 0.05

In C#: Math.Round(0.045, 2) --> this will produce 0.04

Почему C# выдает 0,04? Разве это не должно быть 0,05?

Что я должен сделать, чтобы C# округление = T-SQL округление? Может кто-нибудь мне помочь?

Спасибо Сэм


Из любопытства я попробовал это в C#:

Math.Round(0.055, 2)

Угадай, до чего округлил C#? Это округлено до 0,06! Теперь я полностью сбит с толку!

Math.Round(0.045, 2)   //this becomes 0.04
Math.Round(0.055, 2)   //this becomes 0.06

Кто-нибудь может объяснить это?

Спасибо

2 ответа

Решение

Это связано с тем, что в.NET по умолчанию используется округление ToEven, а в SQL используется AwayFromZero. Смотрите это. Это разные методы округления, они отличаются тем, как они обрабатывают 5. AwayFromZero округляет его до следующего положительного числа или до следующего отрицательного числа. Таким образом, 0,5 становится 1, -0,5 становится -1. ToEven округляет до ближайшего четного числа. Таким образом, 2,5 становится 2, 3,5 становится 4 (и аналогично для отрицательных чисел). Числа, отличные от 5, обрабатываются одинаково, они округляются до ближайшего числа. Поскольку 5 равноудалено от двух чисел, это особый случай с разными стратегиями.

ToEven также известен как "Банковские правила", он используется по умолчанию в IEEE_754, поэтому он используется по умолчанию в.NET.

И наоборот, AwayFromZero также известен как "Коммерческое округление". Я не знаю, почему это сервер SQL по умолчанию, вероятно, просто потому, что это самый широко известный и понятный метод.

Конечно, вы всегда можете настроить то, что вам нужно:

В C# вы можете сделать:

Math.Round(value, MidpointRounding.ToEven)

или же

Math.Round(value, MidpointRounding.AwayFromZero)

В SQL вы можете использовать ROUND (), FLOOR () и / или CEILING ().

Какой из методов лучше, зависит от того, для чего вы его используете и чего хотите. Для разумных коллекций / распределений среднее значение округленных до даже таких же значений совпадает с исходными значениями. Это не обязательно имеет место с AwayFromZero. Если у вас есть коллекция со многими .5 Данные, округляющие AwayFromZero, будут обрабатывать все эти значения одинаково и вводить смещение. В результате среднее значение округленных значений не совпадает с исходными значениями. Смысл округления делает значение более простым, хотя оно имеет то же значение. Это больше не тот случай, если средние значения не совпадают; округленные значения имеют (немного?) другое значение, чем исходные значения.

C# позволяет вам указать, что делать в ситуации округления средней точки - https://msdn.microsoft.com/en-us/library/ms131275(v=vs.110).aspx

Math.Round(0.345, 2, MidpointRounding.AwayFromZero); // returns 0.35

Добавляя к ответу HoneyBadger, вы можете использовать SQLCLR (начиная с SQL Server 2005) для представления.NET Math.Round() метод T-SQL, чтобы его можно было использовать в запросах.

Вы можете либо написать это самостоятельно, либо просто загрузить бесплатную версию библиотеки SQL # SQLCLR (которую я создал и которая содержит как Math_RoundToEvenFloat, так и Math_RoundToEvenDecimal в бесплатной версии), а затем выполнить:

SELECT ROUND(0.045, 2), SQL#.Math_RoundToEvenFloat(0.045, 2);
-- 0.050    0.04

SELECT ROUND(0.055, 2), SQL#.Math_RoundToEvenFloat(0.055, 2);
-- 0.060    0.06

Существуют как функции с плавающей запятой, так и десятичные, по соображениям производительности и точности. FLOAT Значения передаются между контекстами T-SQL и CLR намного быстрее, но иногда могут содержать дополнительные 0,000000000005 (или что-то в этом роде), поступающие в код CLR, поэтому обязательно используйте функцию, соответствующую типу данных, который вы используете. Если вы делаете финансовые расчеты, то вы уже должны использовать DECIMAL (точный тип данных). Если вы используете FLOAT (неточный тип данных) для финансовых расчетов, вы действительно должны изменить это на DECIMAL раньше чем позже;-).

Другие вопросы по тегам