October 7, 2008

Reliable monetary calculations

As you all probably know, reliable and correct monetary and scientific calculations in Java aren't as easy as it may seem. Many Java newbies and in some cases even intermediate Java developers, frequently use the data type double for monetary calculations. What's worse, for scientific applications there is no support worth mentoining, at all.

Now, if you build a small scale web page, you may never encounter rounding errors when dealing with this data type. However, if you are in the business of handling big amounts of cash, where fractions of a cent spread over millions of transactions could render losses in the tens or hundreds of thousands of dollars, you definitely should rethink your strategy.

Right now, what most people do, is using either BigDecimal or even calculating everything with 64 bit integers to avoid dealing with floating point arithmetic inaccuracies found in the primitive double data type.

There is nothing wrong in doing this, however, what about actually treating those essentially ambiguous floating point numbers as amounts of money. What about a data type called Money so you give those numbers a meaning. What if there was built in support in Java that treats money as a unit of measurement so you give those numbers a clear meaning?

A framework for this will likely find it's way into Java 7 and for those of you wanting to get a head-start is to download a fully functioning and mature reference implementation called JScience. I used it in one of my recent projects and I would like to share some very simple examples.

Converting from Euros to Swedish Crownes

import org.jscience.economics.money.Currency;
import org.jscience.physics.amount.Money;
import javax.measure.unit.Unit

public class ConvertCurrency {
 public static void convertSekToEur(double value) {
  Currency sek = new Currency("SEK");
  // Exchange rate SEK to SEK is 1.00
  sek.setExchangeRate(1.00);

  Currency eur = new Currency("EUR");
  // Exchange rate for EUR to SEK is 9.72
  eur.setExchangeRate(9.72);

  // Amount of money in SEK received
  Amount<Money> received = Amount.valueOf(value, sek);

  // Units should be printed with the Euro symbol
  UnitFormat.getInstance().label(Currency.EUR, "€");

  // Convert to SEK to EUR and get a localized text string
  String res = received.to(eur).toText();

  // Should print "value €", depending on Locale
  System.out.println("Teller returned: " + res);
 }
}


I know, this isn't all that impressive, but if you investigate the API further, you will notice, that very powerful things can be done with JScience.

Also, be aware that in the above example, I didn't set a reference currency. This means, that I had to set the exchange rate for both currencies. If I'd set SEK as the reference currency by using the Currency.setReferenceCurrency-method, the exchange rate for SEK would have been obsolete. Naturally, most apps will have a reference currency.

The distance in money

Imagine you were to put 50 Euro bank notes on the street all the way from Stockholm to Malmö. That's a distance of about 700 kilometers. How much money would you actually need?

import org.jscience.economics.money.Currency;
import org.jscience.economics.money.Money;
import org.jscience.physics.amount.Amount;

import javax.measure.quantity.Length;
import javax.measure.unit.SI;

public class DistanceInMoney {
 public static void main(String[] args) {
  // Distance between Stockholm and Malmö
  Amount<Length> distanceSthlmMmo = Amount.valueOf(700, SI.KILOMETER);

  // Length of a banknote
  Amount<Length> lengthBankNote = Amount.valueOf(15, SI.CENTIMETER);

  // Value of that banknote
  Amount<Money> bankNoteValue = Amount.valueOf(50, Currency.EUR);

  // Calculate how many banknotes are needed
  Amount<?> bankNotesNeeded = distanceSthlmMmo.to(SI.CENTIMETER).divide(lengthBankNote);

  // Show how much money you will need in total
  System.out.println("Money needed: " + bankNoteValue.times(bankNotesNeeded));
 }
}


Of course, this can also be done manually, but it's just to show that you give a meaning to numbers by using Amount<Length> or Amount<Money>. As in this number is an amount of money.

Calculating the cost for a trip

The last example is quite neat also. I admit, it's a simplified version of an example provided by the makers of JScience, however, I am not ashamed. I like it and that's why I post it :-)

So, how much does it cost to drive from Stockholm to Uppsala with my car...

import org.jscience.economics.money.Currency;
import org.jscience.economics.money.Money;
import org.jscience.physics.amount.Amount;

import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;

public class TripCost {
 public static void main(String[] args) {
  // Our reference currency is SEK
  Currency.setReferenceCurrency(new Currency("SEK"));

  // My car drives 12,5km with one liter of petrol
  Amount<?> enginesThirst = Amount.valueOf(12.5,   SI.KILOMETER.divide(NonSI.LITER));

  // Fuel costs 12 SEK per liter
  Amount<?> fuelPrice = Amount.valueOf(12, Currency.getReferenceCurrency().divide(NonSI.LITER));

  // The distance to Uppsala is 50km
  Amount<Length> distanceToUppsala = Amount.valueOf(50, SI.KILOMETER);

  // Calculate the cost
  Amount<Money> cost = distanceToUppsala.divide(enginesThirst).times(fuelPrice).to(Currency.getReferenceCurrency());
  System.out.println("Trip costs: " + cost.toString());
 }
}


Again, this is no magic. You can do all this manually, however, isn't it quite helpful to actually treat units and amounts specifically as units, amounts and quantities?

Conclusion

So, what are the benefits besides the obvious? Well, JScience has a wealth of SI and NonSI units to do calculations, it has special numerical datatypes which guarantee IEEE754 accuracy and especially for scientific applications, this is a great treat to us all.

You probably remember the disaster with a NASA space probe a couple of years ago. Due to some mix-ups with units of measurement, millions went up in smoke. This sort of ambiguity should be a thing of the past with JScience (JSR-275).

0 comments: