RoundingMode.UP 与 DecimalFormat 结合使用不如预期那样工作。

huangapple go评论78阅读模式
英文:

RoundingMode.UP with DecimalFormat does not work as expected

问题

以下是翻译好的部分:

我正在尝试在有小数值时将Double数四舍五入。我尝试了以下方法。
下面的代码段按预期将所有其他值四舍五入。

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)); // 期望为1,但产生0

number = 1.08;
System.out.println(df.format(number)); // 正确地产生2

英文:

I'm trying to round up a Double number whenever there is a decimal value. I'm trying the below.
The below snippet is rounding up all other values as expected.

1.08 -> 2
9.5 -> 10

but rounds 0.08 as 0 instead of 1. Am I missing something here?

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

答案1

得分: 11

这看起来像是JDK-8174722的表现,该问题报告针对JDK8,仍然处于待修复状态(我在JDK14上也看到了相同的行为)。在该bug报告中,将0.0001四舍五入到小数点后两位错误地得到了0.00而不是0.01

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

如果容易修复的话,我想链接的bug早就修复了,但它似乎也是一个低优先级的问题。

正如评论中所建议的,使用Math.ceil()Math.floor()Math.round()函数将提供更可预测/一致的结果,并且对于大多数用例而言足够了。

根据您的评论,您建议用户提供了RoundingMode,因此如果您想要直接应用该枚举,bug报告的MCVE包括使用BigDecimal.setScale()的解决方法。@Andreas这条评论中发布了这个解决方法,以生成此字符串:

// 注意:这忽略了Locale
BigDecimal.valueOf(number).setScale(0, RoundingMode.UP).toPlainString();

另一种使用DecimalFormat的选项允许更改Locale设置,如下:

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

请注意,由于您将在setScale()中使用向上四舍五入,因此在DecimalFormat中也不需要再次使用它。

英文:

This looks like a manifestation of bug JDK-8174722, reported for JDK8, and still open with a fix version "tbd" (I see the same behavior on JDK14). In that bug report, rounding up 0.0001 to two fewer decimal places incorrectly results in 0.00 rather than 0.01.

I tried to trace this calculation through the source code of DecimalFormat and found multiple assumptions on numbers of significant digits, retained for compatibility but apparently the cause of issues like this. My hunch based on the source code is that if the first digit after the requested precision is 0 (or close enough) the function rounds down. This behavior is contrary to the documentation which clearly states otherwise.

If it were easy to fix, I gather the linked bug would have been fixed long before now, but it also appears to be a low priority.

As the comments have suggested, using Math.ceil(), Math.floor(), and Math.round() functions would provide more predictable/consistent results and suffice for most use cases.

In your comments, you suggested the user provided the RoundingMode, so if you want a direct application of that enum, the bug report's MCVE includes a workaround using BigDecimal.setScale(). @Andreas posted an application of this workaround in this comment to produce this string:

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

Another option using a DecimalFormat would allow changing Locale settings and would be:

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

Note that since you would use the rounding up in setScale() you do not need to also use it in the DecimalFormat.

huangapple
  • 本文由 发表于 2020年8月2日 00:53:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/63207758.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定