【问题标题】:RoundingMode.UP with DecimalFormat does not work as expected使用 DecimalFormat 的 RoundingMode.UP 无法按预期工作
【发布时间】:2020-11-22 05:46:14
【问题描述】:

只要有十进制值,我就会尝试对 Double 数字进行四舍五入。我正在尝试以下。 下面的 sn-p 按预期四舍五入所有其他值。

1.08 -> 2
9.5 -> 10

但将 0.08 舍入为 0 而不是 1。我在这里遗漏了什么吗?

DecimalFormat df = new DecimalFormat("0");
df.setRoundingMode(RoundingMode.UP);
Double number = 0.08;
System.out.println(df.format(number)); // expecting 1 but produces 0
    
number = 1.08;
System.out.println(df.format(number)); // correctly produces 2

【问题讨论】:

  • 这不是问题的直接答案,但你有理由不使用Math.ceil(...)吗?
  • 这很奇怪。似乎阈值是number = 0.10; 用于四舍五入。我试过number = 0.0999999999999999;,但它给了0。
  • 我们需要根据用户的输入来配置舍入模式。我认为使用 DecimalFormat 的 setRoundingMode 应该是一个更好的选择,因为我可以直接传递用户的选择,而不是为每种模式实现不同的逻辑。我在测试时发现了这个问题,并想了解我是否缺少某些东西
  • 将其报告为错误。这不可能是正确的行为。
  • 解决方法: 使用BigDecimal.valueOf(number).setScale(0, RoundingMode.UP).toPlainString() --- 这会忽略Locale

标签: java double rounding


【解决方案1】:

这看起来像是bug JDK-8174722 的表现,报告用于 JDK8,并且仍然以修复版本“tbd”打开(我在 JDK14 上看到相同的行为)。在该错误报告中,将0.0001 向上舍入到小数点后两位不正确会导致0.00 而不是0.01

我试图通过DecimalFormat 的源代码来跟踪这个计算,并发现了多个关于有效数字的假设,为了兼容性而保留,但显然是导致此类问题的原因。我基于源代码的预感是,如果请求精度之后的第一个数字为 0(或足够接近),则函数会向下舍入。这种行为与明确说明的文档相反。

如果它很容易修复,我猜链接的错误早就被修复了,但它似乎也是一个低优先级。

正如 cmets 所建议的,使用 Math.ceil()Math.floor()Math.round() 函数将提供更可预测/一致的结果,并且足以满足大多数用例。

在您的 cmets 中,您建议用户提供 RoundingMode,因此如果您想要直接应用该枚举,bug report's MCVE 包含使用 BigDecimal.setScale() 的解决方法。 @Andreasthis comment 中发布了此解决方法的应用程序以生成此字符串:

// Note: this ignores Locale
BigDecimal.valueOf(number).setScale(0, RoundingMode.UP).toPlainString();

使用DecimalFormat 的另一个选项将允许更改区域设置,并且是:

df.format(BigDecimal.valueOf(number).setScale(0, RoundingMode.UP));

请注意,由于您将在 setScale() 中使用四舍五入,因此您无需在 DecimalFormat 中也使用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-12-23
    • 2014-12-09
    • 2016-01-13
    • 2020-09-21
    • 2011-08-17
    • 2012-04-29
    • 2021-08-12
    • 2019-02-04
    相关资源
    最近更新 更多