How to avoid floating point precision errors with floats or doubles in Java?
I have a very annoying problem with long sums of floats or doubles in Java. Basic开发者_运维百科ally the idea is that if I execute:
for ( float value = 0.0f; value < 1.0f; value += 0.1f )
System.out.println( value );
What I get is:
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001
I understand that there is an accumulation of the floating precision error, however, how to get rid of this? I tried using doubles to half the error, but the result is still the same.
Any ideas?
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
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.
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.
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.
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.)
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
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.
You should use a decimal datatype, not floats:
https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
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));
}
}
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 :)
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.
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.
精彩评论