I am making a Currency conversion app in Java. Some other awesome StackOverflowians gave me advice to read up on BigDecimal for the purpose of replacing double to fix any precision issues.
I have a two method system; where it converts from the starting currency into USD, then converts the USD value to the destination currency.
Note, my conversion rates are stored like this:
// Conversion Rates - START (as of October 30, 2018 @ 3:19 AM)
// Rates obtained from exchange-rates.org
//Convert to United States Dollar rates
private final BigDecimal CAD_TO_USD = new BigDecimal(0.76135);
private final BigDecimal EUR_TO_USD = new BigDecimal(1.1345);
private final BigDecimal YEN_TO_USD = new BigDecimal(0.008853);
// Conversion Rates - END
After I replaced my doubles with their respective BigDecimals - I decided to test it and see how it all works out.
My tester class runs the following method to start the conversion process.
public BigDecimal convert()
{
BigDecimal value;
value = convertToUSD(); //Converts the current currency into USD
value = convertFromUSD(value); //Converts the previous USD currency value into the destination currency
return value;
}
When I enter my sample variables (which are converting 2.78 YEN to Canadian Dollars), I stepped through the process and found that everything functions up until I return a value.
From the earlier mentioned method, convertToUSD()
is run and is coded as follows
private BigDecimal convertToUSD()
{
switch (fromCurrency)
{
case "USD":
return fromQuantity.multiply(new BigDecimal(1));
case "CAD":
return fromQuantity.multiply(CAD_TO_USD);
case "EUR":
return fromQuantity.multiply(EUR_TO_USD);
case "YEN":
return fromQuantity.multiply(YEN_TO_USD);
}
return new BigDecimal(0);
}
ALl the values are passed in correctly, it steps through down to the proper case ("YEN"), and the variable pane shows that the "fromQuantity" BigDecimal has a intCompact value of 278 (which makes sense to me)
As soon as the breakpoint returns back to the "convert" method, it gets all messed up. Instead of returning the 2.78 * 0.008853 = 0.0246
, it returns -9223372036854775808
.
This causes all other calculations to produce and error.
I am new to using BigDecimal, so I may be making a complete obvious mistake; but I am happy to learn, so I sought advice from you guys :)
Any assistance is appreciated.
Use String
, not double
literals.
new BigDecimal( "2.78" ) // Pass "2.78" not 2.78
.multiply(
new BigDecimal( "0.008853" ) // Pass "0.008853" not 0.008853
)
.toString()
0.02461134
The point of the BigDecimal
class is to avoid the inherent inaccuracies found in floating-point technology. Floating-point types such as float
/Float
and double
/Double
trade away accuracy for speed of execution. In contrast, BigDecimal
is slow but accurate.
Your code:
new BigDecimal( 0.76135 )
new BigDecimal( 1.1345 )
new BigDecimal( 0.008853 )
…is passing a double
primitive literal. During compilation, the text you typed 0.76135
is parsed as a number, specifically as a double
(a 64-bit floating-point value). At that point you have introduced inaccuracies inherent with this type. In other words, the double
produced from 0.76135
may no longer be exactly 0.76135
.
Let’s dump your BigDecimal
instances immediately after instantiating.
System.out.println( new BigDecimal( 0.76135 ) ); // Passing a `double` primitive.
System.out.println( new BigDecimal( 1.1345 ) );
System.out.println( new BigDecimal( 0.008853 ) );
0.7613499999999999712230192017159424722194671630859375
1.13450000000000006394884621840901672840118408203125
0.0088529999999999997584154698415659368038177490234375
So, by creating double
number values, you invoked floating-point technology, and introduced inaccuracies.
The solution? Work with strings, avoiding the double
type entirely.
Put some double-quote marks around those inputs, and voilà.
System.out.println( new BigDecimal( "0.76135" ) ); // Passing a `String` object.
System.out.println( new BigDecimal( "1.1345" ) );
System.out.println( new BigDecimal( "0.008853" ) );
0.76135
1.1345
0.008853
You expected 2.78 * 0.008853 = 0.0246
. Let’s try it.
BigDecimal x = new BigDecimal( "2.78" );
BigDecimal y = new BigDecimal( "0.008853" );
BigDecimal z = x.multiply( y );
System.out.println( x + " * " + y + " = " + z );
2.78 * 0.008853 = 0.02461134
Next you should study up on rounding & truncating with BigDecimal
. Already covered many times on Stack Overflow.
The problem is that you are assuming intCompact
represents a non-floating point version of your float. In some cases, it does, but in most cases it will not.
For example I tried to reduce your problem to the bare minimum.
import java.math.BigDecimal;
class Main {
public static void main(String[] args) {
final BigDecimal YEN_TO_USD = new BigDecimal(0.008853);
BigDecimal value = new BigDecimal(2.78);
value = value.multiply(YEN_TO_USD);
System.out.println(value);
}
}
Placing a break on the line with the println
I get the following:
You see the intCompact
is the same as yours (-9223372036854775808
). But the stringCache
in this case is the expected value.
Refer to the answer by Basil as to why you should construct BigDecimal using strings and not doubles.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With