
Good day all, my budding ledger program could not balance transactions exactly because of rounding error with Double. I *think* I got it working better with Rational (it was late). Another suggestion from #haskell was to multiply all money by 100. I'm tracking multiple currencies/commodities with varying precision so this gets a bit more complicated. Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ? Best - Simon

From: haskell-cafe-bounces@haskell.org [mailto:haskell-cafe-bounces@haskell.org] On Behalf Of Simon Michael
Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ?
I think Data.Fixed would be a good choice: http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-Fixed.ht ml I don't know /exactly/ how you'd go about adding a Money data type; I guess you'd have something like:
data E2 = E2 instance HasResolution E2 where resolution _ = 100 type Money = Fixed E2
Alistair ***************************************************************** Confidentiality Note: The information contained in this message, and any attachments, may contain confidential and/or privileged material. It is intended solely for the person(s) or entity to which it is addressed. Any review, retransmission, dissemination, or taking of any action in reliance upon this information by persons or entities other than the intended recipient(s) is prohibited. If you received this in error, please contact the sender and delete the material from any computer. *****************************************************************

On Jul 12, 2007, at 4:40 , Bayley, Alistair wrote:
From: haskell-cafe-bounces@haskell.org [mailto:haskell-cafe-bounces@haskell.org] On Behalf Of Simon Michael
Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ?
I think Data.Fixed would be a good choice:
http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data- Fixed.ht ml
I don't know /exactly/ how you'd go about adding a Money data type; I guess you'd have something like:
data E2 = E2 instance HasResolution E2 where resolution _ = 100 type Money = Fixed E2
I was playing with that when your message came in:
mress:5003 Z$ cat foo.hs import Data.Fixed
data E2 = E2 instance HasResolution E2 where resolution _ = 100 type Money = Fixed E2
mress:5004 Z$ ghci foo.hs ___ ___ _ / _ \ /\ /\/ __(_) / /_\// /_/ / / | | GHC Interactive, version 6.7.20070322, for Haskell 98. / /_\\/ __ / /___| | http://www.haskell.org/ghc/ \____/\/ /_/\____/|_| Type :? for help.
Loading package base ... linking ... done. [1 of 1] Compiling Main ( foo.hs, interpreted ) Ok, modules loaded: Main. *Main> :m +Data.List *Main Data.List> let l = [1.25 :: Money,2.00,4.46,12.80,1.15,6.00] in sum l / genericLength l 4.61
That what you want? -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

From: Brandon S. Allbery KF8NH [mailto:allbery@ece.cmu.edu]
I was playing with that when your message came in:
mress:5003 Z$ cat foo.hs import Data.Fixed
data E2 = E2 instance HasResolution E2 where resolution _ = 100 type Money = Fixed E2
... *Main> :m +Data.List *Main Data.List> let l = [1.25 :: Money,2.00,4.46,12.80,1.15,6.00] in sum l / genericLength l 4.61
That what you want?
Yes... I'm now wondering how one would read Money values. There's a Show instance for Fixed, but no Read. Could use: readMoney :: String -> Money readMoney s = let d :: Double; d = read s in realToFrac d ... but is there a better way? Note that readMoney is a bit dumb: *Main> readMoney "3.149999" 3.14 Alistair ***************************************************************** Confidentiality Note: The information contained in this message, and any attachments, may contain confidential and/or privileged material. It is intended solely for the person(s) or entity to which it is addressed. Any review, retransmission, dissemination, or taking of any action in reliance upon this information by persons or entities other than the intended recipient(s) is prohibited. If you received this in error, please contact the sender and delete the material from any computer. *****************************************************************

Great - indeed,
sum [1.85, 5.95, -7.80] 8.881784197001252e-16 sum [1.85::Money, 5.95, -7.80] 0.00
I'm not yet sure these will do the best thing in all arithmetic, but it seems to be the right thing for now. Yes, I will need to read these also. Perhaps first reading the integer and decimal digits as separate integers will help. I'm still exploring the number types. Thanks for the tips!

simon:
Great - indeed,
sum [1.85, 5.95, -7.80] 8.881784197001252e-16 sum [1.85::Money, 5.95, -7.80] 0.00
I'm not yet sure these will do the best thing in all arithmetic, but it seems to be the right thing for now.
Yes, I will need to read these also. Perhaps first reading the integer and decimal digits as separate integers will help. I'm still exploring the number types.
Roman Leschinskiy tells me that there are C (or C++?) libraries for locale-specific money handling, where given precisions are mandated in particular countries, below which you must round. Perhaps we should have a binding to this. Anyway, sorting out how money is supposed to be represented in Haskell, and documenting it, seems a very useful thing. -- Don

Donald Bruce Stewart wrote:
Roman Leschinskiy tells me that there are C (or C++?) libraries for locale-specific money handling, where given precisions are mandated in particular countries, below which you must round. Perhaps we should have a binding to this.
IIRC, the rules in the EU were that operations on money have to be done with 3 decimal digits and then rounded to 2. That is, 0.01 / 2 = 0.01 (0.005 rounded to 2 decimal digits). I may be mistaken, though. What I'm fairly sure of is that required precision and rounding policies differ from country to country. Getting it right is *much* harder than it looks. Roman

Donald Bruce Stewart wrote:
simon:
Great - indeed,
sum [1.85, 5.95, -7.80] 8.881784197001252e-16 sum [1.85::Money, 5.95, -7.80] 0.00
I'm not yet sure these will do the best thing in all arithmetic, but it seems to be the right thing for now.
Yes, I will need to read these also. Perhaps first reading the integer and decimal digits as separate integers will help. I'm still exploring the number types.
Roman Leschinskiy tells me that there are C (or C++?) libraries for locale-specific money handling, where given precisions are mandated in particular countries, below which you must round. Perhaps we should have a binding to this.
Anyway, sorting out how money is supposed to be represented in Haskell, and documenting it, seems a very useful thing.
-- Don
It is funny that this thread is going on alongside the "Defaulting to Rational" thread. There are separate issues with Money: (1) Never using Double and Float to avoid representation errors. (2) Provide the correct rounding at the correct moment. For (1) you can use Data.Ratio or a pair Data.Seq of digits in base-10 before and after the decimal point. For (2) you (2.a) you do not always want to round at each step of a calculation and (2.b) do not always want the same precision. Example of (2.a) The 'sum' works, but what about an average price of 100 items? If you total then divide it might work, but if you divide like : 0.32 / 100.00 is 0.00 then you have lost track of the money. Example of (2.b) I have several catalogs that I order from that list prices (in £ or $) to higher than normal precision, e.g. "$ 0.7525", since they are often bought in large quantities, such as electrical components. And exchange rates are also often quoted in higher than normal precision. The "Right Thing" for storing the value is probably a Data.Ratio or a Data.Sequence of digits in base-10. One might also store Currency with a phantom type to prevent ($10 + £10) from compiling: newtype Money cc = Money (Data.Ratio) -- Exact, and no bias toward base 10 class Currency a where unit :: a -- Smallest physical amount, e.g. $ 0.01 nearest :: a -> a -- Round to nearest multiple of unit symbol :: String description :: String Then the localization (l10n) is specified by a dummy type: data USD data GBP I am no l10n expert, but I could also see: instance Show (Money USD) where ... instance Show (Money GBP) where ...

Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ?
Disclaimer: I'm pretty much a beginner at Haskell. Hacked something together a while ago for handling amounts and currencies. It let's you specify the precision of each currency and stores the value as a scaled Integer value. Haven't gotten around to implementing arithmetics yet but by using the Integer values for calculations you sidestep the issues you run into with Reals. module Currency where type Value = Integer data (Currency c) => Amount c = Amount Value c toAmount :: (Real a, Currency c) => a -> c -> (Amount c) toAmount v c = Amount (round $ realToFrac $ v * (10 ^ (currencyPrecision c))) c class Currency c where currencyFormat :: (Num a) => c -> a -> String currencyRoundingUnit :: (Fractional a) => (Amount c) -> a currencyPrecision :: (Num a) => c -> a instance (Currency c) => Show (Amount c) where show a@(Amount _ c) = currencyFormat c $ amountRound a fromAmount :: (Fractional a, Currency c) => (Amount c) -> a fromAmount (Amount v c) = (fromInteger v) / (10 ^ (currencyPrecision c)) amountRound :: (Fractional a, Real a, Currency c) => (Amount c) -> a amountRound a@(Amount _ c) = realToFrac $ integer + (steps * unit) where total = fromAmount a integer = fromInteger $ truncate $ realToFrac total fraction = total - integer unit = currencyRoundingUnit a steps = fromInteger $ round $ fraction / unit data SEK = SEK instance Currency SEK where currencyFormat _ v = show v ++ "kr" currencyRoundingUnit _ = 0.5 currencyPrecision _ = 4 data USD = USD instance Currency USD where currencyFormat _ v = "$" ++ show v currencyRoundingUnit _ = 0.001 currencyPrecision _ = 4 class ExchangeRate c1 c2 where exchangeRate :: (Fractional a) => c1 -> c2 -> a amountConvert :: (Currency c1, Currency c2, ExchangeRate c1 c2) => Amount c1 -> c2 -> Amount c2 amountConvert (Amount v c1) c2 = Amount (round $ (fromInteger v) * (exchangeRate c1 c2)) c2 instance ExchangeRate SEK USD where exchangeRate _ _ = 0.14285

Simon, I think using Rational is a good solution. I have found tracking multiple currencies and commodities problematic. For example take gold, it is traded in London in Troy ounces, in Hong Kong in Tael, which is exactly 37.429 Grams, which differers from the definition of the Main Land Chinese Tael. Furthermore they are each bought and sold in different currencies. Since the conversion between units and exchange rate is always given to a fixed number of digits, I vote for Rational. Best Regards, Robert On Thursday 12 July 2007 15:42, Simon Michael wrote:
Good day all,
my budding ledger program could not balance transactions exactly because of rounding error with Double. I *think* I got it working better with Rational (it was late). Another suggestion from #haskell was to multiply all money by 100. I'm tracking multiple currencies/commodities with varying precision so this gets a bit more complicated.
Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ?
Best - Simon
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Simon Michael
Good day all,
my budding ledger program could not balance transactions exactly because of rounding error with Double. I *think* I got it working better with Rational (it was late). Another suggestion from #haskell was to multiply all money by 100. I'm tracking multiple currencies/commodities with varying precision so this gets a bit more complicated.
Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ?
Best - Simon
Disclaimer: I'm pretty much a beginner at Haskell. Hacked something together a while ago for handling amounts and currencies. It let's you specify the precision of each currency and stores the value as a scaled Integer value. Haven't gotten around to implementing arithmetics yet but by using the Integer values for calculations you sidestep the issues you run into with Reals. module Currency where type Value = Integer data (Currency c) => Amount c = Amount Value c toAmount :: (Real a, Currency c) => a -> c -> (Amount c) toAmount v c = Amount (round $ realToFrac $ v * (10 ^ (currencyPrecision c))) c class Currency c where currencyFormat :: (Num a) => c -> a -> String currencyRoundingUnit :: (Fractional a) => (Amount c) -> a currencyPrecision :: (Num a) => c -> a instance (Currency c) => Show (Amount c) where show a@(Amount _ c) = currencyFormat c $ amountRound a fromAmount :: (Fractional a, Currency c) => (Amount c) -> a fromAmount (Amount v c) = (fromInteger v) / (10 ^ (currencyPrecision c)) amountRound :: (Fractional a, Real a, Currency c) => (Amount c) -> a amountRound a@(Amount _ c) = realToFrac $ integer + (steps * unit) where total = fromAmount a integer = fromInteger $ truncate $ realToFrac total fraction = total - integer unit = currencyRoundingUnit a steps = fromInteger $ round $ fraction / unit data SEK = SEK instance Currency SEK where currencyFormat _ v = show v ++ "kr" currencyRoundingUnit _ = 0.5 currencyPrecision _ = 4 data USD = USD instance Currency USD where currencyFormat _ v = "$" ++ show v currencyRoundingUnit _ = 0.001 currencyPrecision _ = 4 class ExchangeRate c1 c2 where exchangeRate :: (Fractional a) => c1 -> c2 -> a amountConvert :: (Currency c1, Currency c2, ExchangeRate c1 c2) => Amount c1 -> c2 -> Amount c2 amountConvert (Amount v c1) c2 = Amount (round $ (fromInteger v) * (exchangeRate c1 c2)) c2 instance ExchangeRate SEK USD where exchangeRate _ _ = 0.14285 /Adde

At Thu, 12 Jul 2007 00:42:46 -0700, Simon Michael wrote:
Good day all,
my budding ledger program could not balance transactions exactly because of rounding error with Double. I *think* I got it working better with Rational (it was late). Another suggestion from #haskell was to multiply all money by 100. I'm tracking multiple currencies/commodities with varying precision so this gets a bit more complicated.
Is there a type or library out there that's good for representing money and other quantities while avoiding rounding errors ?
Almost. I have a mostly complete Decimal library for Haskell available at: http://www.n-heptane.com/nhlab/repos/Decimal/ It aims to meet the same goals the Python Money PEP: http://www.python.org/dev/peps/pep-0327/ Most of the implementation details are described in this standard: http://www2.hursley.ibm.com/decimal/ Currently there are two very significant bugs: 1. the rounding algorithm is completely wrong 2. In the division routine, I explicitly limit the number of digits after the decimal point to 9 places. Using the Decimal library you can then implement a Money type similar to what is show in: http://www.n-heptane.com/nhlab/repos/Decimal/Money.hs essentially, you declare a phantom data type like:
data Money currency = Money Decimal
which you can then parameterize by different types of currency
data Dollar = Dollar data Yen = Yen
fiveDollars :: Money Dollar fiveDollars = 5
fiveYen :: Money Yen fiveYen = 5
because the types are different, you won't be able to accidently charge someone 5 yen instead of 5 dollars :) However, you can still write currency agnostic functions:
doubleMyIncome :: Money c -> Money c doubleMyIncome income = income * 2.0
So, the decimal library is almost, but not quite, usable. Fixing the two (known) problems is not that hard, I just have not had the time. Also, the library really needs a good test suite before it can be considered suitable for an accounting program ;) Fortunately, the python library already has a good test suite that can copied. I believe the spec at IBM also includes a bunch of examples that could be interegrated into a test suite. The library includes the beginnings of a test suite, but it is not very complete. I am definitely willing to give guidance and assistance if you, or someone else, wants to help finish up the library. j.
participants (9)
-
Adde
-
Bayley, Alistair
-
Brandon S. Allbery KF8NH
-
dons@cse.unsw.edu.au
-
haskell@list.mightyreason.com
-
Jeremy Shaw
-
Robert Daniel Emerson
-
Roman Leshchinskiy
-
Simon Michael