The following code has a precision error but I'm at a loss as to how to resolve it. Given an initial value set in dollar/gal of $3.95, when change the ChoiceBox to "dollars/liter" I get $1.04, but when I change it back to dollar/gal I'm getting $3.94 instead of $3.95
if ("dollar/gal".equals(newValue)) {
double pricePerLiter = Double.parseDouble(txtFields.get(1).getText());
BigDecimal pricePerGallon = BigDecimal.valueOf(pricePerLiter * 3.785);
pricePerGallon = pricePerGallon.setScale(2, RoundingMode.HALF_UP);
txtFields.get(1).setText(pricePerGallon.toString());
} else if ("dollars/liter".equals(newValue)) {
double pricePerGallon = Double.parseDouble(txtFields.get(1).getText());
BigDecimal pricePerLiter = BigDecimal.valueOf(pricePerGallon * 0.264);
pricePerLiter = pricePerLiter.setScale(2, RoundingMode.HALF_UP);
txtFields.get(1).setText(pricePerLiter.toString());
}
I've attempted this with both Math.Round() and BigDecimal and RoundingMode (which is the version above) but cannot resolve this issue.
This is not a floating-point representation issue; it's a rounding issue. When you round, your value is no longer accurate. In addition, the two values you multiply with don't themselves multiply to be 1; they're not quite inverses.
When you take
3.95
and multiply it by0.264
, the value is1.0428
, which you round down to1.04
. When you multiply1.04
by3.785
, you get3.9364
, which is rounded up to3.94
.But you represent the inverse of
3.785
as0.264
, which is close, but not quite equal to the actual inverse of about0.2642008
. But even multiplying the true inverse value by3.95
you get1.04359316
, which you would still round down to1.04
.If you must convert back to the same value, then store the un-rounded value for multiplying later, even if you display the rounded value. Also, instead of multiplying by
0.264
, just divide by the same number that you multiplied by in the other case,3.785
.