BigDecimal from Double incorrect value? - java

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"

Related

String to float/double Parsing

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.

Conversions and calcs of float-types in Java

I was trying to really learn more about floats, doubles and bigdecimals in Java. I wanted to know exactly how a floating point number gets represented in each type, for ex. floats use 2^, big decimals use 10^ plus scaled(32-bit) and unscaled values (arbitrary precision).
I put together simple calcs using all three types and did conversations for each, the result is rather confusing. I would appreciate some hints about why the only correct representation is for float, and why when converted into Double and BigDecimal there were trailing imprecisions. Is it to do with binary representation conversions? Anyhow here are the code and its output:
// Float - 32b
float a = 3.14f;
float b = 3.100004f;
float abAsAFloat = a + b;
double abAsADouble = a + b;
BigDecimal abAsABigDecimal = new BigDecimal(a + b);
System.out.println("a + b as a float: " + abAsAFloat);
System.out.println("a + b as a double: " + abAsADouble);
System.out.println("a + b as a BigDecimal: " + abAsABigDecimal);
// Double - 64b
double c = 3.14;
double d = 3.100004;
double cdAsADouble = c + d;
BigDecimal cdAsABigDecimal = new BigDecimal(c + d);
System.out.println("c + d as a double: " + cdAsADouble);
System.out.println("c + d as a BigDecimal: " + cdAsABigDecimal);
// BigDecimal, arbitrary-precision, signBit*unscaledValue × 10^-scale
BigDecimal e = new BigDecimal(3.14);
BigDecimal f = new BigDecimal(3.100004);
BigDecimal efAsABigDecimal = e.add(f);
System.out.println("e + f: " + efAsABigDecimal);
// Drawbacks. speed, memory, native value equality, no overloads for +/- et al
a + b as a float: 6.240004
a + b as a double: 6.240004062652588
a + b as a BigDecimal: 6.240004062652587890625
c + d as a double: 6.240004000000001
c + d as a BigDecimal:
6.2400040000000007722746886429376900196075439453125
e + f: 6.240004000000000328185478792875073850154876708984375
You're inadvertently mixing types. For example:
BigDecimal e = new BigDecimal(3.14);
BigDecimal f = new BigDecimal(3.100004);
In this case, you're providing doubles as inputs, so e and f will have double residues. Instead, use this:
BigDecimal e = new BigDecimal("3.14");
BigDecimal f = new BigDecimal("3.100004");
The float output is seemingly the most accurate because Java "knows" floats have a limited precision, so it won't print fifteen digits.
float may look correct for this particular case, but it will be just as wrong for other values. Note that when float and double are converted to strings, only as many digits are printed as are necessary to get the right value in that type; this means float may print "the correct answer" even when that representation conceals just as much rounding error as double.
The problem with BigDecimal is that you're not using it correctly: you should be writing new BigDecimal("3.14") instead of new BigDecimal(3.14), which allows double to "mess it up" before BigDecimal has the chance to "fix it."
For the details of representation, https://en.wikipedia.org/wiki/Double-precision_floating-point_format has a thorough explanation with useful diagrams, but the short explanation is that float and double represent numbers as +/- 1 * 1. * 2^, where float stores the mantissa with 22 bits and the exponent with 8 bits, and double uses 52 and 11 bits respectively.
When you convert to either double or BigDecimal it converts to the closest representable value. When you convert to BigDecimal you are actually converting to double first as there is no direct conversion from float.
Usually the you want to convert from double to BigDecimal using BigDecimal.valueOf(double) This method assumes a certain level of rounding to match what the double would look like if you printed it.
Read this: Java Language Specification. Chapter 5. Conversions and Promotions
Especially, 5.6. Numeric Promotions
i.e
float a = 3.14f;
float b = 3.100004f;
double abAsADouble = a + b;
in this case first a will be added to b, giving a float result, then float will be converted to double and assigned. So, it might have a loss of precision, when comparing to (double)a + b;
The same thing, when using sum result as parameter to the constructor
new BigDecimal(a + b)
first, float a added to float b, giving a float result, after that it is converted to double and then BigDecimal object begins being constructed.
Any numeric constants with decimal point, unless you specify f at the end, are considered to be a double, so, when passing constant to the constructor:
new BigDecimal(3.100004);
Number is stored as double and passed as double precision to the constructor. To achieve more precision, use String parameter constructor instead:
new BigDecimal("3.100004");

BigDecimal Multiplication

I try to multiply two BigDecimal Values with multiply methods as follows,
BigDecimal dur = BigDecimal.valueOf(60/1.1);
BigDecimal bal = BigDecimal.valueOf(1.1);
BigDecimal ans = dur.multiply(bal);
System.out.println("Ans:"+ans);
I am excepting ans as 60. But i got it as,
Ans:59.999999999999994
Why this comming and how can we resolve it.
The problem is you have a value which can't be represented in double but nor can it be represented in BigDecimal so you have to apply reasonable rounding to get the expected solution.
double d = 60 / 1.1 * 1.1;
System.out.println("double without rounding: " + d);
System.out.printf("double With rounding %.2f%n", d);
BigDecimal bd = BigDecimal.valueOf(60).divide(BigDecimal.valueOf(1.1), 9, BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(1.1));
System.out.println("BigDecimal without rounding: " + bd);
System.out.printf("BigDecimal with rounding %.2f%n", bd);
// or
bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("BigDecimal with rounding: " + bd);
prints
double without rounding: 60.0
double With rounding 60.00
BigDecimal without rounding: 59.9999999995
BigDecimal with rounding 60.00
BigDecimal with rounding: 60.00
Note: double happens to round correctly for these values and gives the right answer. However, pick a different combination and it will be incorrect. e.g. 54.545454545454545 * 1.1 => 60.00000000000001
You need to use String constructor instead of double one. 60/1.1 doesn't have String representation, as it is 54.(54).
BigDecimal does not support numbers that cannot be written as a fixed length decimals.
From BigDecimal Javadoc:
In the case of divide, the exact quotient could have an infinitely long decimal expansion; for example, 1 divided by 3. If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.

Extracting the integer and fractional part from Bigdecimal in Java

I want to extract the integer part and decimal part from the bigdecimal in java.
I am using the following code for that.
BigDecimal bd = BigDecimal.valueOf(-1.30)
String textBD = bd.toPlainString();
System.out.println("length = "+textBD.length());
int radixLoc = textBD.indexOf('.');
System.out.println("Fraction "+textBD.substring(0,radixLoc)+"Cents: " + textBD.substring(radixLoc + 1, textBD.length()));
I am getting the output as
-1 and 3
But I want the trailing zero also from -1.30
Output should be -1 and 30
If you like to not get involved with Strings (which I think it's not good practice - except the part of creating de BigDecimal) you could do it just with Math:
// [1] Creating and rounding (just like GriffeyDog suggested) so you can sure scale are 2
BigDecimal bd = new BigDecimal("-1.30").setScale(2, RoundingMode.HALF_UP);
// [2] Fraction part (0.30)
BigDecimal fraction = bd.remainder(BigDecimal.ONE);
// [3] Fraction as integer - move the decimal.
BigDecimal fraction2 = fraction.movePointRight(bd.scale());
// [4] And the Integer part can result of:
BigDecimal natural = bd.subtract(fraction);
// [5] Since the fraction part of 'natural' is just Zeros, you can setScale(0) without worry about rounding
natural = natural.setScale(0);
I know, my english is terrible. Feel free to correct if you could understand what I tried to say. Thanks.
The floating point representation of -1.30 is not exact. Here is a slight modification of your code:
BigDecimal bd = new BigDecimal("-1.30").setScale(2, RoundingMode.HALF_UP);
String textBD = bd.toPlainString();
System.out.println("text version, length = <" + textBD + ">, " + textBD.length());
int radixLoc = textBD.indexOf('.');
System.out.println("Fraction " + textBD.substring(0, radixLoc)
+ ". Cents: " + textBD.substring(radixLoc + 1, textBD.length()));
I have put a RoundingMode on the setScale to round fractional pennies like 1.295 "half up" to 1.30.
The results are:
text version, length = <-1.30>, 5
Fraction -1. Cents: 30
Initialize with a String to avoid problems with floating point accuracy. Then use setScale to set your desired number of decimal places:
BigDecimal bd = new BigDecimal("-1.30").setScale(2);
String textBD = bd.toPlainString();
System.out.println("length = "+textBD.length());
int radixLoc = textBD.indexOf('.');
System.out.println("Fraction "+textBD.substring(0,radixLoc)+"Cents: " + textBD.substring(radixLoc + 1, textBD.length()));

"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