T-SQL 舍入与 C# 舍入

T-SQL rounding vs. C# rounding

我正在使用 Microsoft SQL Server Express 2016 to write a stored procedure。其中一项要求是进行舍入。但时不时地,四舍五入是错误的。我发现 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

怎么解释?

这是因为 .NET 默认使用 'ToEven' 舍入,而 SQL 使用 'AwayFromZero'。参见 this。这些是不同的舍入方法,它们在处理 5 的方式上有所不同。

AwayFromZero 向上舍入为下一个正数,或向下舍入为下一个负数。所以,0.5 变成 1,-0.5 变成 -1。 ToEven 四舍五入到最接近的偶数。所以 2.5 变成 2,3.5 变成 4(对于负数也是如此)。 5 以外的数字被视为相同,并四舍五入到最接近的数字。由于 5 与两个数字等距,这是一个特殊情况,具有不同的策略。

ToEven 也称为 'Banking Rules',它是 IEEE_754, which is why 中使用的默认值,它是 .NET 中的默认值。

相反,AwayFromZero 也被称为 'Commercial Rounding'。我不知道为什么它是 SQL 服务器的默认设置,可能只是因为它是最广为人知和理解的方法。

当然,你可以随时配置你需要的:

在 C# 中你可以这样做:

Math.Round(value, MidpointRounding.ToEven)

Math.Round(value, MidpointRounding.AwayFromZero)

在SQL中你可以使用ROUND(), FLOOR() and/or CEILING()

哪种方法更好,取决于你用它做什么,以及你想要什么。对于合理的collections/distributions,四舍五入到Even值的平均值与其原始值相同。 AwayFromZero 不一定是这种情况。如果您有一个包含许多 .5 数据的集合,舍入 AwayFromZero 会将所有这些值视为相同,并引入偏差。

结果是舍入值的平均值与原始值的平均值不同。四舍五入的目的是使值更简单,同时具有相同的含义。如果平均值不匹配,情况就不再如此;四舍五入的值与原始值具有(略微?)不同的含义。

C# 允许您指定在中点舍入情况下要执行的操作 - 请参阅 Round(Decimal, Int32, MidpointRounding).

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

添加到 ,您可以使用 SQLCLR(从 SQL Server 2005 开始)将 .NET Math.Round() 方法公开给 T-SQL 以便它可以在查询中使用。

您可以自己编写代码,也可以直接下载 SQL# SQLCLR 库的免费版本(这是我创建的,包含 Math_RoundToEvenFloatMath_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" 和 "Decimal" 特定的函数。 FLOAT 值在 T-SQL 和 CLR 上下文之间传输的速度要快得多,但有时会在 CLR 代码中包含额外的 0.000000000005(或类似的东西),因此请务必使用与您正在使用的数据类型。如果您正在进行财务计算,那么您应该已经在使用 DECIMAL(一种精确的数据类型)。如果您使用 FLOAT(一种不精确的数据类型)进行财务计算,您真的应该尽快将其更改为 DECIMAL ;-)。