different behaviors of roundoff error in java - java

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.

Related

Java Math.IEEERemainder confusing result

Java's Math.IEEERemainder function states:
The remainder value is mathematically equal to f1 - f2 × n, where n is
the mathematical integer closest to the exact mathematical value of
the quotient f1/f2, and if two mathematical integers are equally close
to f1/f2, then n is the integer that is even
For the following:
double f1 = 0.1;
double f2 = 0.04;
System.out.println(Math.IEEEremainder(f1, f2));
The output is -0.019999999999999997
However, 0.1/0.04 = 2.5 which is equidistant from both the integers 2 and 3. Shouldn't we pick n = 2 here, resulting in 0.1 - 0.04*2 = 0.02, instead of -0.02 ?
See: Is floating point math broken?
You would think that 0.1 / 0.04 would return exactly 2.5, but that's not true. According to this article, 0.1 cannot be accurately represented using IEEE 754, and is actually represented as 0.100000000000000005551....
In this case, the quotient is slightly higher due to that minuscule offset, which results in a value of 3 for n, as it's no longer equidistant between 2 and 3.
Computing it results in the following:
0.1 - 0.04 * 3 = 0.1 - 0.12 = -0.02 ~= -0.019999999999999997

How to achieve "1 - 1/3 - 1/3 - 1/3 = 0"? [duplicate]

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.

Finding ranges of character frequencies [duplicate]

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.

How to improve Java calculate accuracy

in some cases I need to do really precise calculations in Java, but it always have some unexpected errors. How can I avoid them or keep the error in a acceptable range?
e.g.
public static void main(String[] args) throws Exception {
double x = 0.0;
while (x <= 1.0){
System.out.println(x);
x += 0.1;
System.out.println("add 0.1");
}
}
- the result will be
0.0 add 0.1
0.1 add 0.1
0.2 add 0.1
0.30000000000000004 add 0.1
0.4 add 0.1
0.5 add 0.1
0.6 add 0.1
0.7 add 0.1
0.7999999999999999 add 0.1
0.8999999999999999 add 0.1
0.9999999999999999 add 0.1
which is not as expected.
Thanks in advance
You should use BigDecimal in these cases. It saves you from various deviations you will find working with float or double.
For more information, Double vs. BigDecimal?
http://www.opentaps.org/docs/index.php/How_to_Use_Java_BigDecimal:_A_Tutorial
Rather than using BigDecimal you should probably just format your double when you're printing it out.
Replace your println with printf("%.1f\n", x); and you'll get the output you're looking for.
BigDecimal is needed when you absolutely need to have precise accuracy during complex calculations (such as when money is involved).

Java doubles adding strangely (and no, it's not for money)

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

Categories