有人可以向我解释为什么以下代码:
public class Test {
public static void main(String... args) {
round(6.2088, 3);
round(6.2089, 3);
}
private static void round(Double num, int numDecimal) {
System.out.println("BigDecimal: " + new BigDecimal(num).toString());
// Use Locale.ENGLISH for '.' as decimal separator
NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH);
nf.setGroupingUsed(false);
nf.setMaximumFractionDigits(numDecimal);
nf.setRoundingMode(RoundingMode.HALF_UP);
if(Math.abs(num) - Math.abs(num.intValue()) != 0){
nf.setMinimumFractionDigits(numDecimal);
}
System.out.println("Formatted: " + nf.format(num));
}
}
给出以下输出?
[me@localhost trunk]$ java Test
BigDecimal: 6.208800000000000096633812063373625278472900390625
Formatted: 6.209
BigDecimal: 6.208899999999999863575794734060764312744140625
Formatted: 6.208
如果你没有看到:“6.2089”四舍五入到3位数给出输出“6.208”而“6.2088”给出“6.209”作为输出。少即是多?
使用Java 5,6或7时结果很好但是这个Java 8给了我这个奇怪的输出。 Java版本:
[me@localhost trunk]$ java -version
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) Server VM (build 25.5-b02, mixed mode)
编辑:这是Java 7的输出:
[me@localhost trunk]$ java Test
BigDecimal: 6.208800000000000096633812063373625278472900390625
Formatted: 6.209
BigDecimal: 6.208899999999999863575794734060764312744140625
Formatted: 6.209
Java 7版本:
[me@localhost trunk]$ java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) Server VM (build 24.51-b03, mixed mode)
答案 0 :(得分:19)
我可以将这个问题追溯到java.text.DigitList
课程522。
情况是它认为小数位数6.0289
已经四舍五入(与等效的BigDecimal
表示6.208899…
比较时这是正确的)并且决定不再向上舍入。问题是这个决定只有在四舍五入产生的数字为5
的情况下才有意义,而不是在大于5
的情况下。请注意HALF_DOWN
的代码如何正确区分digit=='5'
和digit>'5'
案例。
这显然是一个错误,而且是一个奇怪的错误,因为执行类似权利的代码(仅针对另一个方向)正好在破坏的代码之下。
case HALF_UP:
if (digits[maximumDigits] >= '5') {
// We should not round up if the rounding digits position is
// exactly the last index and if digits were already rounded.
if ((maximumDigits == (count - 1)) &&
(alreadyRounded))
return false;
// Value was exactly at or was above tie. We must round up.
return true;
}
break;
case HALF_DOWN:
if (digits[maximumDigits] > '5') {
return true;
} else if (digits[maximumDigits] == '5' ) {
if (maximumDigits == (count - 1)) {
// The rounding position is exactly the last index.
if (allDecimalDigits || alreadyRounded)
/* FloatingDecimal rounded up (value was below tie),
* or provided the exact list of digits (value was
* an exact tie). We should not round up, following
* the HALF_DOWN rounding rule.
*/
return false;
else
// Value was above the tie, we must round up.
return true;
}
// We must round up if it gives a non null digit after '5'.
for (int i=maximumDigits+1; i<count; ++i) {
if (digits[i] != '0') {
return true;
}
}
}
break;
其他数字不会发生这种情况的原因是6.2088
不是四舍五入的结果(再次,与BigDecimal
输出6.208800…
相比)。所以在这种情况下,它会向上舍入。
答案 1 :(得分:3)
1.8.0_40-b25
公开GA 8u40发布release notes)感谢Holger's answer的研究,我能够开发一个运行时补丁,我的雇主已经根据GPLv2许可条款免费发布了它,并且有类路径异常 1 (与OpenJDK源代码相同)。
补丁项目和源代码是hosted on GitHub,其中包含有关此错误的更多详细信息以及可下载二进制文件的链接。该补丁不对磁盘上安装的Java文件进行任何修改,并且应该可以安全地在所有版本的Oracle Java&gt; = 6上使用,至少通过版本8(包括固定版本)。
当补丁检测到表明存在错误的字节码签名时,它会用修改后的实现取代HALF_UP
转换案例:
if (digits[maximumDigits] > '5') {
return true;
} else if (digits[maximumDigits] == '5') {
return maximumDigits != (count - 1)
|| allDecimalDigits
|| !alreadyRounded;
}
// else
return false; // in original switch(), was: break;
1 我不是律师,但我的理解是 GPLv2 w / CPE 允许以二进制形式进行商业用途,而不适用于GPL合并后的工作。
答案 2 :(得分:2)
通过代码追踪到达DigitList.set
final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) {
FloatingDecimal.BinaryToASCIIConverter fdConverter = FloatingDecimal.getBinaryToASCIIConverter(source);
boolean hasBeenRoundedUp = fdConverter.digitsRoundedUp();
我对此错误进行了更简单的测试
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;
public class Test {
public static void main(String... args) {
for (int i = 0; i < 100; i++)
test(i / 100.0);
}
private static void test(double num) {
NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH);
nf.setMaximumFractionDigits(1);
String round1 = nf.format(num);
NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
nf2.setMaximumFractionDigits(1);
nf2.setRoundingMode(RoundingMode.HALF_UP);
String round2 = nf2.format(num);
if (!round1.equals(round2))
System.out.printf("%s, formatted with HALF_UP was %s but should be %s%n", num, round2, round1);
}
}
打印
0.06, formatted with HALF_UP was 0 but should be 0.1
0.09, formatted with HALF_UP was 0 but should be 0.1
0.18, formatted with HALF_UP was 0.1 but should be 0.2
0.25, formatted with HALF_UP was 0.3 but should be 0.2
0.29, formatted with HALF_UP was 0.2 but should be 0.3
0.36, formatted with HALF_UP was 0.3 but should be 0.4
0.37, formatted with HALF_UP was 0.3 but should be 0.4
0.47, formatted with HALF_UP was 0.4 but should be 0.5
0.48, formatted with HALF_UP was 0.4 but should be 0.5
0.49, formatted with HALF_UP was 0.4 but should be 0.5
0.57, formatted with HALF_UP was 0.5 but should be 0.6
0.58, formatted with HALF_UP was 0.5 but should be 0.6
0.59, formatted with HALF_UP was 0.5 but should be 0.6
0.69, formatted with HALF_UP was 0.6 but should be 0.7
0.86, formatted with HALF_UP was 0.8 but should be 0.9
0.87, formatted with HALF_UP was 0.8 but should be 0.9
0.96, formatted with HALF_UP was 0.9 but should be 1
0.97, formatted with HALF_UP was 0.9 but should be 1
0.98, formatted with HALF_UP was 0.9 but should be 1
0.99, formatted with HALF_UP was 0.9 but should be 1
在不正确的情况下hasBeenRoundedUp
为真,这可以防止任何进一步的舍入。请注意,如果您放弃设置舍入,则它具有正确舍入的默认路径。
我不会使用NumberFormat。使用起来非常缓慢和复杂。
import java.math.BigDecimal;
public class Test {
public static void main(String... args) {
round(6.2088, 3);
round(6.2089, 3);
}
private static void round(double num, int numDecimal) {
BigDecimal bd = new BigDecimal(num);
BigDecimal bd2 = BigDecimal.valueOf(num);
System.out.println("new BigDecimal: " + bd);
System.out.println("BigDecimal.valueOf: " + bd2);
System.out.printf("%." + numDecimal + "f%n", num);
System.out.printf("%." + numDecimal + "f%n", bd);
System.out.printf("%." + numDecimal + "f%n", bd2);
System.out.printf("%f%n", round3(num));
System.out.printf("%s%n", round3(num));
System.out.printf("%f%n", bd.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
System.out.printf("%s%n", bd.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
System.out.printf("%f%n", bd2.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
System.out.printf("%s%n", bd2.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
}
private static double round3(double num) {
final double factor = 1e3;
return Math.round(num * factor) / factor;
}
}
使用Java 8进行打印。
new BigDecimal: 6.208800000000000096633812063373625278472900390625
BigDecimal.valueOf: 6.2088
6.209
6.209
6.209
6.209000
6.209
6.209000
6.209
6.209000
6.209
new BigDecimal: 6.208899999999999863575794734060764312744140625
BigDecimal.valueOf: 6.2089
6.209
6.209
6.209
6.209000
6.209
6.209000
6.209
6.209000
6.209