How to avoid floating point precision errors with floats or doubles in Java? [duplicate]
Asked Answered
D

12

32

I have aproblem with long sums of floats or doubles in Java.

If I execute:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )
    System.out.println( value );

I get:

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001

How do I get rid of the accumulation of the floating precision error?

I tried using doubles to halve the error, but the result is the same.

Dripps answered 10/3, 2011 at 8:35 Comment(2)
Closely related is this question: #6699566Petulah
I don’t agree with the closing of this question, as the answers have useful Java-specific information and code examples that the supposed “duplicate” does not. Unfortunately I’m outnumbered by the others who want almost all floating point questions directed to the main general one. See discussion on meta here.Crum
D
37

There is a no exact representation of 0.1 as a float or double. Because of this representation error the results are slightly different from what you expected.

A couple of approaches you can use:

  • When using the double type, only display as many digits as you need. When checking for equality allow for a small tolerance either way.
  • Alternatively use a type that allows you to store the numbers you are trying to represent exactly, for example BigDecimal can represent 0.1 exactly.

Example code for BigDecimal:

BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
     value.compareTo(BigDecimal.ONE) < 0;
     value = value.add(step)) {
    System.out.println(value);
}

See it online: ideone

Delectable answered 10/3, 2011 at 8:36 Comment(0)
F
11

Don't use float/double in an iterator as this maximises your rounding error. If you just use the following

for (int i = 0; i < 10; i++)
    System.out.println(i / 10.0);

it prints

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

I know BigDecimal is a popular choice, but I prefer double not because its much faster but its usually much shorter/cleaner to understand.

If you count the number of symbols as a measure of code complexity

  • using double => 11 symbols
  • use BigDecimal (from @Mark Byers example) => 21 symbols

BTW: don't use float unless there is a really good reason to not use double.

Franke answered 10/3, 2011 at 9:20 Comment(0)
S
10

You can avoid this specific problem using classes like BigDecimal. float and double, being IEEE 754 floating-point, are not designed to be perfectly accurate, they're designed to be fast. But note Jon's point below: BigDecimal can't represent "one third" accurately, any more than double can represent "one tenth" accurately. But for (say) financial calculations, BigDecimal and classes like it tend to be the way to go, because they can represent numbers in the way that we humans tend to think about them.

Spade answered 10/3, 2011 at 8:37 Comment(3)
It's not a matter of "precise" and "imprecise" - it's a matter of what can be represented in each type. BigDecimal is no more capable of representing "a third" exactly than double is capable of representing "a tenth" exactly.Brechtel
@Jon: Actually, as you commented I was editing, I'd said "precise" where I meant "accurate" (because everyone does, but I try to avoid doing it). Fascinating point about "a third", though. Very good point indeed.Spade
I'd say that "accurate" isn't necessarily a good word either. There are two issues here - one is base representation, and the other is a fixed or varying size (where BigDecimal can expand as it sees fit depending on the MathContext, whereas something like System.Decimal in .NET is always 128 bits). But it's definitely a complicated thing to describe concisely :) "Accurate" may or may not be appropriate for BigDecimal based on the MathContext used - I believe that with an "unlimited", operations will throw an exception if the result can't be represented exactly.Brechtel
K
4

It's not just an accumulated error (and has absolutely nothing to do with Java). 1.0f, once translated to actual code, does not have the value 0.1 - you already get a rounding error.

From The Floating-Point Guide:

What can I do to avoid this problem?

That depends on what kind of calculations you’re doing.

  • If you really need your results to add up exactly, especially when you work with money: use a special decimal datatype.
  • If you just don’t want to see all those extra decimal places: simply format your result rounded to a fixed number of decimal places when displaying it.
  • If you have no decimal datatype available, an alternative is to work with integers, e.g. do money calculations entirely in cents. But this is more work and has some drawbacks.

Read the linked-to site for detailed information.

Khosrow answered 10/3, 2011 at 8:40 Comment(0)
A
3

Another solution is to forgo == and check if the two values are close enough. (I know this is not what you asked in the body but I'm answering the question title.)

Arlon answered 10/3, 2011 at 8:45 Comment(0)
H
3

For the sake of completeness I recommend this one:

Shewchuck, "Robust Adaptive Floating-Point Geometric Predicates", if you want more examples of how to perform exact arithmetic with floating point - or at least controlled accuracy which is the original intention of author, http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf

Homochromatic answered 16/11, 2012 at 20:28 Comment(0)
S
2

You should use a decimal datatype, not floats:

https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

Sauer answered 10/3, 2011 at 8:37 Comment(1)
@anivaler The link was broken from the beginning. Java 1.4 was long dead when this answer was posted.Michelinamicheline
E
2

I had faced same issue, resolved the same using BigDecimal. Below is the snippet which helped me.

double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
    total += (double)array[i];
    bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);

Hope it will help you.

Exterior answered 24/7, 2015 at 6:39 Comment(0)
M
2
package loopinamdar;

import java.text.DecimalFormat;

public class loopinam {
    static DecimalFormat valueFormat = new DecimalFormat("0.0");

    public static void main(String[] args) {
        for (float value = 0.0f; value < 1.0f; value += 0.1f)
            System.out.println("" + valueFormat.format(value));
    }
}
Medea answered 12/5, 2016 at 20:7 Comment(0)
T
2

First make it a double. Don't ever use float or you will have trouble using the java.lang.Math utilities.

Now if you happen to know in advance the precision you want and it is equal or less than 15, then it becomes easy to tell your doubles to behave. Check below:

// the magic method:
public final static double makePrecise(double value, int precision) {
    double pow = Math.pow(10, precision);
    long powValue = Math.round(pow * value);
    return powValue / pow;
}

Now whenever you make an operation, you must tell your double result to behave:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 1) + " => " + value );

Output:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999

If you need more than 15 precision then you are out of luck:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 16) + " => " + value );

Output:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999

NOTE1: For performance you should cache the Math.pow operation in an array. Not done here for clarity.

NOTE2: That's why we never use doubles for prices, but longs where the last N (i.e. where N <= 15, usually 8) digits are the decimal digits. Then you can forget about what I wrote above :)

Teddy answered 13/4, 2018 at 20:31 Comment(0)
T
0

If you want to keep on using float and avoid accumulating errors by repeatedly adding 0.1f, try something like this:

for (int count = 0; count < 10; count++) {
    float value = 0.1f * count;
    System.out.println(value);
}

Note however, as others have already explained, that float is not an infinitely precise data type.

Thao answered 10/3, 2011 at 9:18 Comment(0)
D
0

You just need to be aware of the precision required in your calculation and the precision your chosen data type is capable of and present your answers accordingly.

For example, if you are dealing with numbers with 3 significant figures, use of float (which provides a precision of 7 significant figures) is appropriate. However, you can't quote your final answer to a precision of 7 significant figures if your starting values only have a precision of 2 significant figures.

5.01 + 4.02 = 9.03 (to 3 significant figures)

In your example you are performing multiple additions, and with each addition there is a consequent impact on the final precision.

Decretory answered 10/3, 2011 at 10:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.