DecimalFormat using incorrect RoundingMode even when set explicitly - java

Why is this DecimalFormat not rounding using RoundingMode.HALF_UP as expected, and what should be done to get the expected output? Can it be done with DecimalFormat?
DecimalFormat df = new DecimalFormat("0.0");
df.setRoundingMode(RoundingMode.HALF_UP);
df.format(0.05); // expecting 0.1, getting 0.1
df.format(0.15); // expecting 0.2, getting 0.1 << unexpected
df.format(0.06); // expecting 0.1, getting 0.0 << unexpected
I have seen the answers from this question, specifically this answer, but it only seems to work when rounding to an integer.
My JDK is 8.0.110 and my JRE is 8.0.730.2

(Answer below using Java 8.)
The issue you are seeing comes from specifying "0.15" or "0.05" in code, which when represented as a double is something slightly less than 0.15. Check this out
DecimalFormat df = new DecimalFormat("#.#");
df.setRoundingMode(RoundingMode.HALF_UP);
BigDecimal bd = new BigDecimal(0.15);
System.out.println("bd=" + bd);
System.out.println(df.format(0.15)); // expecting 0.1, getting 0.1
bd = new BigDecimal(0.05);
System.out.println("bd=" + bd);
System.out.println(df.format(0.05));
bd = new BigDecimal(0.06);
System.out.println("bd=" + bd);
System.out.println(df.format(0.06));
The output of this code is
bd=0.1499999999999999944488848768742172978818416595458984375
0.1
bd=0.05000000000000000277555756156289135105907917022705078125
0.1
bd=0.059999999999999997779553950749686919152736663818359375
0.1
A possible solution (if you absolutely need it to round the right way) is to use BigDecimal.valueOf to create the value. For example
BigDecimal bd = BigDecimal.valueOf(0.15);
System.out.println("bd=" + bd);
System.out.println(df.format(bd)); // expecting 0.1, getting 0.1
bd = BigDecimal.valueOf(0.05);
System.out.println("bd=" + bd);
System.out.println(df.format(bd));
bd = BigDecimal.valueOf(0.06);
System.out.println("bd=" + bd);
System.out.println(df.format(bd));
Will now yield
bd=0.15
0.2
bd=0.05
0.1
bd=0.06
0.1
BTW, as Scary Wombat pointed out, the mask set as 0.0 instead of #.# will make 0.6 0. But I think that was a later edit then when I started looking at it. Use #.#.

When you do
df.format(0.15);
there are actually two rounding operations. The obvious one is the one you asked for with df.format, but there's an earlier one that happens at compile time.
The decimal value 0.15 isn't representable as a double. Doubles can only represent rationals whose denominator is a power of two, just like decimal notation can only represent rationals whose denominator is a power of ten. When you write 0.15, the compiler rounds this to the closest value representable as a double, and that value happens to be
0.1499999999999999944488848768742172978818416595458984375
which df.format correctly rounds down. It's not exactly halfway between 0.1 and 0.2, so the HALF_UP rounding mode doesn't matter.
As for
df.format(0.06); // expecting 0.1, getting 0.0 << unexpected
If you're seeing that, that's a bug. It looks like it matches one linked by ScaryWombat, reported to be fixed in 8u40 and 8u45. A test on Ideone, which uses 8u51, shows the correct behavior. Updating your Java should resolve the issue.

I think the answer is IEEE Standard for Floating-Point Arithmetic (IEEE 754). The Wiki article has a good example in the Rounding rules section. Also you can read section 9.1 of Introduction to Java that expends more about floating point. BigDecimal solves most of these short coming by storing the uncalled value inside a BigInteger, the precision and scale in separate in integer fields.

Related

how to round off if the value is 1.01 to 1, if 1.1 then 2 in java?

In java
How to roundoff a value(either float, bigdecimal or double) having the following pattern,
(1) If the value is 1.0, i.e., if the decimal is started with zero then this should not rounded off and the value should be the whole number. ie., in this case "1".
(2) If the value is 1.1 i.e., if the decimal place started with number greater than 0, then the whole number should be rounded to the next number. i.e., if 1.1 then it should be 2.
So you want to round anything below 0.1 down to 0 and 0.1 or more to 1.0
long round = Math.round(x + 0.4);
Try this for a start (for float and double)
int rounded = Math.round(x + 0.4);
See https://docs.oracle.com/javase/8/docs/api/java/math/RoundingMode.html. I believe RoundingMode.CEILING is what you want. BigDecimal lets you control the rounding:
new BigDecimal(1.0).setScale(0, RoundingMode.CEILING).doubleValue(); => 1.0
new BigDecimal(1.1).setScale(0, RoundingMode.CEILING).doubleValue(); => 2.0
Guava includes some utility classes for rounding floats and doubles directly with RoundingModes.
DoubleMath.roundToInt(1.0, RoundingMode.CEILING); => 1
DoubleMath.roundToInt(1.1, RoundingMode.CEILING); => 2
Edit: Whoops. I missed the part where rounding 1.01 should result in 1. The other suggested methods are more correct.
Your specification is not entirely clear to me. I understand your question to mean the "ceil" function, that is, 1.01 has to be rounded up, but your question can also be interpreted such that 1.01 has to be rounded down. (If the latter is what you want, look at Peter Lawrey's answer.)
For doubles (and floats) Java provides the standard method Math.ceil(double a) for the ceil function.
For BigDecimal values you can use the setScale method: set the scale to 0 (no decimal fraction) and the rounding mode to RoundingMode.CEILING to specify how to round:
static BigDecimal ceil(BigDecimal a) {
return a.setScale(0, RoundingMode.CEILING);
}

Java DecimalFormat rounding error?

There seems to be some inconsistency with the way DecimalFormat rounds numbers.
new DecimalFormat("0.0", new DecimalFormatSymbols(Locale.US)).format(number)
Here's what's returned for some different number values.
0.05 returns 0.0
0.15 returns 0.2
0.25 returns 0.2
0.35 returns 0.4
0.45 returns 0.4
Why does this happen? And more importantly, how can I get the correct values (0.3 when rounding 0.25 and 0.5 when rounding 0.45)?
The Javadoc states:
DecimalFormat provides rounding modes defined in RoundingMode for formatting. By default, it uses RoundingMode.HALF_EVEN.
HALF_EVEN does exactly what you've noticed: it prefers rounding to the nearest even number when it's exactly halfway between two numbers. (This is useful for a number of reasons, primarily because it doesn't bias up or down on average, tending to balance out rounding error.)
Just call setRoundingMode(RoundingMode.HALF_UP) for the behavior you describe.

Strange float division result

I have occurred in this strange division error in a grails project (But I think grails has little to do with it, is a groovy or java question I think):
If in the groovy console I run this
float money = -1.30
float r = 0.01
println ((money/r).class.name)
println ((money/r).floatValue())
println ((money/r).toString() )
I get this output
java.lang.Double
-130.0
-129.99999813735482
The float division in groovy give me a Double, and this is correct but
why the Double toString() give me a so strange value "-129.99999813735482" and
not the correct "-130.0"?
From the Floating-Point Guide:
Why don’t my numbers, like 0.1 + 0.2 add up to a nice round 0.3, and
instead I get a weird result like 0.30000000000000004?
Because internally, computers use a format (binary floating-point)
that cannot accurately represent a number like 0.1, 0.2 or 0.3 at all.
When the code is compiled or interpreted, your “0.1” is already
rounded to the nearest number in that format, which results in a small
rounding error even before the calculation happens.
Specifically, neither 1.3 nor 0.01 can be accurately represented by a float.
As everyone says, double and float aren't precise enough for what you're trying to do.
One solution is to not use float as your object type, and do:
def money = -1.30
def r = 0.01
println ((money/r).class.name)
println ((money/r).floatValue())
println ((money/r).toString() )
As you can see, Groovy uses BigDecimal, which means the output is:
java.math.BigDecimal
-130.0
-130
By doing the floatValue, you are limiting the precission of the value. So the JVM does a rounding and you get the different value.
And before you say "but 130.0 should be the value calculated because it is the correct one" keep in mind that the computer uses binary format to represent decimal numbers and this causes rounding errors with fractions (try to represent 0.3 in binary to understand why).

Java BigDecimal precision problems

I know the following behavior is an old problem, but still I don't understand.
System.out.println(0.1 + 0.1 + 0.1);
Or even though I use BigDecimal
System.out.println(new BigDecimal(0.1).doubleValue()
+ new BigDecimal(0.1).doubleValue()
+ new BigDecimal(0.1).doubleValue());
Why this result is: 0.30000000000000004 instead of: 0.3?
How can I solve this?
What you actually want is
new BigDecimal("0.1")
.add(new BigDecimal("0.1"))
.add(new BigDecimal("0.1"));
The new BigDecimal(double) constructor gets all the imprecision of the double, so by the time you've said 0.1, you've already introduced the rounding error. Using the String constructor avoids the rounding error associated with going via the double.
First never, never use the double constructor of BigDecimal. It may be the right thing in a few situations but mostly it isn't
If you can control your input use the BigDecimal String constructor as was already proposed. That way you get exactly what you want. If you already have a double (can happen after all), don't use the double constructor but instead the static valueOf method. That has the nice advantage that we get the cannonical representation of the double which mitigates the problem at least.. and the result is usually much more intuitive.
This is not a problem of Java, but rather a problem of computers generally. The core problem lies in the conversion from decimal format (human format) to binary format (computer format). Some numbers in decimal format are not representable in binary format without infinite repeating decimals.
For example, 0.3 decimal is 0.01001100... binary But a computer has a limited "slots" (bits) to save a number, so it cannot save all the whole infinite representation. It saves only
0.01001100110011001100 (for example). But that number in decimal is no longer 0.3, but 0.30000000000000004 instead.
Try this:
BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1));
EDIT: Actually, looking over the Javadoc, this will have the same problem as the original. The constructor BigDecimal(double) will make a BigDecimal corresponding to the exact floating-point representation of 0.1, which is not exactly equal to 0.1.
This, however, gives the exact result, since integers CAN always be expressed exactly in floating-point representation:
BigDecimal one = new BigDecimal(1);
BigDecimal oneTenth = one.divide(new BigDecimal(10));
BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth);
The problem you have is that 0.1 is represented with a slightly higher number e.g.
System.out.println(new BigDecimal(0.1));
prints
0.1000000000000000055511151231257827021181583404541015625
The Double.toString() takes into account this representation error so you don't see it.
Similarly 0.3 is represented by a value slightly lower than it really is.
0.299999999999999988897769753748434595763683319091796875
If you multiply the represented value of 0.1 by 3 you don't get the represented value for 0.3, you instead get something a little higher
0.3000000000000000166533453693773481063544750213623046875
This is not just a representation error but also a rounding error caused by the operations. This is more than the Double.toString() will correct and so you see the rounding error.
The moral of the story, if you use float or double also round the solution appropriately.
double d = 0.1 + 0.1 + 0.1;
System.out.println(d);
double d2 = (long)(d * 1e6 + 0.5) / 1e6; // round to 6 decimal places.
System.out.println(d2);
prints
0.30000000000000004
0.3

Java Glitch? Subtracting numbers?

Is this a glitch in Java?
I go to solve this expression: 3.1 - 7.1
I get the answer: -3.9999999999999996
What is going on here?
A great explanation can be found here. http://www.ibm.com/developerworks/java/library/j-jtp0114/
Floating point arithmetic is rarely exact. While some numbers, such
as 0.5, can be exactly represented as a binary (base 2) decimal (since
0.5 equals 2-1), other numbers, such as 0.1, cannot be. As a result, floating point operations may result in rounding errors, yielding a
result that is close to -- but not equal to -- the result you might
expect. For example, the simple calculation below results in
2.600000000000001, rather than 2.6:
double s=0;
for (int i=0; i<26; i++)
s += 0.1;
System.out.println(s);
Similarly, multiplying .1*26 yields a result different from that of
adding .1 to itself 26 times. Rounding errors become even more serious
when casting from floating point to integer, because casting to an
integral type discards the non-integral portion, even for calculations
that "look like" they should have integral values. For example, the
following statements:
double d = 29.0 * 0.01;
System.out.println(d);
System.out.println((int) (d * 100));
will produce as output:
0.29
28
which is probably not what you might expect at first.
See the provided reference for more information.
As mentioned by several others you cannot count on double if you would like to get an exact decimal value, e.g. when implementing monetary applications. What you should do instead is to take a closer look at BigDecimal:
BigDecimal a = new BigDecimal("3.1");
BigDecimal b = new BigDecimal("7.1");
BigDecimal result = a.subtract(b);
System.out.println(result); // Prints -4.0
Computers are 100% so in the math world that is correct, to the average person it is not. Java cant have a error on a specific number as it is just code that runs the same way but has a different input!
P.S. Google how to round a number
rounding errors in floating points
same way that 3 * 0.1 != 0.3 (when it's not folded by the compiler at least)
Automatic type promotion is happening and that is the result.
Here is some resource to learn.
http://docs.oracle.com/javase/specs/jls/se5.0/html/conversions.html
The next step would be is to learn to use formatters to format it to the given precision / requirements.

Categories

Resources