Why does DecimalFormat round off wrong?
I have this nice method that can format a double.
You may also specify the number of decimals.
public static String formatNumber(double number, int decimals) {
String pattern = "###,##0";
if (decimals > 0) {
pattern += "." ;
// Add the wanted number of decimals to the pattern
for (int i = 0; i < decimals; i++) {
pattern += "0";
}
}
// Get the English version of the decimalformatter
DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(
Locale.ENGLISH);
df.applyPattern(pattern);
return df.format(number);
}
Now I want to run it:
public static void main(String[] args) {
testNumber(1.5, 0, "2");
testNumber(2.5, 0, "3");
testNumber(2.15, 1, "2.2");
testNumber(2.25, 1, "2.3");
}
public static void testNumber(double number, int decimals,
String expected) {
String formatted = formatNumber(number, decimals);
System.out.print("Testing " + number + ", decimals " +
decimals + ", Actual = " + formatted);
if (formatted.equals(expected)) {
System.out.println(" Expected = " + expected);
} else {
System.out.println(" Expected = " + expected +
" <- Not the same!");
}
}
The outcome is:
Testing 1.5, decimals 0, Actual = 2 Expected = 2
Testing 2.5, decimals 0, Actual = 2 Expected = 3 <- Not the same!
Testing 2.15, decimals 1, Actual = 2.2 Expected = 2.2
Testing 2.25, decimals 1, Actual = 2.2 Expected = 2.3 <- Not the same!
What???
It looks like it rounds off completely randomly.
If you look closer to the documentation, you'll find that
DecimalFormat
uses ROUND_HALF_EVEN
.This means that "
Rounding mode to round towards the 'nearest neighbor' unless both neighbors are equidistant, in which case, round towards the even neighbor.
"But I don't want that, I want it to round up where the fraction is
>= 0.5
, which is ROUND_HALF_UP
, but it seems that you can't change the way DecimalFormat
is rounding off, so I've found a way to do it.If you move the fraction, so the rounding off allways is at the first decimal, you can use
Math.round
which uses ROUND_HALF_UP
, and then set back the correct position for the fraction.Something like:
double pow = Math.pow(10, decimals);
double numberPowed = number * pow;
number = Math.round(numberPowed) / pow;
Now we can format it using
DecimalFormat
But we're not yet homefree, because
Math.round
is returning a Long, which can't hold big numbers like doubles, so we can only do this trick if the number don't get too big!The complete method now looks like this:
public static String formatNumber(double number, int decimals) {
double pow = Math.pow(10, decimals);
double numberPowed = number * pow;
// Only do it if the number is not too big
// Math.round returns the neastest Long, so it must fit
// inside a long
if (numberPowed < Long.MAX_VALUE) {
number = Math.round(numberPowed) / pow;
}
String pattern = "###,##0";
if (decimals > 0) {
pattern += "." ;
// Add the wanted number of decimals to the pattern
for (int i = 0; i < decimals; i++) {
pattern += "0";
}
}
// Get the English version of the decimalformatter
DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(
Locale.ENGLISH);
df.applyPattern(pattern);
return df.format(number);
}
And we'll get the right outcome:
Testing 1.5, decimals 0, Actual = 2 Expected = 2
Testing 2.5, decimals 0, Actual = 3 Expected = 3
Testing 2.15, decimals 1, Actual = 2.2 Expected = 2.2
Testing 2.25, decimals 1, Actual = 2.3 Expected = 2.3