做订单金额时踩了个坑:Math.Round(2.345, 2) 期望得到 2.35,实际返回 2.34。同事一度以为是浮点精度问题,其实是 C# 默认用了银行家舍入。

Math.Round 的默认行为

Math.Round 默认 MidpointRounding.ToEven,遇到 .5 时往偶数方向靠。2.345 的两个候选是 2.34 和 2.35,取偶数的 2.34。想按小学教的那种五入,要显式传 MidpointRounding.AwayFromZero,这时才得到 2.35。

三种写法对比

double a = Math.Round(2.345, 2, MidpointRounding.AwayFromZero);
string b = value.ToString('F2');
decimal c = Math.Round(2.345m, 2, MidpointRounding.AwayFromZero);

第一种返回 double,适合继续参与运算;第二种返回字符串,直接用于显示并自动补足两位;第三种用 decimal,金额场景首选。注意 double 存的是二进制近似值,2.345 实际可能是 2.34499999,所以连 AwayFromZero 也未必给到预期结果。

金额一律用 decimal

decimal 是十进制浮点,字面量加后缀 m,2.345m 就是精确的 2.345,Math.Round 稳定返回 2.35m。数据库字段用 decimal(18,2),实体类也用 decimal,中间不要转成 double 再转回来,一转就容易多出一分钱。

四个提醒

  • F2 是格式化不是舍入:它按当前区域设置输出,某些环境下仍走银行家舍入。
  • 别在循环里做舍入:每步都舍入误差会累积,最后统一处理。
  • 比较用 decimal 常量:0.1m 比 0.1 可靠。
  • 格式化带区域:多语言环境显式传 InvariantCulture,避免小数点变逗号。