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.