1. 入门
现实生活中,钱很少会精确到一亿亿分之一欧元,房间长度也没人量到百分之一毫米。在编程里也是这样:用double或float算出来的结果经常“太小数”了。
来个经典问题:
double result = 10.0 / 3.0;
Console.WriteLine(result); // 3.3333333333333335
那如果我们想只显示两位小数(别吓到用户)怎么办?这就得用四舍五入了。
2. C#里的主要四舍五入方法
C#有好几种办法能把浮点数四舍五入。这里就有意思了:四舍五入其实有很多种!咱们来看看主流的几种方式,搞清楚它们的区别。
1) Math.Round函数——经典四舍五入
最常用的就是Math.Round方法。它能把数字四舍五入到最近的整数(或者你想要的小数位数)。
Math.Round(数字[, 小数位数[, MidpointRounding]])
- 数字 —— 你要四舍五入的数。
- 小数位数 —— 保留几位小数(默认0,就是取整)。
- MidpointRounding —— 四舍五入“中间值”的方式(马上讲)。
例子:
double x = 2.71828;
Console.WriteLine(Math.Round(x)); // 3
Console.WriteLine(Math.Round(x, 2)); // 2.72
Console.WriteLine(Math.Round(x, 3)); // 2.718
可视化:
| 输入值 | Math.Round(x) | Math.Round(x, 2) |
|---|---|---|
| 2.3 | 2 | 2.3 |
| 2.5 | 2 | 2.5 |
| 2.7 | 3 | 2.7 |
| 2.71828 | 3 | 2.72 |
返回类型:
本讲所有函数返回的都是double,所以你要是直接赋值给int变量会报错:
double result = 10.0 / 5.0; // 结果2.0
int x = Math.Round(x); // 错误! 不能直接把double赋给int变量
如果你想把double赋给int变量,得手动强制类型转换:
double result = 10.0 / 5.0; // 结果2.0
int x = (int) Math.Round(x); // 这样就没问题了!
2) 向上或向下取整
C#不仅有“经典”四舍五入,还能强制只往上或只往下取整。
向下取整(Math.Floor)
Math.Floor方法就是直接“砍掉”小数部分(往负无穷方向)。也就是永远向下!
Console.WriteLine(Math.Floor(2.99)); // 2
Console.WriteLine(Math.Floor(-2.99)); // -3
向上取整(Math.Ceiling)
Math.Ceiling方法和Floor正好相反:它永远往上(正无穷方向)取整。
Console.WriteLine(Math.Ceiling(2.01)); // 3
Console.WriteLine(Math.Ceiling(-2.01)); // -2
对比表:
| 函数 | 2.3 | -2.3 |
|---|---|---|
| Math.Round | 2 | -2 |
| Math.Floor | 2 | -3 |
| Math.Ceiling | 3 | -2 |
3) Truncate:只砍掉小数部分
Math.Truncate方法就像“佛系外科医生”。它只把小数部分去掉,完全不管数学意义上的四舍五入。如果x是正数,结果和Floor一样;如果是负数,和Ceiling一样。
Console.WriteLine(Math.Truncate(2.99)); // 2
Console.WriteLine(Math.Truncate(-2.99)); // -2
3. Math.Round怎么处理“中间值”?(MidpointRounding)
想象一下:你有2.5。要四舍五入——往哪边?正好在2和3中间。数学上有两种常见做法:
- 总是往上取整(“到最近的大数”)
- 取最近的偶数(Banker's rounding)
C#默认用第二种——取最近的偶数(“banker's rounding”,也叫“向偶数取整”)。
Console.WriteLine(Math.Round(2.5)); // 2
Console.WriteLine(Math.Round(3.5)); // 4
为啥?因为两个一样远的整数里,偶数“赢”。这样做是为了大量四舍五入时不会总是偏大或偏小。比如银行算钱的时候很重要(所以叫banker's rounding)。
如果你想每次都往上,可以手动指定模式:
// 总是“远离0”取整
Console.WriteLine(Math.Round(2.5, 0, MidpointRounding.AwayFromZero)); // 3
Console.WriteLine(Math.Round(-2.5, 0, MidpointRounding.AwayFromZero)); // -3
MidpointRounding模式表:
| 输入值 | Round(默认) | AwayFromZero | ToZero / ToEven |
|---|---|---|---|
| 2.5 | 2 | 3 | 2 |
| 3.5 | 4 | 4 | 4 |
| -2.5 | -2 | -3 | -2 |
4. 四舍五入到指定小数位:别忘了“8”和“3”!
有时候你不是要取整,而是要保留两位小数(比如显示金额或百分比)。
double price = 149.9999;
double roundedPrice = Math.Round(price, 2);
Console.WriteLine(roundedPrice); // 150
那如果我们只是想“砍掉”多余的小数,不想四舍五入呢?比如把123.4567变成123.45。
可以用个小技巧:
double num = 123.4567;
double result = Math.Floor(num * 100) / 100;
Console.WriteLine(result); // 123.45
我们手动“移动”小数点,砍掉小数,再移回来。
5. 输出格式化 vs 真正四舍五入
输出格式化({x:F2})其实不一定是“真正的”四舍五入,只是“外表”看起来那样。内存里还是原来的“长尾巴”double。你要是真的想把值四舍五入并保存下来,得用Math.Round。
有时候这个区别很关键:
double value = 2.555;
Console.WriteLine($"{value:F2}"); // 2.56
Console.WriteLine(Math.Round(value, 2)); // 2.56
double stored = Math.Round(value, 2);
Console.WriteLine(stored); // 2.56
// 但如果只是格式化输出,不四舍五入...
Console.WriteLine($"{value:F2}"); // 2.56,但内存里还是2.555
6. 常见错误和小细节
- 别用double做精确的财务计算! .NET里有专门的decimal类型——它不会“飘”小数点(详细以后讲)。
- 讨厌的“悬挂8”:因为二进制存储,有些数肉眼看着不“合理”地四舍五入。比如0.1 + 0.2其实不是0.3。
- 别把Math.Floor和Math.Round搞混! Floor永远向下,Round按数学规则来。
- 误差积累失控:如果你在计算过程中频繁四舍五入(比如循环里),最后的结果可能会“跑偏”。一般最好只对最终结果四舍五入,不要每一步都处理。
7. 总结表:每种方法的效果
| 输入值 | Math.Round(x) | Math.Floor(x) | Math.Ceiling(x) | Math.Truncate(x) |
|---|---|---|---|---|
| 3.2 | 3 | 3 | 4 | 3 |
| 3.5 | 4 | 3 | 4 | 3 |
| -3.2 | -3 | -4 | -3 | -3 |
| -3.5 | -4 | -4 | -3 | -3 |
8. 有趣的事实
- NASA有次因为坐标四舍五入不严谨、单位换算(公制/英制)搞错,直接把探测器弄丢了。教训:四舍五入要认真!
- 银行早就用“banker's rounding”了,不然以前大家都往上取整,客户每次转账都亏点小钱。
- C#里还有Math.Sign方法,能返回数字的正负号——有时候四舍五入前用它自己决定往哪边“砍”挺方便。
GO TO FULL VERSION