Strange Floating-Point Behaviour in a Java Program

Strange floating-point behaviour in a Java program

The most common storage for floating-point values in programming languages - IEEE singles and doubles - does not have exact representations for most decimal fractions.

The reason is that they store values in binary floating-point format, rather than decimal floating-point format. The only fractional values which can be represented exactly are those which are sums of negative powers of two. Numbers like:

  • 0.5 (2^-1)
  • 0.125 (2^-3)
  • 0.625 (2^-1 + 2^-3)

Etc.

What you are seeing is the fact that representations of numbers like 0.96 are not exactly representable, because they are not expressible as a sum of negative powers of two. Thus, when printed out with full precision as a decimal fraction, they won't match the original value.

Strange behaviour managing double and float values (Java)

Your problem is due to the way in which double and float work.

In floating-point arithmetic, the computer only calculates to a certain precision, i.e. after so many decimal places, it just rounds the number off. 0.1 may seem like a nice round number in decimal, but in binary it is recurring - 0.0001100110011 and so on. With each calculation, the rounding-off makes the result a bit more inaccurate. Have a look at this page for a more thorough explanation.

double is more precise than float, but it will still display rounding errors like this.

To circumvent the problem, you could do one of two things. First, as Jon Skeet said in the comments, you could use arbitrary-precision arithmetic like BigDecimal.

Alternatively, you could print out only a couple of decimal places like this:

String answer = String.format("%.2f", myNumber);

This will round off the printed value to 2 decimal places.

Hope this helps!

Strange behavior of float point

The primary cause of the difference is that .9 + x •.1 is larger than .1 + x • .9 for any x < 1, and Random.nextFloat returns values less than 1. For the largest value it returns, the former expression is so close to 1 that rounding it to float produces 1, but the latter expression is farther from 1, and rounding it to float produces the next float below 1.

The largest value that Random.nextFloat returns is Random.nextfloat 16777215/16777216. Let M be this maximum, and let d = 1 − M = 1/16777216.

If we calculated t1 using real numbers, its maximum value would be .9 + M•.1. = .9 + (1−d)•.1 = 1−.1•d. Similarly, t2 would be .1 + M•.9 = .1 + (1-d)•.9 = 1-.9•d.

Now we can easily see that the maximum of t1 (if calculated with real numbers) is .1•d away from 1, but t2 is .9•d away from 1.

A reason the largest value Random.nextFloat returns is 16777215/16777216 is that it is the largest float less than 1. The precision of the float format is such that the steps between representable values leading up to 1 are 1/16777216. This means that the two representable values in this neighborhood are 16777215/16777216 and 1, and d is the distance between them. Our calculations above show us the maximum value of t1 is just .1•d away from 1. So, when it is rounded to float, 1 is the nearest float.

In contrast, the maximum value of t2 is .9•d away from 1. This means it is just .1•d away from 16777215/16777216. So, when it is rounded to float, 16777215/16777216 is the nearest float.

That is using real-number arithmetic. Below, I will show the floating-point arithmetic. It has rounding errors, but these turn out to be small enough not to change the results.

In Java, the source texts .1f and .9f are converted to float, which yield 0.100000001490116119384765625 and 0.89999997615814208984375. The arithmetic in the expressions is performed using double, and then the result is rounded to float for assignment to t1 or t2.

The largest that t1 can be may be calculated:

  • Substituting the largest return value of nextFloat yields 0.9f + 16777215./16777216 * 0.1f;.
  • Evaluating in double arithmetic yields 0.999999971687793642871611154987476766109466552734375.
  • Converting to float yields 1, because that double value is between 0.999999940395355224609375 (the next lower float value) and 1 (the next higher), and it is closer to the latter than to the former.

The largest that t2 can be may be calculated:

  • Substituting the largest return value of nextFloat yields 0.1f + 16777215./16777216 * 0.9f;.
  • Evaluating in double arithmetic yields 0.99999992400407933246242464520037174224853515625.
  • Converting to float yields 0.999999940395355224609375.

Floating point Weird Phenomenon in Java

This is a typical floating point result runded.

You get different results from Float and Double :

System.out.println(4.0f-3.1f);
System.out.println(4.0d-3.1d);

Output:

0.9000001
0.8999999999999999

This is because 0.1 cannot be represented evenly in base 2, and cause a loss of precision. For example :

System.out.println(2.0f-1.9f);
System.out.println(2.0d-1.9d);

Should both return 0.1 but in fact will output :

0.100000024
0.10000000000000009

Java strange behaviour when using double division in declaration/init

Because double b = 1 / 2; first computes 1 / 2, which is simple integer division and evaluates to 0, and then casts that value to the double 0.0 to assign it to b.

How come Java handle floating-point-imprecision so strange?

Java’s default formatting produces just enough decimal digits to uniquely distinguish the floating-point number from neighboring floating-point numbers.

When the source text 0.3 is converted to double, the result is the closest double value, which is 0.299999999999999988897769753748434595763683319091796875. When this is printed, “0.3” suffices to uniquely identify it, because 0.299999999999999988897769753748434595763683319091796875 is of course the closest value, since that is how we got it from 0.3 in the first place.

The source texts 0.1 and 0.2 produce 0.1000000000000000055511151231257827021181583404541015625 and
0.200000000000000011102230246251565404236316680908203125. When these are added in double format, the result is 0.3000000000000000444089209850062616169452667236328125. Note this is different from the number above. So, when it is printed, “0.3” does not suffice to distinguish it from the neighboring value 0.299999999999999988897769753748434595763683319091796875. It is necessary to produce “0.30000000000000004” to show that it is different.

When the value is converted to float, formatting for float is used, instead of formatting for double. When formatting for float, only values representable in float are candidates for what are neighbors. The value representable in float that is nearest 0.3 is 0.300000011920928955078125. This is also the result of (float) 0.1 + (float) 0.2, and it is sufficiently distinguished from the neighboring float values (0.2999999821186065673828125 and 0.3000000417232513427734375) by printing merely “0.3”.

Weird Java fraction behavior

That's the magic of binary encoding of floating point values (look for IEEE754 : http://en.wikipedia.org/wiki/IEEE_754-2008 ). If you want to be sure to never have this kind of things, you're maybe looking for BigDecimal :

http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html

Basic rules :

  • don't use equality tests when dealing with floating point numbers (you must test gaps)
  • round numbers you're displaying (usually using DecimalFormat)
  • don't use floating point numbers for financial applications
  • the float is generally the way to go for scientific or industrial operations, as long as you understand IEEE754

Java: Inaccuracy using double

Some decimals cannot be exactly represented by double values. 0.3 is one of those values.

All integer values less than a certain number (I forget which) happen to have an exact representation by a double value, so you don't see the approximation.

Consider how we think of numbers: the number 123 is represented as (1 * 100) + (2 * 10) + (3 * 1). We use 10 as our base. Binary numbers use two. So when you look at fractions of a number, how could you represent 0.3 by adding individual powers of 2? You can't. The best you can come up with is about 0.30000000000000004 (I'd have to see the exact binary digits to see how it reaches that).



Related Topics



Leave a reply



Submit