May 11

Most of you probably ran into the well-known precision problems with floating point numbers at one time or another. If you haven’t, consider this example:

1
2
scala> 1.2 - 1.0
res1: Double = 0.19999999999999996

Oops. Shouldn’t that be 0.2? Yes it should, but since the number 0.2 cannot be represented exactly in binary form, the result is a little bit less. This problem becomes annoying when Floats or Double values are used to represent money. It gets worse when multiplication or division operations magnify the error, as for example in the case of interest calculations. Simply put, you can’t guarantee the exactness of calculations down to the cent when you represent monetary amounts as floating point values. The Java language has a type called BigDecimal which solves this problem. It offers arbitrary scale fixed point arithmetics that allows precise financial calculations. Unfortunately, Java’s BigDecimal class has a rather unwieldy API which is a pain to use. No problem, you may think, because Scala offers a wrapper for BigDecimal that lets you use it like a normal number. That is true, but try this out:

1
2
3
scala> val b: BigDecimal = 0.1
b: BigDecimal =
  0.1000000000000000055511151231257827021181583404541015625

Oops again. This doesn’t look much better. What is more, the BigDecimal wrapper class in Scala has abstracted away the control over rounding behaviour and precision offered by the Java API. Of course, you could follow another approach and use Long values to represent money and scale them to the required precision, say one ten thousands of a Dollar. However, there are a number of problems with this approach. First, the value range is limited by MAX_LONG/scale. Second, you have to do complex formatting every time you print the values. Third, you have to code rounding manually because remainders are discarded in integer arithmetics:

1
2
scala> 59L / 10L
res2: Long = 5

With this in mind, I have created a Scala currency type that offers arbitrary precision fixed point arithmetics with an easy-to-use API. It is based on the Java’s BigDecimal type - why reinvent the wheel? Let’s repeat the first arithmetic operation using the currency type:

1
2
scala> Currency(1.2) - Currency(1.0)
res3: Currency = 0.20

This time the result is correct. It is correct, because the currency type automatically takes care of rounding. By default, the result is rounded to the second decimal place using the ROUND_HALF_UP rounding mode, which means that 1.555 is rounded to 1.56 and -1.555 is rounded to -1.56. Unlike the rounding for Float and Double values, the rounding is symmetric, which is standard in most financial calculations. If you need a different rounding behaviour – no problem. With the currency type, you have complete control over precision and rounding behaviour.

1
2
3
4
5
6
7
8
9
scala> val c = Currency("1234.56789", 4)
c: Currency = 1234.5679
 
scala> val d = Currency(0.1, 20)
d: Currency = 0.10000000000000000000
 
scala> import Currency.RoundingMode._
scala> val e = Currency(22.78, 2, ROUND_DOWN)
e: Currency = 22.78

The first expression creates a currency value with a precision of 1/10000 (= four places right of the decimal point) from a string argument where the fifth decimal place is rounded up in construction. The second expression creates a value of 0.1 (10 cents) with a precision of 10-20 or 20 places after the decimal point. The third expression constructs a value of 22.78 with a special rounding mode that stipulates that values should always be rounded down. The effect can be seen when performing an arithmetic operation:

1
2
3
4
5
6
7
8
9
10
11
scala> e * 1.1
res4: Currency = 25.05
 
scala> val f = Currency(22.78, 2, ROUND_UP)
f: Currency = 22.78
 
scala> e == f
res5: Boolean = true
 
scala> f * 1.1
res6: Currency = 25.06

In this example, the first operation e * 1.1 yields 25.05. We create another currency object with the same value, however with a different rounding mode. When performing the same operation on the second value f, the result differs by one cent from the first because it was rounded differently. This means that arithmetic operations with currency values that have different precision and/or rounding modes are not transitive. This is of course intended behaviour.

The above examples show simple calculations with non-specific currency values. In addition to the numeric amount, the currency type optionally takes a currency designation in the form of an ISO 4217 three-letter code. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> val salary = Currency(4789.90, "USD")
salary: Currency = 4789.90 USD
 
scala> val bonus = Currency(500, "USD")
bonus: Currency = 500.00 USD
 
scala> val holidayInEurope = Currency(2400, "EUR")
holidayInEurope: Currency = 2400.00 EUR
 
scala> salary + bonus
res7: Currency = 5289.90 USD
 
scala> salary + holidayInEurope
MismatchedCurrencyException

You can add, subtract and compare amounts in the same currency (or of a non-specific currency value), but when you try to add two different currency values, you get an exception. Alternatively, different currencies could be implemented as different subtypes, as suggested in Ch.20, Programming in Scala (Odersky, Spoon, Venners). This has the advantage that currency mismatches can be detected at compile time. However, it complicates the design and makes mapping the currency type to a database rather awkward. I think that the disadvantages outweigh the benefits, so I implemented the currency type as a “monolithic” class that knows about all currencies. With this design, it becomes easy to format currency values for output.

1
2
3
4
5
6
7
8
9
10
11
12
scala> salary.format
res8: String = $4,789.90
 
scala> import java.util.Locale
scala> holidayInEurope.format(new Locale("de", "DE"))
res9: String = 2.400,00 €
 
scala> holidayInEurope.format(new Locale("fr", "CH"))
res10: String =2'400.00
 
scala> salary.format("\u00A4 #,##0.0000")
res11: String = $ 4,789.9000

The currency class offers a severalfold overloaded format method that formats currency amounts according to either the default locale or a chosen locale. Alternatively, you can specify a formatting pattern and/or symbols to use, or you can use Java’s DecimalFormat class for formatting. Thus you have total control over the formatted output. The currency class can also verbalise amounts in four languages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scala> salary.say
res12: String =
  four thousand seven hundred eighty nine Dollars ninety Cents
 
scala> salary.sayNumber
res13: String =
  four thousand seven hundred eighty nine 90/100
 
scala> val pesos = Currency("1538.20", "MXN")
pesos: Currency = 1538.20 MXN
 
scala> pesos.say(new Locale("es"))
res14: String =
  mil cincocientos treinta y ocho Mexican Peso veinte Centavo
 
scala> pesos.sayNumber(new Locale("es"))
res15: String = mil cincocientos treinta y ocho 20/100

Finally, the currency class provides a number of convenience methods, such as percentage(), setDecimals(), abs(), fraction(), integral(), pow(), symbol(), getAllCurrencies(), etc., and a number of factory methods and conversion methods to simplify programming with currency values.

View Source Code.
Download source code and API documentation.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Technorati
  • Facebook
  • Mixx
  • Google
  • YahooMyWeb
  • Slashdot
  • LinkedIn
  • blogmarks
  • Live
  • description
  • StumbleUpon
  • Ma.gnolia
  • MisterWong
  • NewsVine
  • Reddit
  • Spurl
  • Yigg
  • E-mail this story to a friend!
May 1

I have been a Linux fan for more than a decade. I used Linux in my own company and projects since 1996  and I was also one of the founding members of the Bangkok Linux User Group. Oddly however, the computer on my desktop still runs on Windows. It’s a glaring contradiction. I’ve wanted to replace Windows for years. There’s always been a reason not to, mainly because I need to test software under Windows for my customers. Last weekend, the XP installation on my laptop “forgot” my user account and with it all account data. Simultaneously, the file system started to behave funny. “Ah, a sign from above,” I thought. “Finally the day has come, I will install Ubuntu on my laptop.” So I did. Ubuntu Dekstop 9.04 was installed with ease and -even more impressively- it recognised all of my Thinkpad hardware. Even the Wifi connection was up and running without fiddling about.

I should have said “almost all” hardware. Unfortunately one piece of hardware refused cooperation with Linux, namely my Novatel USB modem. Since I’ve come to rely on 3G mobile Internet, this is a knockout criterion. No modem, no Internet. After hours of scouring the Web for possible solutions and  trying out various settings, I gave up in frustration. There wasn’t anything I could do except zapping the Linux partition and installing old friend XP. To attenuate my disappointment, I will make it a dual boot machine, though. Note to hardware vendors: please take Linux seriously and provide drivers for your nifty electronics. That would make life much easier. I guess I have to postpone my switch-over to Linux for another year. Hopefully I will be able to resist the urge to buy another piece of exotic hardware in the meantime.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Technorati
  • Facebook
  • Mixx
  • Google
  • YahooMyWeb
  • Slashdot
  • LinkedIn
  • blogmarks
  • Live
  • description
  • StumbleUpon
  • Ma.gnolia
  • MisterWong
  • NewsVine
  • Reddit
  • Spurl
  • Yigg
  • E-mail this story to a friend!