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:
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:
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:
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:
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.
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:
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:
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.
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.
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.