Why does adding 0.1 multiple times remain lossless? - java

I know the 0.1 decimal number cannot be represented exactly with a finite binary number (explanation), so double n = 0.1 will lose some precision and will not be exactly 0.1. On the other hand 0.5 can be represented exactly because it is 0.5 = 1/2 = 0.1b.
Having said that it is understandable that adding 0.1 three times will not give exactly 0.3 so the following code prints false:
double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
sum += d;
System.out.println(sum == 0.3); // Prints false, OK
But then how is it that adding 0.1 five times will give exactly 0.5? The following code prints true:
double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?
If 0.1 cannot be represented exactly, how is it that adding it 5 times gives exactly 0.5 which can be represented precisely?

The rounding error is not random and the way it is implemented it attempts to minimise the error. This means that sometimes the error is not visible, or there is not error.
For example 0.1 is not exactly 0.1 i.e. new BigDecimal("0.1") < new BigDecimal(0.1) but 0.5 is exactly 1.0/2
This program shows you the true values involved.
BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
x = x.add(_0_1);
}
prints
0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0
Note: that 0.3 is slightly off, but when you get to 0.4 the bits have to shift down one to fit into the 53-bit limit and the error is discarded. Again, an error creeps back in for 0.6 and 0.7 but for 0.8 to 1.0 the error is discarded.
Adding it 5 times should cumulate the error, not cancel it.
The reason there is an error is due to limited precision. i.e 53-bits. This means that as the number uses more bits as it get larger, bits have to be dropped off the end. This causes rounding which in this case is in your favour.
You can get the opposite effect when getting a smaller number e.g. 0.1-0.0999 => 1.0000000000000286E-4
and you see more error than before.
An example of this is why in Java 6 Why does Math.round(0.49999999999999994) return 1 In this case the loss of a bit in calculation results in a big difference to the answer.

Barring overflow, in floating-point, x + x + x is exactly the correctly rounded (i.e. nearest) floating-point number to the real 3*x, x + x + x + x is exactly 4*x, and x + x + x + x + x is again the correctly rounded floating-point approximation for 5*x.
The first result, for x + x + x, derives from the fact that x + x is exact. x + x + x is thus the result of only one rounding.
The second result is more difficult, one demonstration of it is discussed here (and Stephen Canon alludes to another proof by case analysis on the last 3 digits of x). To summarize, either 3*x is in the same binade as 2*x or it is in the same binade as 4*x, and in each case it is possible to deduce that the error on the third addition cancels the error on the second addition (the first addition being exact, as we already said).
The third result, “x + x + x + x + x is correctly rounded”, derives from the second in the same way that the first derives from the exactness of x + x.
The second result explains why 0.1 + 0.1 + 0.1 + 0.1 is exactly the floating-point number 0.4: the rational numbers 1/10 and 4/10 get approximated the same way, with the same relative error, when converted to floating-point. These floating-point numbers have a ratio of exactly 4 between them. The first and third result show that 0.1 + 0.1 + 0.1 and 0.1 + 0.1 + 0.1 + 0.1 + 0.1 can be expected to have less error than might be inferred by naive error analysis, but, in themselves, they only relate the results to respectively 3 * 0.1 and 5 * 0.1, which can be expected to be close but not necessarily identical to 0.3 and 0.5.
If you keep adding 0.1 after the fourth addition, you will finally observe rounding errors that make “0.1 added to itself n times” diverge from n * 0.1, and diverge even more from n/10. If you were to plot the values of “0.1 added to itself n times” as a function of n, you would observe lines of constant slope by binades (as soon as the result of the nth addition is destined to fall into a particular binade, the properties of the addition can be expected to be similar to previous additions that produced a result in the same binade). Within a same binade, the error will either grow or shrink. If you were to look at the sequence of the slopes from binade to binade, you would recognize the repeating digits of 0.1 in binary for a while. After that, absorption would start to take place and the curve would go flat.

Floating point systems do various magic including having a few extra bits of precision for rounding. Thus the very small error due to the inexact representation of 0.1 ends up getting rounded off to 0.5.
Think of floating point as being a great but INEXACT way to represent numbers. Not all possible numbers are easily represented in a computer. Irrational numbers like PI. Or like SQRT(2). (Symbolic math systems can represent them, but I did say "easily".)
The floating point value may be extremely close, but not exact. It may be so close that you could navigate to Pluto and be off by millimeters. But still not exact in a mathematical sense.
Don't use floating point when you need to be exact rather than approximate. For example, accounting applications want to keep exact track of a certain number of pennies in an account. Integers are good for that because they are exact. The primary issue you need to watch for with integers is overflow.
Using BigDecimal for currency works well because the underlying representation is an integer, albeit a big one.
Recognizing that floating point numbers are inexact, they still have a great many uses. Coordinate systems for navigation or coordinates in graphics systems. Astronomical values. Scientific values. (You probably cannot know the exact mass of a baseball to within a mass of an electron anyway, so inexactness doesn't really matter.)
For counting applications (including accounting) use integer. For counting the number of people that pass through a gate, use int or long.

Related

Math.sin giving weird answers

I have:
double num1 = sc.nextInt();
I used:
double sin = Math.sin(Math.toRadians(num1));
and made the output:
if(calc.contains("sin")) {
System.out.println (sin);
}
if i typed in for num1:
30 and it calculates sin. It gives me 0.49999999999999994
π/6 cannot be represented exactly in a computer (it can't be represented exactly on paper, either). This will cause the result to be a little bit off too. Using "bc", a Unix high-precision calculator (note: I don't know just how accurate it is for sine and cosine), I find that the actual value in your program will be π/6 + ε where ε is about 5.3604 x 10-17. Using the formula for sin(x+y), the expected result should be sin π/6 cos ε + sin ε cos π/6. cos ε is about 1 - 10-33, so this difference won't be enough to affect the result. However, sin ε cos π/6 is about 4.64 x 10-17. So the actual result should be something like 0.4999999999999999535774978 instead of 0.5.
This result won't be represented exactly in a double, either. Because a double has a mantissa of 52 bits, numbers whose values are >= 0.25 and < 0.5 could be represented by numbers that are off by as much as 2-54. The double used to represent this result would be 0.499999999999999944488848768742172978818416595458984375. When this is printed with System.out.println, it will stop after a certain number of decimal places are printed, so this gets truncated to 0.49999999999999994, which is the result you're seeing. (The number of digits displayed is discussed in [http://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#toString-double-](this javadoc).
sin(30°) is surely 0.5, but there is not a dictionary that contains a key of 30° with value 0.5, so computer need to calculate it.
The formula is showing above.
Let's calculate sin(30°), 30° = π/6, so f(x)= π/6 - π^3/1296 - π^5/933120 - ....
And then in this process, accuracy error can lead to "unpredictable"(actually predictable) problems.

Floating point precision in literals vs calculations

I'm wondering why floating point numbers in Java can represent exact value when they are initialized as literals, but they are approximate when they represent result of some calculation.
For example:
double num1 = 0.3;
double num2 = 0.1 + 0.2;
System.out.println(num1);
System.out.println(num2);
why the result is:
0.3
0.30000000000000004
and not:
0.30000000000000004
0.30000000000000004
When there is no exact binary representation of 0.3.
I know the BigDecimal class, but I don't quite understand this primitive numbers inconsistency.
None of the three numbers can be represented exactly as a double. The reason that you get different results is that the value after adding 0.1 to 0.2 has a different representation error than 0.3. The difference of about 5.5E-17 is enough to cause a difference when printing out the result (demo).
double a = 0.2;
double b = 0.1;
double c = 0.3;
double d = a+b;
double e = d-c; // This is 5.551115123125783E-17
When 0.3 is converted to its representation as ones and zeroes then converted back to decimal, it rounds to 0.3.
However, when 0.1 and 0.2 are respectively converted to binary, the errors add up upon addition so as to show up when the sum is converted back to decimal.
A thorough explanation would involve demonstrating the IEEE representation of each number along with the addition and conversions. A bit involved, but I hope you got the idea.
The addition itself cannot produce an exact representation of 0.3, hence printing the result of 0.1 + 0.2 yields 0.30000000000000004.
On the other hand, when calling System.out.println(0.3);, the println(double) method will perform some rounding on the result: it eventually calls Double.toString(double) which mentions that the result is approximate:
How many digits must be printed for the fractional part of m or a? 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. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0.
If you use a BigDecimal the difference can be seen:
System.out.println(0.3); // 0.3
System.out.println(new BigDecimal(0.3)); // 0.299999999999999988897769753748434595763683319091796875

what maks the code infinite loop in java

public class yyy
{
public static void main(String[] args)
{
double sum = 0;
float d = 0;
while (d != 10.0) {
d += 0.1;
sum += sum + d;
}
System.out.print("The sum is: "+sum);
}
}
Where is the error which causes the infinite loop?
Why?
I thought that the type is the problem but its not; I changed d to double but nothing changed.
It's an infinite loop because d is never equal to 10.0. Why not? Because floating point numbers are not perfectly precise, tiny errors creep in as you add 0.1 to d. You can watch this in the debugger, stepping through the code. The kind of floating point used in Java is IEEE-754, more info here.
Here are the values as d approaches, and then skips over, 10.0:
d = 9.4
d = 9.5
d = 9.6
d = 9.700001
d = 9.800001
d = 9.900002
d = 10.000002
What makes the loop infinite is the imprecision of float's representation of 0.1: since it is not an exact sum of negative powers of two, adding it 100 times to itself does not make exactly 10.0, even though in theory it does. The number is very close, but since you use !=, the loop never stops.
Replace with
while (d < 10.05)
to fix the infinite loop problem. However, this is not the most readable approach: using an int for your loop counter would be a lot cleaner.
Note that if you used a different step, for example, 0.125, your loop would have worked. This is because 0.125 is 2 ^ -3, which has an exact representation as a float.
I changed d to double but nothing changed.
The double type has the same logical representation as float, but with more bits to extend its range and precision. Using BigDecimal would have fixed the problem, though, because this data type uses a different representation, which allows 0.1 to be represented exactly.
The main point that you should learn from this exercise is that you need to be very careful when comparing floating point values for equality and inequality.
The infinite loop is due to the inexact way computers represent floating-point numbers.
A computer can dedicate only a certain number of bits to each variable. You can think of this as storing a limited number of digits after the decimal place. In base ten you can represent 1/10 exactly as 0.1, so you can write d with perfect precision as it progresses through 0.0, 0.1, 0.2, ..., 10.0. But imagine incrementing by 1/7. This is a repeating decimal (0.142857142857...)
and therefore cannot be represented precisely in base 10 with a limited number of digits. If the computer could store only 2 decimal places for each variable, then d would look something like this:
Fraction Actual Value Stored Value
1/7 0.142857... 0.14
2/7 0.285714... 0.28
3/7 0.428571... 0.42
4/7 0.571428... 0.56 ← mismatch
5/7 0.714285... 0.70
6/7 0.857142... 0.84
7/7 1.0 0.99
Note the rounding errors in every value from 4/7 onward. (Actually, every value has a rounding error due to truncation; it's just more obvious when the digits don't match.) The important thing to notice is that it doesn't matter how many digits we store; unless it's infinite, there will always be a rounding error.
So, in base ten, incrementing a variable by 0.1 is simple and "clean" because the number can be represented exactly with a limited number of digits. But that's not the case for numbers that are represented by repeating decimals, like 1/6, 1/7, 1/13, and so on.
Computers store numbers in binary (base 2), but the concept is exactly the same. The fraction 1/10 does not have an exact representation in base 2. The computer must represent every number by adding together different powers of 2: numbers like 8, 4, 2, 1, ½ ¼, &frac18;, and so on. For example:
15 = 8 + 4 + 2 + 1 = 1111b
10 = 8 + 0 + 2 + 0 = 1010b
2½ = 0 + 0 + 2 + 0 + ½ = 0010.1b
¾ = 0 + 0 + 0 + 0 + ½ + ¼ = 0010.11b
But we can't represent 1/10 exactly using only powers of 2:
1/10 = 0/2 + 0/4 + 0/8 + 1/16 + 1/32 + 0/64 + 0/128 + 1/256 +
1/512 + 0/1024 + 0/2048 + 1/4096 + 1/8192 + ...
1/10 = 0.0001100110011...b
Again, imagine we have a limited number of decimal places. You'll see that no matter how many we use, we'll eventually generate a rounding error if we continue to add 1/10. And that's exactly what happens in your program: repeatedly adding the binary representation of 1/10 will generate a rounding error before the sum reaches 10.0, so the condition d != 10.0 will always be true.
Because of this, when working with floating-point numbers, the best practice is as several others suggested: never test floating-point variables for equality; always use inequalities. You can eliminate the infinite loop with while (d < 10.0).
Doubles and floats have what is known as floating-point precision, i.e. besides the 0.1 you are adding there are many more decimal places which you do not see. As T.J. Crowder said, you can see the values by stepping through with a debugger. My suggestion is to work with ints when comparing values, otherwise check out this suggestion.
The best way to do this loop is using fixed precision. i.e. use an integer value assuming one decimal place which you add at the end. As you can see the room for error is very small.
int sum = 0;
for(int i = 0; i <= 100; i++)
sum += i;
// sum is 10x the value you need.
System.out.print("The sum is: " + sum/10.0);
The reason this helps is that otherwise you are performing a long series of imprecise calculations and looking for an exact outcome of 10.0 which is possible by highly unlikely. i.e. the cumulative error could be zero, but you wouldn't want to assume it is for different values.

Weird Java fraction behavior

I have seen a very weird behaviour in Java's double variable, as I'm trying to simply add small fractions to a double and I see a completely bizarre results.
double test = 0;
test += 0.71;
test += 0.2;
Now I'd expect the result to be:
test = 0.91
Right? Wrong!
In reality, this is the number I get in my test double:
test = 0.9099999999999999
Now while this is very close, it's a very bizarre fraction loss, and in the long run it causes serious bugs in my program.
With a float I've gotten even a weirder result.
Any help would be greatly appreciated.
Thanks
There is nothing bizarre about it at all. 0.91, 0.71 and 0.2 are not representable as a IEEE754 floating point values as they would have a recurring fractional part when represented in binary. The situation is entirely analogous to trying to represent 1/3 in base 10 with a finite number of digits. You can't do it.
What you are seeing is a rounding error that is normal when doing floating point calculations. You have to code around it. So for instance, you can't reliably compare for equality, you have to see the two numbers are within some small delta of each other. For a slightly more in depth but still understandable explanation see The Floating Point Guide.
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
double can only approximate most fractional values. This means you need to use some rounding if you want to get your expect result. Or you can use BigDecimal which takes care of this issue for you.
double test = 0;
test += 0.71;
test += 0.2;
System.out.printf("%.2f%n", test);
prints
0.91
For your own interest
System.out.println("0.71 is actually " + new BigDecimal(0.71));
System.out.println("0.2 is actually " + new BigDecimal(0.2));
System.out.println("0.71+0.2 is actually " + new BigDecimal(0.71 + 0.2));
System.out.println("0.91 is actually " + new BigDecimal(0.91));
System.out.println("0.71+0.2 == 0.91 is " + (0.71 + 0.2 == 0.91));
prints
0.71 is actually 0.70999999999999996447286321199499070644378662109375
0.2 is actually 0.200000000000000011102230246251565404236316680908203125
0.71+0.2 is actually 0.9099999999999999200639422269887290894985198974609375
0.91 is actually 0.91000000000000003108624468950438313186168670654296875
0.71+0.2 == 0.91 is false
Java uses something called floating-point to represent decimals. They use exponential notation. Here's what I mean:
There is a multiplier (M), and an exponent between 1023 and -1022 (E).
A number (N) is represented like this: M * 2^E.
4.25 is represented like this:
17 * 2^-2.
0.91 cannot be represented in base 2 exactly, but Java can get pretty close:
0.909999999999..
Therefore, it is impossible to accurately add these numbers together.

Java:Why should we use BigDecimal instead of Double in the real world? [duplicate]

This question already has answers here:
Double vs. BigDecimal?
(7 answers)
Closed 7 years ago.
When dealing with real world monetary values, I am advised to use BigDecimal instead of Double.But I have not got a convincing explanation except, "It is normally done that way".
Can you please throw light on this question?
I think this describes solution to your problem: Java Traps: Big Decimal and the problem with double here
From the original blog which appears to be down now.
Java Traps: double
Many traps lay before the apprentice programmer as he walks the path of software development. This article illustrates, through a series of practical examples, the main traps of using Java's simple types double and float. Note, however, that to fully embrace precision in numerical calculations you a text book (or two) on the topic is required. Consequently, we can only scratch the surface of the topic. That being said, the knowledge conveyed here, should give you the fundamental knowledge required to spot or identify bugs in your code. It is knowledge I think any professional software developer should be aware of.
Decimal numbers are approximations
While all natural numbers between 0 - 255 can be precisely described using 8 bit, describing all real numbers between 0.0 - 255.0 requires an infinitely number of bits. Firstly, there exists infinitely many numbers to describe in that range (even in the range 0.0 - 0.1), and secondly, certain irrational numbers cannot be described numerically at all. For example e and π. In other words, the numbers 2 and 0.2 are vastly differently represented in the computer.
Integers are represented by bits representing values 2n where n is the position of the bit. Thus the value 6 is represented as 23 * 0 + 22 * 1 + 21 * 1 + 20 * 0 corresponding to the bit sequence 0110. Decimals, on the other hand, are described by bits representing 2-n, that is the fractions 1/2, 1/4, 1/8,... The number 0.75 corresponds to 2-1 * 1 + 2-2 * 1 + 2-3 * 0 + 2-4 * 0 yielding the bits sequence 1100 (1/2 + 1/4).
Equipped with this knowledge, we can formulate the following rule of thumb: Any decimal number is represented by an approximated value.
Let us investigate the practical consequences of this by performing a series of trivial multiplications.
System.out.println( 0.2 + 0.2 + 0.2 + 0.2 + 0.2 );
1.0
1.0 is printed. While this is indeed correct, it may give us a false sense of security. Coincidentally, 0.2 is one of the few values Java is able to represent correctly. Let's challenge Java again with another trivial arithmetical problem, adding the number 0.1 ten times.
System.out.println( 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f );
System.out.println( 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d );
1.0000001
0.9999999999999999
According to slides from Joseph D. Darcy's blog the sums of the two calculations are 0.100000001490116119384765625 and 0.1000000000000000055511151231... respectively. These results are correct for a limited set of digits. float's have a precision of 8 leading digits, while double has 17 leading digits precision. Now, if the conceptual mismatch between the expected result 1.0 and the results printed on the screens were not enough to get your alarm bells going, then notice how the numbers from mr. Darcy's slides does not seem to correspond to the printed numbers! That's another trap. More on this further down.
Having been made aware of mis-calculations in seemingly the simples possible scenarios, it is reasonable to contemplate on just how quickly the impression may kick in. Let us simplify the problem to adding only three numbers.
System.out.println( 0.3 == 0.1d + 0.1d + 0.1d );
false
Shockingly, the imprecision already kicks in at three additions!
Doubles overflow
As with any other simple type in Java, a double is represented by a finite set of bits. Consequently, adding a value or multiplying a double can yield surprising results. Admitedly, numbers have to be pretty big in order to overflow, but it happens. Let's try multiplying and then dividing a big number. Mathematical intuition says that the result is the original number. In Java we may get a different result.
double big = 1.0e307 * 2000 / 2000;
System.out.println( big == 1.0e307 );
false
The problem here is that big is first multiplied, overflowing, and then the overflowed number is divided. Worse, no exception or other kinds of warnings are raised to the programmer. Basically, this renders the expression x * y completely unreliable as no indication or guarantee is made in the general case for all double values represented by x, y.
Large and small are not friends!
Laurel and Hardy were often disagreeing about a lot of things. Similarly in computing, large and small are not friends. A consequence of using a fixed number of bits to represent numbers is that operating on really large and really small numbers in the same calculations will not work as expected. Let's try adding something small to something large.
System.out.println( 1234.0d + 1.0e-13d == 1234.0d );
true
The addition has no effect! This contradicts any (sane) mathematical intuition of addition, which says that given two numbers positive numbers d and f, then d + f > d.
Decimal numbers cannot be directly compared
What we have learned so far, is that we must throw away all intuition we have gained in math class and programming with integers. Use decimal numbers cautiously. For example, the statement for(double d = 0.1; d != 0.3; d += 0.1) is in effect a disguised never ending loop! The mistake is to compare decimal numbers directly with each other. You should adhere to the following guide lines.
Avoid equality tests between two decimal numbers. Refrain from if(a == b) {..}, use if(Math.abs(a-b) < tolerance) {..} where tolerance could be a constant defined as e.g. public static final double tolerance = 0.01
Consider as an alternative to use the operators <, > as they may more naturally describe what you want to express. For example, I prefer the form
for(double d = 0; d <= 10.0; d+= 0.1) over the more clumsy
for(double d = 0; Math.abs(10.0-d) < tolerance; d+= 0.1)
Both forms have their merits depending on the situation though: When unit testing, I prefer to express that assertEquals(2.5, d, tolerance) over saying assertTrue(d > 2.5) not only does the first form read better, it is often the check you want to be doing (i.e. that d is not too large).
WYSINWYG - What You See Is Not What You Get
WYSIWYG is an expression typically used in graphical user interface applications. It means, "What You See Is What You Get", and is used in computing to describe a system in which content displayed during editing appears very similar to the final output, which might be a printed document, a web page, etc. The phrase was originally a popular catch phrase originated by Flip Wilson's drag persona "Geraldine", who would often say "What you see is what you get" to excuse her quirky behavior (from wikipedia).
Another serious trap programmers often fall into, is thinking that decimal numbers are WYSIWYG. It is imperative to realize, that when printing or writing a decimal number, it is not the approximated value that gets printed/written. Phrased differently, Java is doing a lot of approximations behind the scenes, and persistently tries to shield you from ever knowing it. There is just one problem. You need to know about these approximations, otherwise you may face all sorts of mysterious bugs in your code.
With a bit of ingenuity, however, we can investigate what really goes on behind the scene. By now we know that the number 0.1 is represented with some approximation.
System.out.println( 0.1d );
0.1
We know 0.1 is not 0.1, yet 0.1 is printed on the screen. Conclusion: Java is WYSINWYG!
For the sake of variety, let's pick another innocent looking number, say 2.3. Like 0.1, 2.3 is an approximated value. Unsurprisingly when printing the number Java hides the approximation.
System.out.println( 2.3d );
2.3
To investigate what the internal approximated value of 2.3 may be, we can compare the number to other numbers in a close range.
double d1 = 2.2999999999999996d;
double d2 = 2.2999999999999997d;
System.out.println( d1 + " " + (2.3d == d1) );
System.out.println( d2 + " " + (2.3d == d2) );
2.2999999999999994 false
2.3 true
So 2.2999999999999997 is just as much 2.3 as the value 2.3! Also notice that due to the approximation, the pivoting point is at ..99997 and not ..99995 where you ordinarily round round up in math. Another way to get to grips with the approximated value is to call upon the services of BigDecimal.
System.out.println( new BigDecimal(2.3d) );
2.29999999999999982236431605997495353221893310546875
Now, don't rest on your laurels thinking you can just jump ship and only use BigDecimal. BigDecimal has its own collection of traps documented here.
Nothing is easy, and rarely anything comes for free. And "naturally", floats and doubles yield different results when printed/written.
System.out.println( Float.toString(0.1f) );
System.out.println( Double.toString(0.1f) );
System.out.println( Double.toString(0.1d) );
0.1
0.10000000149011612
0.1
According to the slides from Joseph D. Darcy's blog a float approximation has 24 significant bits while a double approximation has 53 significant bits. The morale is that In order to preserve values, you must read and write decimal numbers in the same format.
Division by 0
Many developers know from experience that dividing a number by zero yields abrupt termination of their applications. A similar behaviour is found is Java when operating on int's, but quite surprisingly, not when operating on double's. Any number, with the exception of zero, divided by zero yields respectively ∞ or -∞. Dividing zero with zero results in the special NaN, the Not a Number value.
System.out.println(22.0 / 0.0);
System.out.println(-13.0 / 0.0);
System.out.println(0.0 / 0.0);
Infinity
-Infinity
NaN
Dividing a positive number with a negative number yields a negative result, while dividing a negative number with a negative number yields a positive result. Since division by zero is possible, you'll get different result depending on whether you divide a number with 0.0 or -0.0. Yes, it's true! Java has a negative zero! Don't be fooled though, the two zero values are equal as shown below.
System.out.println(22.0 / 0.0);
System.out.println(22.0 / -0.0);
System.out.println(0.0 == -0.0);
Infinity
-Infinity
true
Infinity is weird
In the world of mathematics, infinity was a concept I found hard to grasp. For example, I never acquired an intuition for when one infinity were infinitely larger than another. Surely Z > N, the set of all rational numbers is infinitely larger than the set of natural numbers, but that was about the limit of my intuition in this regard!
Fortunately, infinity in Java is about as unpredictable as infinity in the mathematical world. You can perform the usual suspects (+, -, *, / on an infinite value, but you cannot apply an infinity to an infinity.
double infinity = 1.0 / 0.0;
System.out.println(infinity + 1);
System.out.println(infinity / 1e300);
System.out.println(infinity / infinity);
System.out.println(infinity - infinity);
Infinity
Infinity
NaN
NaN
The main problem here is that the NaN value is returned without any warnings. Hence, should you foolishly investigate whether a particular double is even or odd, you can really get into a hairy situation. Maybe a run-time exception would have been more appropriate?
double d = 2.0, d2 = d - 2.0;
System.out.println("even: " + (d % 2 == 0) + " odd: " + (d % 2 == 1));
d = d / d2;
System.out.println("even: " + (d % 2 == 0) + " odd: " + (d % 2 == 1));
even: true odd: false
even: false odd: false
Suddenly, your variable is neither odd nor even!
NaN is even weirder than Infinity
An infinite value is different from the maximum value of a double and NaN is different again from the infinite value.
double nan = 0.0 / 0.0, infinity = 1.0 / 0.0;
System.out.println( Double.MAX_VALUE != infinity );
System.out.println( Double.MAX_VALUE != nan );
System.out.println( infinity != nan );
true
true
true
Generally, when a double have acquired the value NaN any operation on it results in a NaN.
System.out.println( nan + 1.0 );
NaN
Conclusions
Decimal numbers are approximations, not the value you assign. Any intuition gained in math-world no longer applies. Expect a+b = a and a != a/3 + a/3 + a/3
Avoid using the ==, compare against some tolerance or use the >= or <= operators
Java is WYSINWYG! Never believe the value you print/write is approximated value, hence always read/write decimal numbers in the same format.
Be careful not to overflow your double, not to get your double into a state of ±Infinity or NaN. In either case, your calculations may be not turn out as you'd expect. You may find it a good idea to always check against those values before returning a value in your methods.
It's called loss of precision and is very noticeable when working with either very big numbers or very small numbers. The binary representation of decimal numbers with a radix is in many cases an approximation and not an absolute value. To understand why you need to read up on floating number representation in binary. Here is a link: http://en.wikipedia.org/wiki/IEEE_754-2008. Here is a quick demonstration:
in bc (An arbitrary precision calculator language) with precision=10:
(1/3+1/12+1/8+1/15) = 0.6083333332
(1/3+1/12+1/8) = 0.541666666666666
(1/3+1/12) = 0.416666666666666
Java double:
0.6083333333333333
0.5416666666666666
0.41666666666666663
Java float:
0.60833335
0.5416667
0.4166667
If you are a bank and are responsible for thousands of transactions every day, even though they are not to and from one and same account (or maybe they are) you have to have reliable numbers. Binary floats are not reliable - not unless you understand how they work and their limitations.
While BigDecimal can store more precision than double, this is usually not required. The real reason it used because it makes it clear how rounding is performed, including a number of different rounding strategies. You can achieve the same results with double in most cases, but unless you know the techniques required, BigDecimal is the way to go in these case.
A common example, is money. Even though money is not going to be large enough to need the precision of BigDecimal in 99% of use cases, it is often considered best practice to use BigDecimal because the control of rounding is in the software which avoids the risk that the developer will make a mistake in handling rounding. Even if you are confident you can handle rounding with double I suggest you use helper methods to perform the rounding which you test thoroughly.
This is primarily done for reasons of precision. BigDecimal stores floating point numbers with unlimited precision. You can take a look at this page that explains it well. http://blogs.oracle.com/CoreJavaTechTips/entry/the_need_for_bigdecimal
When BigDecimal is used, it can store a lot more data then Double, which makes it more accurate, and just an all around better choice for the real world.
Although it is a lot slower and longer, it's worth it.
Bet you wouldn't want to give your boss inaccurate info, huh?
Another idea: keep track of the number of cents in a long. This is simpler, and avoids the cumbersome syntax and slow performance of BigDecimal.
Precision in financial calculations is extra important because people get very irate when their money disappears due to rounding errors, which is why double is a terrible choice for dealing with money.

Categories

Resources