C#四舍五入的正确方式Math.Round
IEEE标准
C# 中没有四舍五入函数,事实上我知道的程序语言都没有四舍五入函数,因为四舍五入算法不科学,国际通行的是 Banker 舍入法 Banker 's rounding(银行家舍入)算法,即四舍六入五取偶。事实上这也是 IEEE 规定的舍入标准。因此所有符合 IEEE 标准的语言都应该是采用这一算法的
Math.Round的说明:
在C#中Math.Round默认实际上是用的IEEE舍入标准,不是四舍五入,而是 四舍六如五取偶
测试代码:
using System;
using System.Text;
namespace MathTest
{
internal class Program
{
static void Main(string[] args)
{
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
StringBuilder sb3 = new StringBuilder();
StringBuilder sb4 = new StringBuilder();
for (int i = 0; i < 10; i++)
{
decimal v0 = (decimal)(0 + (i * 1.0 / 10));
decimal v1 = (decimal)(1 + (i * 1.0 / 10));
decimal v2 = (decimal)(2 + (i * 1.0 / 10));
decimal v3 = (decimal)(3 + (i * 1.0 / 10));
decimal v4 = (decimal)(4 + (i * 1.0 / 10));
decimal v5 = (decimal)(5 + (i * 1.0 / 10));
decimal v6 = (decimal)(6 + (i * 1.0 / 10));
decimal v7 = (decimal)(7 + (i * 1.0 / 10));
decimal v8 = (decimal)(8 + (i * 1.0 / 10));
decimal v9 = (decimal)(9 + (i * 1.0 / 10));
sb1.AppendLine($"{WriteVal(v0)} {WriteVal(v1)} {WriteVal(v2)} {WriteVal(v3)} {WriteVal(v4)}");
sb2.AppendLine($"{WriteVal(v5)} {WriteVal(v6)} {WriteVal(v7)} {WriteVal(v8)} {WriteVal(v9)}");
v0 = v0 / 10 + 1;
v1 = v1 / 10 + 1;
v2 = v2 / 10 + 1;
v3 = v3 / 10 + 1;
v4 = v4 / 10 + 1;
v5 = v5 / 10 + 1;
v6 = v6 / 10 + 1;
v7 = v7 / 10 + 1;
v8 = v8 / 10 + 1;
v9 = v9 / 10 + 1;
sb3.AppendLine($"{WriteVal(v0, 1)} {WriteVal(v1, 1)} {WriteVal(v2, 1)} {WriteVal(v3, 1)} {WriteVal(v4, 1)}");
sb4.AppendLine($"{WriteVal(v5, 1)} {WriteVal(v6, 1)} {WriteVal(v7, 1)} {WriteVal(v8, 1)} {WriteVal(v9, 1)}");
}
Console.WriteLine(sb1.ToString());
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(sb2.ToString());
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(sb3.ToString());
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(sb4.ToString());
Console.ReadKey();
}
static string WriteVal(decimal val, int digits = 0)
{
var str = $"Math.Round({val})={Math.Round(val, digits)}";
return str.PadRight(23, ' ');
}
}
}
Math.Round策略枚举
好在Math.Round中有一个重载
public static decimal Round(decimal d, int decimals, MidpointRounding mode)
MidpointRounding 枚举 指定数学舍入方法应用于舍入数字的策略。
AwayFromZero | 1 | 舍入到最接近的数字的策略,当数字在两个数字之间的中间时,它将舍入到离零的最接近的数字。 |
ToEven | 0 | 舍入到最接近的数字的策略,当数字在两个数字之间的中间时,它将舍入到最接近的偶数。 |
ToNegativeInfinity | 3 | 向下定向舍入的策略,结果最接近且不大于无限精确结果。 |
ToPositiveInfinity | 4 | 向上定向舍入的策略,结果最接近且不小于无限精确结果。 |
ToZero | 2 | 定向向零舍入的策略,结果最接近且数量级不大于无限精确结果。 |
舍入到最接近
字段:
舍入到最接近的操作采用具有隐式或指定精度的原始数字;检查下一位数字,该数字的精度为 1;并返回与原始数字相同的最接近的数字。 对于正数,如果下一位数字从 0 到 4,则最接近的数字为负无穷大。 如果下一位数字从 6 到 9,则最接近的数字是正无穷大。 对于负数,如果下一个数字从 0 到 4,则最接近的数字为正无穷大。 如果下一位数字从 6 到 9,则最接近的数字为负无穷大。
如果下一位数字从 0 到 4 或 6 到 9,则 MidpointRounding.AwayFromZero
不会影响 MidpointRounding.ToEven
舍入操作的结果。 但是,如果下一位数字为 5,这是两个可能结果之间的中点,并且所有剩余的数字均为零或没有剩余数字,则最接近的数字不明确。 在这种情况下,通过舍入到最接近的模式 MidpointRounding
,可以指定舍入操作是返回最接近的从零还是最接近的偶数。
下表演示了将一些负数和正数舍入的结果以及舍入到最接近的模式。 用于对数字进行舍入的精度为零,这意味着小数点后的数字会影响舍入运算。 例如,对于数字 -2.5,小数点后面的数字为 5。 由于该数字是中点,因此可以使用值 MidpointRounding
来确定舍入结果。 如果 AwayFromZero
指定,则返回 -3,因为它是离零最近的数字,精度为零。 如果 ToEven
指定,则返回 -2,因为它是精度为零的最接近偶数。
原始数字 | AwayFromZero | ToEven |
---|---|---|
3.5 | 4 | 4 |
2.8 | 3 | 3 |
2.5 | 3 | 2 |
2.1 | 2 | 2 |
-2.1 | -2 | -2 |
-2.5 | -3 | -2 |
-2.8 | -3 | -3 |
-3.5 | -4 | -4 |
定向舍入
字段:
定向舍入运算采用具有隐式或指定精度的原始数字,并在与原始数字相同的特定方向返回下一个最接近的数字。 控制 MidpointRounding
执行舍入的预定义编号的定向模式。
下表演示了将一些负数和正数舍入与定向舍入模式的结果。 用于舍入数字的精度为零,这意味着小数点前的数字受舍入运算的影响。
原始数字 | ToNegativeInfinity | ToPositiveInfinity | ToZero |
---|---|---|---|
3.5 | 3 | 4 | 3 |
2.8 | 2 | 3 | 2 |
2.5 | 2 | 3 | 2 |
2.1 | 2 | 3 | 2 |
-2.1 | -3 | -2 | -2 |
-2.5 | -3 | -2 | -2 |
-2.8 | -3 | -2 | -2 |
-3.5 | -4 | -3 | -3 |
Math.Round四舍五入的正确方式
所以,要实现四舍五入函数,对于正数,可以加一个 MidpointRounding.AwayFromZero 参数指定当一个数字是其他两个数字的中间值时其舍入为两个值中绝对值较大的值,例:
Math.Round(3.45, 1, MidpointRounding.AwayFromZero) // 3.5
Math.Round(-3.45, 1, MidpointRounding.AwayFromZero) // -3.5