How to Resolve a Java Rounding Double Issue

How to resolve a Java Rounding Double issue

To control the precision of floating point arithmetic, you should use java.math.BigDecimal. Read The need for BigDecimal by John Zukowski for more information.

Given your example, the last line would be as following using BigDecimal.

import java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

This results in the following output.

877.85 = 1586.6 - 708.75

Round a double to 2 decimal places

Here's an utility that rounds (instead of truncating) a double to specified number of decimal places.

For example:

round(200.3456, 2); // returns 200.35

Original version; watch out with this

public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();

long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (double) tmp / factor;
}

This breaks down badly in corner cases with either a very high number of decimal places (e.g. round(1000.0d, 17)) or large integer part (e.g. round(90080070060.1d, 9)). Thanks to Sloin for pointing this out.

I've been using the above to round "not-too-big" doubles to 2 or 3 decimal places happily for years (for example to clean up time in seconds for logging purposes: 27.987654321987 -> 27.99). But I guess it's best to avoid it, since more reliable ways are readily available, with cleaner code too.

So, use this instead

(Adapted from this answer by Louis Wasserman and this one by Sean Owen.)

public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();

BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}

Note that HALF_UP is the rounding mode "commonly taught at school". Peruse the RoundingMode documentation, if you suspect you need something else such as Bankers’ Rounding.

Of course, if you prefer, you can inline the above into a one-liner:

new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue()

And in every case

Always remember that floating point representations using float and double are inexact.
For example, consider these expressions:

999199.1231231235 == 999199.1231231236 // true
1.03 - 0.41 // 0.6200000000000001

For exactness, you want to use BigDecimal. And while at it, use the constructor that takes a String, never the one taking double. For instance, try executing this:

System.out.println(new BigDecimal(1.03).subtract(new BigDecimal(0.41)));
System.out.println(new BigDecimal("1.03").subtract(new BigDecimal("0.41")));

Some excellent further reading on the topic:

  • Item 48: "Avoid float and double if exact answers are required" in Effective Java (2nd ed) by Joshua Bloch
  • What Every Programmer Should Know About Floating-Point Arithmetic

If you wanted String formatting instead of (or in addition to) strictly rounding numbers, see the other answers.

Specifically, note that round(200, 0) returns 200.0. If you want to output "200.00", you should first round and then format the result for output (which is perfectly explained in Jesper's answer).

How do I avoid rounding errors with doubles?

Lets start with this:

How do I avoid rounding errors with doubles?

Basically, you can't. They are inherent to numerical calculations using floating point types. Trust me ... or take the time to read this article:

  • What every Computer Scientist should know about floating-point arithmetic by David Goldberg.

In this case, the other thing that comes into play is that trigonometric functions are implemented by computing a finite number of steps of an infinite series with finite precision (i.e. floating point) arithmetic. The javadoc for the Math class leaves some "wiggle room" on the accuracy of the math functions. It is worth reading the javadocs to understand the expected error bounds.

Finally, if you are computing (for example) sin π/2 you need to consider how accurate your representation of π/2 is.


So what you should really be asking is how to deal with the rounding error that unavoidably happens.

In this case, you are asking is how to make it look like the user of your program as if there isn't any rounding error. There are two approaches to this:

  • Leave it alone! The rounding errors occur, so we should not lie to the users about it. It is better to educate them. (Honestly, this is high school maths, and even "the pointy haired boss" should understand that arithmetic is inexact.)

    Routines like printf do a pretty good job. And the -0.000 displayed in this case is actually a truthful answer. It means that the computed answer rounds to zero to 3 decimal places but is actually negative. This is not actually hard for someone with high school maths to understand. If you explain it.

  • Lie. Fake it. Put in some special case code to explicitly convert numbers between -0.0005 and zero to exactly zero. The code suggested in a comment

      System.out.printf(Locale.ROOT, "%.3f ", Math.round(v * 1000d) / 1000d);

    is another way to do the job. But the risk of this is that the lie could be dangerous in some circumstances. On the other hand, you could say that real mistake problem is displaying the numbers to 3 decimal places.

Retain precision with double in Java

As others have mentioned, you'll probably want to use the BigDecimal class, if you want to have an exact representation of 11.4.

Now, a little explanation into why this is happening:

The float and double primitive types in Java are floating point numbers, where the number is stored as a binary representation of a fraction and a exponent.

More specifically, a double-precision floating point value such as the double type is a 64-bit value, where:

  • 1 bit denotes the sign (positive or negative).
  • 11 bits for the exponent.
  • 52 bits for the significant digits (the fractional part as a binary).

These parts are combined to produce a double representation of a value.

(Source: Wikipedia: Double precision)

For a detailed description of how floating point values are handled in Java, see the Section 4.2.3: Floating-Point Types, Formats, and Values of the Java Language Specification.

The byte, char, int, long types are fixed-point numbers, which are exact representions of numbers. Unlike fixed point numbers, floating point numbers will some times (safe to assume "most of the time") not be able to return an exact representation of a number. This is the reason why you end up with 11.399999999999 as the result of 5.6 + 5.8.

When requiring a value that is exact, such as 1.5 or 150.1005, you'll want to use one of the fixed-point types, which will be able to represent the number exactly.

As has been mentioned several times already, Java has a BigDecimal class which will handle very large numbers and very small numbers.

From the Java API Reference for the BigDecimal class:

Immutable,
arbitrary-precision signed decimal
numbers. A BigDecimal consists of an
arbitrary precision integer unscaled
value and a 32-bit integer scale. If
zero or positive, the scale is the
number of digits to the right of the
decimal point. If negative, the
unscaled value of the number is
multiplied by ten to the power of the
negation of the scale. The value of
the number represented by the
BigDecimal is therefore (unscaledValue
× 10^-scale).

There has been many questions on Stack Overflow relating to the matter of floating point numbers and its precision. Here is a list of related questions that may be of interest:

  • Why do I see a double variable initialized to some value like 21.4 as 21.399999618530273?
  • How to print really big numbers in C++
  • How is floating point stored? When does it matter?
  • Use Float or Decimal for Accounting Application Dollar Amount?

If you really want to get down to the nitty gritty details of floating point numbers, take a look at What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Java Rounding (Double)

correct result should be (and it is)
6303.750000000000017069679003611781820568916563219777423023367655509563911228609889292329171439632773399353027343750000

Try to give a look at the BigDecimal class.

    BigDecimal i = new BigDecimal(0.025);
int n = 2;
BigDecimal h1 = new BigDecimal(2000);
BigDecimal h2 = new BigDecimal(4000);

BigDecimal y1 = ((BigDecimal.ONE.add(i)).pow(n)).multiply(h1);
BigDecimal y2 = ((BigDecimal.ONE.add(i)).pow(n)).multiply(h2);
BigDecimal result = y1.add(y2);

System.out.println(result.toEngineeringString());

Incorrect rounding of currency double values

I found the solution to this issue some time ago,forgot to come back. It was kind of a mistake on my part.Kind of.
The user application was making the calculus somewhere,sending it to the database then i was extracting it and redoing the calculus in another application(with the same calculus method).

The issue was that when I made the database,the "client" told me that the prices will never have more then 2 decimals so i declared the price field as Decimal(10,2),since I asked and made sure it was ok.
They then set a price as 0.305 for example. The user app would do the calculus, for example 24*0.305=7.32 and in the database it would record as 0.30 since it could only accept 2 decimals and then, when I took the data from the database and redid it in my other application , of course, 24*0.30=7.2

Sorry for leading you down the wrong path.
All good now.I learned that you should never trust the user/client and always double check I guess, heh.
Have a good one!

Strange double rounding issue

There's a small but important difference between the code in the link and the code originally posted here,

meaning += Math.random() + 1;    // link

vs.

meaning += (int)Math.random() + 1;  // StackOverflow

If the code posted here prints out anything but 42, it's a serious bug.

Here, the result of Math.random() is explicitly cast to int, that must result in 0, then 1 is added, resulting in 1, which then is added to meaning.

The code in the linked post, however performs an implicit cast to int after adding the result of Math.random() to 1 and that to meaning, basically

meaning = (int)(Math.random() + (double)1 + (double)meaning);

Now, if the result of Math.random() is close enough to 1.0, it occasionally happens that the result of the double addition is rounded up, so producing a final result slightly larger than immediately expected.

Java Rounding Off Issue

The problem is that 4.35 cannot be exactly represented in binary. So 4.35 * 100 is not exactly 435.0. (Every fraction in binary is the sum of inverse powers of 2, all of which are terminating decimals. Unfortunately, 0.35 decimal has a non-terminating binary expansion. So 4.35 is more like 4.349999...x (where x is something beyond which everything is zero, courtesy of the machine's finite representation of floating point numbers.) Integer truncation then produces 434. You can use Math.round() to avoid this problem.



Related Topics



Leave a reply



Submit