java BigDecimal subraction failing - java

I tried the following code. but getting different result when subtracting using BigDecimal.
double d1 = 0.1;
double d2 = 0.1;
System.out.println("double result: "+ (d2-d1));
float f1 = 0.1F;
float f2 = 0.1F;
System.out.println("float result: "+ (f2-f1));
BigDecimal b1 = new BigDecimal(0.01);
BigDecimal b2 = new BigDecimal(0.01);
b1 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b1);
Result:
double result: 0.0
float result: 0.0
BigDecimal result: 0E-59
I am still working on this. can anyone please clarify.

[There are a lot of answers here telling you that binary floating-point can't exactly represent 0.01, and implying that the result you're seeing is somehow inexact. Whilst the first part of that is true, it's not really the core issue here.]
The answer is that "0E-59" is equal to 0. Recall that a BigDecimal is the combination of an unscaled value and a decimal scale factor:
System.out.println(b1.unscaledValue());
System.out.println(b1.scale());
displays:
0
59
The unscaled value is 0, as expected. The "strange" scale value is simply an artifact of the decimal expansion of the non-exact floating-point representation of 0.01:
System.out.println(b2.unscaledValue());
System.out.println(b2.scale());
displays:
1000000000000000020816681711721685132943093776702880859375
59
The next obvious question is, why doesn't BigDecimal.toString just display b1 as "0", for convenience? The answer is that the string representation needs to be unambiguous. From the Javadoc for toString:
There is a one-to-one mapping between the distinguishable BigDecimal values and the result of this conversion. That is, every distinguishable BigDecimal value (unscaled value and scale) has a unique string representation as a result of using toString. If that string representation is converted back to a BigDecimal using the BigDecimal(String) constructor, then the original value will be recovered.
If it just displayed "0", then you wouldn't be able to get back to this exact BigDecimal object.

Use constructor from String: b1 = new BigDecimal("0.01");
Java loss of precision
(slide 23)
http://strangeloop2010.com/system/talks/presentations/000/014/450/BlochLee-JavaPuzzlers.pdf

Interesting, the values appear to be equal and subtraction does give you zero, it appears to just be an issue with the printing code. The following code:
import java.math.BigDecimal;
public class Test {
public static void main(String args[]) {
BigDecimal b1 = new BigDecimal(0.01);
BigDecimal b2 = new BigDecimal(0.01);
BigDecimal b3 = new BigDecimal(0);
if (b1.compareTo(b2) == 0) System.out.println("equal 1");
b1 = b1.subtract(b2);
if (b1.compareTo(b3) == 0) System.out.println("equal 2");
System.out.println("BigDecimal result: "+ b1);
}
}
outputs both equal messages, indicating that the values are the same and that you get zero when you subtract.
You could try to raise this as a bug and see what Oracle comes back with. It's likely they'll just state that 0e-59 is still zero, so not a bug, or that the rather complex behaviour being described on the BigDecimal documentation page is working as intended. Specifically, the point that states:
There is a one-to-one mapping between the distinguishable BigDecimal values and the result of this conversion. That is, every distinguishable BigDecimal value (unscaled value and scale) has a unique string representation as a result of using toString. If that string representation is converted back to a BigDecimal using the BigDecimal(String) constructor, then the original value will be recovered.
That fact that the original value needs to be recoverable means that toString() needs to generate a unique string for each scale, which is why you're getting 0e-59. Otherwise, converting the string back to a BigDecimal may give you a different value (unscaled-value/scale tuple).
If you really want zero to show up as "0" regardless of the scale, you can use something like:
if (b1.compareTo(BigDecimal.ZERO) == 0) b1 = new BigDecimal(0);

You have to get the return value:
BigDecimal b3 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b3);

BigDecimal(double val)
1.The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a
BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with
a scale of 1), but it is actually equal to
0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that
matter, as a binary fraction of any finite length). Thus, the value
that is being passed in to the constructor is not exactly equal to
0.1, appearances notwithstanding.
2.The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly
equal to 0.1, as one would expect. Therefore, it is generally
recommended that the String constructor be used in preference to this
one.
3.When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the
same result as converting the double to a String using the
Double.toString(double) method and then using the BigDecimal(String)
constructor. To get that result, use the static valueOf(double)
method.

So the real question is: with the following code,
BigDecimal b1 = new BigDecimal(0.01);
BigDecimal b2 = new BigDecimal(0.01);
b1 = b1.subtract(b2);
why does b1.toString() evaluate to "0E-59" and not to something like "0.0", "0E0" or just "0"?
The reason is that toString() prints the canonical format of the BigDecimal. See BigDecimal.toString() for more information.
At the end, 0E-59 is 0.0 - it is 0*10^59 which mathematically evaluates to 0. So, the unexpected result is a matter of the internal representation of the BigDecimal.
To get the float or double values, use
b1.floatValue());
or
b1.doubleValue());
Both evaluate to 0.0.

It's a known issue, BigDecimal(double val) API The results of this constructor can be somewhat unpredictable. Though it looks really wierd in this interpertation. Actual reason is that new BigDecimal(0.01) produces a BigDecimal with approx values
0.01000000000000000020816681711721685132943093776702880859375
which has a long precision, and so the result of subtract has a long precision too.
Anyway, we can solves the "problem" this way
BigDecimal b1 = new BigDecimal("0.01");
BigDecimal b2 = new BigDecimal("0.01");
or we can use a constructor with setting a precision
BigDecimal b1 = new BigDecimal(0.01, new MathContext(1));
BigDecimal b2 = new BigDecimal(0.01, new MathContext(1));

Use like this:
BigDecimal b1 = BigDecimal.valueOf(0.01);
BigDecimal b2 = BigDecimal.valueOf(0.01);
b1 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b1);

Related

Check Multiple and module of two BigDecimal in java

BigDecimal x = new BigDecimal(1.95);
BigDecimal y = new BigDecimal(0.65);
BigDecimal rem = x.remainder(y);
if (rem.compareTo(BigDecimal.ZERO) != 0.) {
System.out.println("Not In mutilple of");
} else {
System.out.println("In mutilple of");
}
System.out.println(rem);
Not giving correct result for the above scenario.
given the input condition but giving incorrect result if it is in multiple of the second value
Explanation
Useing new BigDecimal(double) might lose precision, see the doc:
The results of this constructor can be somewhat unpredictable. One
might assume that writing new BigDecimal(0.1) in Java creates a
BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with
a scale of 1), but it is actually equal to
0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that
matter, as a binary fraction of any finite length). Thus, the value
that is being passed in to the constructor is not exactly equal to
0.1, appearances notwithstanding.
and:
BigDecimal x= new BigDecimal(1.95);
BigDecimal y = new BigDecimal(0.65);
System.out.println(x); // 1.9499999999999999555910790149937383830547332763671875
System.out.println(y); // 0.65000000000000002220446049250313080847263336181640625
Solution
Use BigDecimal(String val):
The String constructor, on the other hand, is perfectly predictable:
writing new BigDecimal("0.1") creates a BigDecimal which is exactly
equal to 0.1, as one would expect. Therefore, it is generally
recommended that the String constructor be used in preference to this
one.
and
BigDecimal x = new BigDecimal("1.95");
BigDecimal y = new BigDecimal("0.65");
System.out.println(x); // 1.95
System.out.println(y); // 0.65
System.out.println(x.remainder(y)); // 0.00
Instantiate like
BigDecimal x= new BigDecimal("1.95");
BigDecimal y= new BigDecimal("0.65");
as not all float point number can be represented exactly as doubles.
The output in your code shows
0.65 ss 0.64999999999999995559107901499373838305473327636718750
see https://study.com/academy/lesson/java-floating-point-numbers.html

How to compare two big decimal

I am having following code, however I am not able to understand why the two bigdecimal are not considered as equal
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
BigDecimal b = new BigDecimal(13.90);
BigDecimal b2 = new BigDecimal("13.9");
System.out.println(b.compareTo(b2));
}
}
This code outputs 1 as output. Why would that be the case? Shouldn't it be 0?
Also if I write 13.9 instead of "13.9" it gives 0 as output
Because you are assuming that if you use 13.9 it will exactly be 13.9. Try printing the values of b and b2 and you'll see that the 13.9 is actually 13.9000000000000003552713678800500929355621337890625, and the parsed string value is 13.9. So b is (slightly) higher than b2.
public static void main(String...strings) {
BigDecimal b = new BigDecimal(13.9);
BigDecimal b2 = new BigDecimal("13.9");
System.out.printf("%s %s %d%n", b, b2, b.compareTo(b2));
}
Gives as output:
13.9000000000000003552713678800500929355621337890625 13.9 1
On the topic of floating point mathemetics you might want to read Why Are Floating Point Numbers Inaccurate? and other links available on stackoverflow.
Floating-point numeric values are not exact, they're approximate. So when you type the literal value 13.90, the actual value retained is something not exactly 13.90, but as close to it as possible.
BigDecimal values are exact, and Strings are just Strings.
So when you use new BigDecimal(13.90) and new BigDecimal("13.9"), the first one holds a value that's exactly the floating-point value passed to it, which is very close but different to 13.9. And the second one contains exactly 13.9.
One of them is 13.9 and the other is close, but not equals, to 13.9. So the two objects aren't equals.
The javadoc for the BigDecimal constructor of double notes that the result "can be somewhat unpredictable". That's because the input decimal value is actually a floating point approximation. Generally, it is a good practice to construct decimal values using the BigDecimal String constructor. However, if you don't or are unable to do that, you can always set the scale and round. The code below would give you the results you seek.
BigDecimal b = new BigDecimal(13.90).setScale(2,BigDecimal.ROUND_HALF_UP);
BigDecimal b2 = new BigDecimal("13.9");
System.out.println(b.compareTo(b2));
Note: The rounding occurs at the end of the scale. So with a set of 2, the value 13.900004 would round to 13.90.

BigDecimal Subtraction

I want to substract 2 double values, and I have tried the following code.
double val1 = 2.0;
double val2 = 1.10;
System.out.println(val1 - val2);
and I got the output as,
0.8999999999999999
For getting output as 0.9 I tried with BigDecimal as follows,
BigDecimal val1BD = new BigDecimal(val1);
BigDecimal val2BD = new BigDecimal(val2);
System.out.println(val1BD.subtract(val2BD));
And I got the output as,
0.899999999999999911182158029987476766109466552734375
Then I tried with BigDecimal.valueOf()
val1BD = BigDecimal.valueOf(val1);
val2BD = BigDecimal.valueOf(val2);
System.out.println(val1BD.subtract(val2BD));
And finally I got the output as 0.9.
My question is what is the difference between case 2 & case 3?
In case 2 why I got the output like that?
BigDecimal.valueOf(double d) uses canonical String representation of double value, internally Double.toString(double) is used, that's why you are getting 0.9 in second case.
Note: This is generally the preferred way to convert a double (or
float) into a BigDecimal, as the value returned is equal to that
resulting from constructing a BigDecimal from the result of using
Double.toString(double).
While with new BigDecimal(0.9) it converts value to exact floating point representation of double value without using String representation,
Translates a double into a BigDecimal which is the exact decimal
representation of the double's binary floating-point value.
...
NOTES :
The results of this constructor can be somewhat unpredictable.
...
FOR EXAMPLE :
BigDecimal bd1 = new BigDecimal(Double.toString(0.9));
BigDecimal bd2 = new BigDecimal(0.9);
System.out.println(bd1);
System.out.println(bd2);
OUTPUT :
0.9
0.90000000000000002220446049250313080847263336181640625
Just for those others that got here looking for some other issue with BigDecimal(not related to the question above)...
remember to give a mathContext to the methods to avoid certain problems e.g.
MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
BigDecimal hitRate = new BigDecimal(totalGetValuesHitted).divide(new BigDecimal(totalGetValuesRequested), mc);
BigDecimal missRate = new BigDecimal(1.0, mc).subtract(hitRate, mc);

BigDecimal operations precision overflow

import java.math.BigDecimal;
public class TestNumber {
public static void main(String[] args) {
BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(19.89));
System.out.println(bd.doubleValue() + " - \t " + bd);
}
}
I have multiple BidDecimals fields and arithmetic operations/comparations, the problem is with arithmetic results and decimals values.
For the above example the output is as follows:
19.89 - 19.8900000000000005684341886080801486968994140625
I expects:
19.89
The unexpected result creates other undesirable outputs to perform operations on the field type BigDecimal
The precision is already lost once you use the BigDecimal constructor that accepts double values. The value youre seeing is the true IEEE 754 representation of the number. You can use
bd = bd.add(new BigDecimal("19.89"));
The double value displayed by println is not the same as the actual value stored in that double variable.
In any range there are an infinite number of real numbers but only a finite number of representable floating point values. When you define a floating point value, that value may not map to a representable floating point value, in which case you get the representable value that is closest to what you want. (Also keep in mind the representation is in binary, and a lot of numbers that are familiar to us become repeating decimals in binary that have to get truncated.) Here of course it's off by only 0.0000000000000005684341886080801486968994140625.
The lines
double d = 19.89d;
System.out.println(d);
will show you a cleaned-up approximation of what's in d. Java is hiding the messy trailing decimals from you.
On the other hand, these lines
double d = 19.89d
BigDecimal b = new BigDecimal(d);
System.out.println(b);
result in the BigDecimal getting initialized with the entire contents of d, which the BigDecimal reproduces faithfully out to the last trailing digit.
When println is passed the BigDecimal, the BigDecimal's toString method returns a string showing the digits it stored, and println writes that string to the console.
Using
BigDecimal b = new BigDecimal("19.89");
will result in the actual decimal value 19.89 getting stored in the BigDecimal, because no floating point evaluation is involved.
If you have a double and you need to make a BigDecimal out of it, without adding all the extra precision, try something like
double d = 19.89; // or something else
bd = new BigDecimal(d, new MathContext(15));
This tells it to keep only 15 digits of precision (which is about how many digits of precision a double supports). This creates a BigDecimal whose toString() returns
"19.8900000000000"
which isn't quite perfect, since all the trailing zeroes will show up, but it doesn't give you the extra non-zero digits you're getting.

"new BigDecimal(13.3D)" results in imprecise "13.3000000000000007105.."?

How is it that Java's BigDecimal can be this painful?
Double d = 13.3D;
BigDecimal bd1 = new BigDecimal(d);
BigDecimal bd2 = new BigDecimal(String.valueOf(d));
System.out.println("RESULT 1: "+bd1.toString());
System.out.println("RESULT 2: "+bd2.toString());
RESULT 1: 13.300000000000000710542735760100185871124267578125
RESULT 2: 13.3
Is there any situation where Result 1 would be desired? I know that Java 1.5 changed the toString() method but was this the intended consequence?
Also I realise that BigDecimal has doubleValue() etc, but the library that I am working with helpfully uses a toString() and I can't change that :-(
Cheers.
Well, the API does address this apparent inconsistency in the constructor BigDecimal(double val):
The results of this constructor can be somewhat unpredictable. One might
assume that writing new
BigDecimal(0.1) in Java creates a
BigDecimal which is exactly equal to
0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal
to
0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be
represented exactly as a double (or,
for that matter, as a binary fraction
of any finite length). Thus, the value
that is being passed in to the
constructor is not exactly equal to
0.1, appearances notwithstanding.
The String constructor, on the other hand, is perfectly predictable:
writing new BigDecimal("0.1") creates
a BigDecimal which is exactly equal to
0.1, as one would expect. Therefore, it is generally recommended that the
String constructor be used in
preference to this one.
When a double must be used as a source for a BigDecimal, note that
this constructor provides an exact
conversion; it does not give the same
result as converting the double to a
String using the
Double.toString(double) method and
then using the BigDecimal(String)
constructor. To get that result, use
the static valueOf(double) method.
Moral of the story: The pain seems self-inflicted, just use new BigDecimal(String val) or BigDecimal.valueOf(double val) instead =)
Your problem has nothing to do with BigDecimal, and everything with Double, which cannot represent 13.3 accurately, since it uses binary fractions internally.
So your error is introduced in the very first line. The first BigDecimal simply preserves it, while String.valueOf() does some fishy rounding that causes the second one to have the desired content, pretty much through luck.
You might want to inform yourself about how floating-point values are implemented (IEEE 754-1985). And suddenly, everything will become crystal-clear.
This isn't the fault of BigDecimal - it's the fault of double. BigDecimal is accurately representing the exact value of d. String.valueOf is only showing the result to a few decimal places.
Fractions represented with binary number types(i.e. double, float) cannot be accurately stored in those types.
Double d = 13.3;
BigDecimal bdNotOk = new BigDecimal(d);
System.out.println("not ok: " + bdNotOk.toString());
BigDecimal bdNotOk2 = new BigDecimal(13.3);
System.out.println("not ok2: " + bdNotOk2.toString());
double x = 13.3;
BigDecimal ok = BigDecimal.valueOf(x);
System.out.println("ok: " + ok.toString());
double y = 13.3;
// pretty lame, constructor's behavior is different from valueOf static method
BigDecimal bdNotOk3 = new BigDecimal(y);
System.out.println("not ok3: " + bdNotOk3.toString());
BigDecimal ok2 = new BigDecimal("13.3");
System.out.println("ok2: " + ok2.toString());
Double e = 0.0;
for(int i = 0; i < 10; ++i) e = e + 0.1; // some fractions cannot be accurately represented with binary
System.out.println("not ok4: " + e.toString()); // should be 1
BigDecimal notOk5 = BigDecimal.valueOf(e);
System.out.println("not ok5: " + notOk5.toString()); // should be 1
/*
* here are some fractions that can be represented exactly in binary:
* 0.5 = 0.1 = 1 / 2
* 0.25 = 0.01 = 1 / 4
* 0.75 = 0.11 = 3 / 4
* 0.125 = 0.001 = 1 / 8
*/
output:
not ok: 13.300000000000000710542735760100185871124267578125
not ok2: 13.300000000000000710542735760100185871124267578125
ok: 13.3
not ok3: 13.300000000000000710542735760100185871124267578125
ok2: 13.3
not ok4: 0.9999999999999999
not ok5: 0.9999999999999999
Just use BigDecimal.valueOf(d) or new BigDecimal(s).

Categories