HOWTO · Csharp

在 C# 中将双精度值四舍五入为整数值

在 C# 中,可使用 Math.Ceiling()、Math.Floor() 和 Math.Round() 按不同规则将 double 舍入为整数值。

本页内容

将 double 转换为 int 时,应先选择舍入规则。使用 Math.Ceiling() 向正无穷方向舍入,使用 Math.Floor() 向负无穷方向舍入,使用 Math.Round() 取最接近的整数值。像 (int)value 这样的强制转换会向零截断,不会舍入。

这三个数学方法都返回整数值的 double,因此需要 int 时,应再将结果转换为 int。如果输入值可能较大,请在转换前确认结果处于 int 的取值范围内。

转换为 int 前先选择舍入规则

对于零两侧的值,舍入方向都很重要。例如,Math.Ceiling(-8.3) 会得到 -8,而 Math.Floor(-8.3) 会得到 -9。这两个方法都不只是“去掉小数部分”。请使用下表选择符合应用需求的操作:

操作 规则 8.3 的示例结果 -8.3 的示例结果
Math.Ceiling(value) 向正无穷方向舍入 9 -8
Math.Floor(value) 向负无穷方向舍入 8 -9
Math.Round(value) 取最接近的整数;默认将中点舍入到偶数 8 -8
(int)value 向零截断 8 -8

表中显示的是转换前的结果;每个数学方法都返回 double。对于处于整数取值范围内的输入,显式转换为 int 会保留这些整数值。正确的选择取决于整数所代表的含义:计数、边界值或最近似值可能各自需要不同的规则。

运行以下自包含示例:

本节中的代码以及显示的标签或输出仍为英文。

using System;

class Program
{
    static void Main()
    {
        double value = 8.3;
        double otherValue = -8.3;

        Console.WriteLine($"Ceiling: {(int)Math.Ceiling(value)}, {(int)Math.Ceiling(otherValue)}");
        Console.WriteLine($"Floor: {(int)Math.Floor(value)}, {(int)Math.Floor(otherValue)}");
        Console.WriteLine($"Nearest: {(int)Math.Round(value)}, {(int)Math.Round(otherValue)}");
        Console.WriteLine($"Cast: {(int)value}, {(int)otherValue}");
        Console.WriteLine($"Midpoints: {(int)Math.Round(2.5)}, {(int)Math.Round(3.5)}, {(int)Math.Round(2.5, MidpointRounding.AwayFromZero)}");
    }
}

使用 Math.Ceiling() 向上舍入

Math.Ceiling() 返回大于或等于输入值的最小整数值 double。这表示向正无穷方向舍入:正的小数会移至下一个整数,而负的小数会移至更接近零的整数。只有确认结果可以用 int 表示后,才将其转换为 int。

当结果绝不能小于原值时,可以使用此规则,例如计算容纳某个数量所需的整只容器数。比如,Math.Ceiling(7.3) 会得到整数值 double 8;如果结果处于适当的 int 取值范围内,(int)Math.Ceiling(7.3) 就会得到整数。如果你的需求是“远离零舍入”,那么对于负值,Ceiling 并不符合这条规则。

使用 Math.Floor() 向下舍入

Math.Floor() 返回小于或等于输入值的最大整数值 double,也就是向负无穷方向舍入。因此,Math.Floor(7.8) 会得到 7,而 Math.Floor(-7.8) 会得到 -8。这一区别很重要:对负值向下取整并不只是丢弃小数部分。

当结果不能大于输入值时,可以使用 Math.Floor(),例如计算测量数量中包含多少个完整单位。与 Ceiling 一样,只有返回的 double 处于整数取值范围内时,才能将它转换为 int。如果想向零截去小数部分,可以使用强制转换或 Math.Truncate()。

使用 Math.Round() 舍入到最接近的整数

Math.Round() 会选择最接近的整数值,并以 double 返回。其默认中点规则是舍入到偶数:当一个值恰好位于两个整数的正中间时,会选择偶数。例如,Math.Round(2.5) 会选择 2,而 Math.Round(3.5) 会选择 4。在反复计算中点时,这可以减少方向性偏差。

若要使用另一种常见的中点规则,请传入 MidpointRounding.AwayFromZero:Math.Round(2.5, MidpointRounding.AwayFromZero) 会选择 3,而 Math.Round(-2.5, MidpointRounding.AwayFromZero) 会选择 -3。此选项只改变中点的处理方式,并不会让所有值都远离零舍入。需要 int 且结果处于其取值范围内时,可将舍入后的结果转换为 int。

二进制浮点数只能近似表示某些十进制小数。如果某个值本应是精确中点,却由其他浮点数计算得出,那么它存储的值可能会略微偏向中点的一侧。对于依赖精确十进制数量的规则,可以考虑使用 decimal 表示相应数据,并使用对应的 Math.Round 重载,同时仍要选择所需的中点模式。

为什么强制转换不等于舍入

将 double 显式转换为 int 时,小数部分会向零截断。例如,(int)8.9 会变成 8,而 (int)(-8.9) 会变成 -8。第二个结果不同于 Math.Floor(-8.9) 得到的 -9,也不同于根据所选中点规则取最接近整数的舍入结果。

只有在确实需要截断时才使用强制转换,不要把它当作“舍入为整数”的捷径。如果输入可能超出 int 的取值范围,请在转换前显式处理范围;否则,结果可能无法表示应用程序所需的值。简而言之,需要向正无穷方向取边界值时选择 Math.Ceiling(),需要向负无穷方向取边界值时选择 Math.Floor(),需要最接近的整数时选择 Math.Round(),只有需要向零截断时才使用强制转换。