So this is the only relevant section of the code
System.out.println("first term of " + firstTerm +
" second term of " + secondTerm +
" third term of " + finalTermHolder +
" should equal " + oppositeIntHolder);
double holder = firstTerm + secondTerm + finalTermHolder;
System.out.println(holder + " should equal " + oppositeIntHolder);
This is uninterrupted code, there is nothing in between these. The output for the first println is:
first term of 2.5147186257614296 second term of -9.514718625761429 third term of 7.0 should equal 0.0
The second println results in:
8.881784197001252E-16 should equal 0.0
Why are -9.5, 2.5, and 7 adding up to 8.9 instead of 0?
they are not adding up to 8.9. they are adding up to 8.9e-16. that's something like 0.00000000000000089
even if the numbers were displayed as -9.5 etc, you might still see this. it is because binary computers do not store decimals exactly. small errors occur. and yes, this is exactly the problem that happens with money.
8.881784197001252E-16 is a lot closer to zero than you think ; )
Double in Java is a floating point number. IF you're looking for an exact representation of the number try using BigDecimal instead of Double
BigDecimal num1 = new BigDecimal(3.32);
BigDecimal num2 = new BigDecimal(3.68);
System.out.println(num1.add(num2)); //will output 7.0
When you perform double operations you need to provide appropriate rounding. Even for BigDecimal division you need to provide appropriate rounding.
For printing double a small amount of rounding is done so you don't see the representation error. However after a few calculations (only one is needed) you the rounding error is too large and you can see the error.
If you want to see the representation and rounding error use BigDecimal as it does an exact conversion from double. Something which can be surprising in itself.
BTW, you won't get a rounding error with simple powers of 2. so -9.5 + 2.5 + 7.0 will always be 0.0. You only get rounding error with other decimals like 0.1
double[] ds = {
0.1,
0.2,
-0.3,
0.1 + 0.2 - 0.3};
for (double d : ds) {
System.out.println(d + " => " + new BigDecimal(d));
}
prints
0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 => 5.5511151231257827021181583404541015625E-17
You can see that the representation for 0.1 and 0.2 is slightly higher than those values, and -0.3 is also slightly higher. When you print them, you get the nicer 0.1 instead of the actual value represented 0.1000000000000000055511151231257827021181583404541015625
However, when you add these values together, you get a value which is slightly higher than 0.
To resolve this issue, you need to provide appropriate rounding. With money this is easy as you know how many decimal places are appropriate and unless you have $70 trillion you won't get a rounding error large enough you cannot correct it.
public static double roundToTwoPlaces(double d) {
return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
}
If you add this into the result, there is still a small representation error, however it is not large enough that the Double.toString(d) cannot correct for it.
double[] ds = {
0.1,
0.2,
-0.3,
0.1 + 0.2 - 0.3};
for (double d : ds) {
System.out.println(d + " to two places " + roundToTwoPlaces(d) + " => " + new BigDecimal(roundToTwoPlaces(d)));
}
prints
0.1 to two places 0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 to two places 0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 to two places -0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 to two places 0.0 => 0
Related
I know about roundoff error in programming languages!
System.out.println(0.1 + 0.1 + 0.1);
this code output is 0.30000000000000004 because 0.3 in binary needs an infinite number of digits to be represented and 0.3 is an irrational number in binary.
but what about this one?
System.out.println(0.1 + 0.1);
why output is 0.2 ? 0.2 is also an irrational number in binary! so the output should be 0.200000002 or 0.1999999999!
what's the difference between them?
When you convert a floating point number to a String in Java, it's done in a way that uses the least number of digits necessary to distinguish the number from the adjacent numbers.
This means that 0.2 is displayed as "0.2", since you don't need any more digits. The true value of 0.2 is of course a bit greater than 0.2:
jshell> new BigDecimal(0.2)
$1 ==> 0.200000000000000011102230246251565404236316680908203125
Another interpretation of your question is "why is 0.1 + 0.1 equal to 0.2?"
It's because the error in computing 0.1+0.1 is not large enough to make it become distinct from 0.2. It's of course not exactly the same value as 0.2, but out of all floating point numbers 0.2 is the closest.
jshell> new BigDecimal(0.1).add(new BigDecimal(0.1))
$2 ==> 0.2000000000000000111022302462515654042363166809082031250
When a double value is printed, Java uses the return value of Double.toString(double), which says:
There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double.
So, lets print the values of 0.1 - 1.0, and their adjacent values. We use Math.nextDown(double) and Math.nextUp(double) to find the adjacent values. We use new BigDecimal(double) and toPlainString() to see more digits from the value.
We also calculate the value from summing multiple 0.1 values and mark that value with a <, =, or >, as appropriate.
System.out.printf("%9s%-13s%-12s%s%n", "", "double", "sum", "BigDecimal");
double sum = 0.1;
for (int i = 1; i <= 10; i++, sum += 0.1) {
double val = i / 10d;
double down = Math.nextDown(val);
double up = Math.nextUp(val);
System.out.printf("%d:%n %-21s%-3s%s%n %-21s%-3s%s%n %-21s%-3s%s%n",
i,
down, (sum == down ? "<" : " "), new BigDecimal(down).toPlainString(),
val, (sum == val ? "=" : " "), new BigDecimal(val).toPlainString(),
up, (sum == up ? ">" : " "), new BigDecimal(up).toPlainString());
}
Output
double sum BigDecimal
1:
0.09999999999999999 0.09999999999999999167332731531132594682276248931884765625
0.1 = 0.1000000000000000055511151231257827021181583404541015625
0.10000000000000002 0.10000000000000001942890293094023945741355419158935546875
2:
0.19999999999999998 0.1999999999999999833466546306226518936455249786376953125
0.2 = 0.200000000000000011102230246251565404236316680908203125
0.20000000000000004 0.2000000000000000388578058618804789148271083831787109375
3:
0.29999999999999993 0.29999999999999993338661852249060757458209991455078125
0.3 0.299999999999999988897769753748434595763683319091796875
0.30000000000000004 > 0.3000000000000000444089209850062616169452667236328125
4:
0.39999999999999997 0.399999999999999966693309261245303787291049957275390625
0.4 = 0.40000000000000002220446049250313080847263336181640625
0.4000000000000001 0.400000000000000077715611723760957829654216766357421875
5:
0.49999999999999994 0.499999999999999944488848768742172978818416595458984375
0.5 = 0.5
0.5000000000000001 0.50000000000000011102230246251565404236316680908203125
6:
0.5999999999999999 0.5999999999999998667732370449812151491641998291015625
0.6 = 0.59999999999999997779553950749686919152736663818359375
0.6000000000000001 0.600000000000000088817841970012523233890533447265625
7:
0.6999999999999998 0.69999999999999984456877655247808434069156646728515625
0.7 = 0.6999999999999999555910790149937383830547332763671875
0.7000000000000001 0.70000000000000006661338147750939242541790008544921875
8:
0.7999999999999999 < 0.79999999999999993338661852249060757458209991455078125
0.8 0.8000000000000000444089209850062616169452667236328125
0.8000000000000002 0.80000000000000015543122344752191565930843353271484375
9:
0.8999999999999999 < 0.899999999999999911182158029987476766109466552734375
0.9 0.90000000000000002220446049250313080847263336181640625
0.9000000000000001 0.9000000000000001332267629550187848508358001708984375
10:
0.9999999999999999 < 0.99999999999999988897769753748434595763683319091796875
1.0 1
1.0000000000000002 1.0000000000000002220446049250313080847263336181640625
As you can see, because of cumulative rounding issues, the summed value is not always exactly the value closest to what you'd expect, so it has to print extra digits for that "unique" value.
The summed value is wrong for 0.3, 0.8, 0.9, and 1.0.
I have a very annoying problem with long sums of floats or doubles in Java. Basically 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.
I have a very annoying problem with long sums of floats or doubles in Java. Basically 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.
When I try to parse the following string into a float and into a double :
String abc = "8.40";
System.out.println("Double Value: " + Double.parseDouble(abc) * 100);
System.out.println("Float Value: " + Float.parseFloat(abc) * 100);
I get two different results.
Double Value: 840.0
Float Value: 839.99994
But when I try the same code with multiplying the float and double by 10 or 1000 I get the similar results for both of them.
String abc = "8.40";
System.out.println("Double Value: " + Double.parseDouble(abc) * 10);
System.out.println("Float Value: " + Float.parseFloat(abc) * 10);
I get two similar results.
Double Value: 84.0
Float Value: 84.0
And when I try this :
String abc = "8.40";
System.out.println("Double Value: " + Double.parseDouble(abc) * 1000);
System.out.println("Float Value: " + Float.parseFloat(abc) * 1000);
I get two similar results.
Double Value: 8400.0
Float Value: 8400.0
This will work fine:
System.out.println("Float Value: "+Math.round((float)Float.parseFloat(abc)*100));
So, this happens because of different representation of double and float, or more precise, about IEEE-754 rounding for float. Read about it here.
float has a smaller range and precision, so double would be better when you have memory (which you do today). But, they are both evil! There is a better option in Java called BigDecimal and you should use it, since it doesn't have problem with size and today we have strong computers so we will not have problems with memory and speed when dealing with a large number of decimal numbers needing max precision. For example, if you work on software that deals with a lot of money transactions, its a must to use BigDecimal.
It is true that double has more precision than float, but both of them suffer from the same problem: their value may not be exact, and they both have some (small) rounding error in their Least Significant Bit (LSB). This is clear in the first result you got: float value is not accurate. But when you multiply by 10 or 1000, the LSB is discarded from the result, and so you get the right answer for both float and double.
I am trying to make a BigDecimal from a string. Don't ask me why, I just need it! This is my code:
Double theDouble = new Double(".3");
System.out.println("The Double: " + theDouble.toString());
BigDecimal theBigDecimal = new BigDecimal(theDouble);
System.out.println("The Big: " + theBigDecimal.toString());
This is the output I get?
The Double: 0.3
The Big: 0.299999999999999988897769753748434595763683319091796875
Any ideas?
When you create a double, the value 0.3 cannot be represented exactly. You can create a BigDecimal from a string without the intermediate double, as in
new BigDecimal("0.3")
A floating point number is represented as a binary fraction and an exponent. Therefore there are some number that cannot be represented exactly. There is an analogous problem in base 10 with numbers like 1/3, which is 0.333333333..... Any decimal representation of 1/3 is inexact. This happens to a DIFFERENT set of fractions in binary, and 0.3 is one of the set that is inexact in binary.
Another way is to use MathContext.DECIMAL32 which guarantees 7 digit precision (which is good enough in our case):
Double theDouble = new Double(".3");
System.out.println("The Double: " + theDouble.toString());
BigDecimal theBigDecimal = new BigDecimal(theDouble, MathContext.DECIMAL32); // <-- here
System.out.println("The Big: " + theBigDecimal.toString());
OUTPUT
The Double: 0.3
The Big: 0.3000000
Since new Double(".3") can't be represented exactly, the nearest value is 0x1.3333333333333P-2 or .299999999999999988897769753748434595763683319091796875, what would be need to is this:
Double theDouble = new Double(".3");
System.out.println("The Double: " + theDouble.toString());
BigDecimal theBigDecimal = new
BigDecimal(theDouble).setScale(2, RoundingMode.CEILING); // <-- here
System.out.println("The Big: " + theBigDecimal.toString());
This will print:
The Double: 0.3
The Big: 0.30
You can give a big decimal a specified precision. e.g. append to your example:
Double theDouble = new Double(".3");
theBigDecimal = new BigDecimal(theDouble, new MathContext(2));
System.out.println("The Big: " + theBigDecimal.toString());
This will print out "0.30"