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_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" 和 "Decimal" 特定的函数。 FLOAT
值在 T-SQL 和 CLR 上下文之间传输的速度要快得多,但有时会在 CLR 代码中包含额外的 0.000000000005(或类似的东西),因此请务必使用与您正在使用的数据类型。如果您正在进行财务计算,那么您应该已经在使用 DECIMAL
(一种精确的数据类型)。如果您使用 FLOAT
(一种不精确的数据类型)进行财务计算,您真的应该尽快将其更改为 DECIMAL
;-)。
我正在使用 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
添加到 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" 和 "Decimal" 特定的函数。 FLOAT
值在 T-SQL 和 CLR 上下文之间传输的速度要快得多,但有时会在 CLR 代码中包含额外的 0.000000000005(或类似的东西),因此请务必使用与您正在使用的数据类型。如果您正在进行财务计算,那么您应该已经在使用 DECIMAL
(一种精确的数据类型)。如果您使用 FLOAT
(一种不精确的数据类型)进行财务计算,您真的应该尽快将其更改为 DECIMAL
;-)。