This was posted on reddit --
http://cafe.elharo.com/programming/spot-the-bug/
Printed representations aside (that can be taken care of with ~$), how
do you perform monetary calculations in CL?
One way is to represent "cents" using integers. But what I would like to
have is something like Python's decimal type [1]. I don't think standard
CL supports anything like that. Is there any library, etc. which can do
this?
Or is Python's decimal type the wrong way to look? Is there anything
better out there?
Thanks,
Chaitanya
Note:
1. http://cafe.elharo.com/programming/spot-the-bug/
On Feb 10, 11:35 am, Chaitanya Gupta <····@chaitanyagupta.com> wrote:
> Printed representations aside (that can be taken care of with ~$), how
> do you perform monetary calculations in CL?
Chaitanya, I have a written a library whose express intent is
supporting monetary calculations in Common Lisp. You can even do
multi-currency calculations, in which case "balance" objects are
creating which track the individual sub-totals of each currency. You
can convert between currencies using a conversion price, and track the
history of known prices in order to support historical reporting of
market values.
The library is called CAMBL, or the Commoditized Amounts and Balances
Library, and you can find it here:
http://hg.newartisans.com/cambl
It depends on the LOCAL-TIME module (see Google), and also on two
others modules which are also available on the same server:
http://hg.newartisans.com/red-black
http://hg.newartisans.com/periods
The library is very close to its first release. I'm currently using
it as the back-end for an accounting engine, CL-Ledger, which is also
available at the same location. Note that even though it has not been
formally released yet, the code is quite mature, as it represents a
direct port from C++ of a library which has been in active use for
over 4 years now. It comes with a suite of unit tests -- you will
need XLUNIT to run them -- which you should use to verify its
correctness on your platform.
In case you're interested in the implementation, I use rational number
to store all values, plus an integer to indicate how much floating-
point precision to use when printing a particular currency. You don't
have to explicitly specify the precision, either; the library observes
the formatting you use when inputting numbers, and maintains this
format automatically when outputting them. (There are also variants
functions for inputting special numbers whose display characteristics
should not be remembered, like dollar figures with more precision than
2 decimal places).
There is preliminary usage documentation at the top of the Lisp file.
I would be happy to work with you if changes are needed to suit the
library to your needs.
John
JohnW wrote:
> Chaitanya, I have a written a library whose express intent is
> supporting monetary calculations in Common Lisp. You can even do
> multi-currency calculations, in which case "balance" objects are
> creating which track the individual sub-totals of each currency. You
> can convert between currencies using a conversion price, and track the
> history of known prices in order to support historical reporting of
> market values.
This is great. I just checked out cambl from the repository. Will try it
and see how it works out. Is there any mailing list for the project?
> The library is very close to its first release. I'm currently using
> it as the back-end for an accounting engine, CL-Ledger, which is also
> available at the same location. Note that even though it has not been
> formally released yet, the code is quite mature, as it represents a
> direct port from C++ of a library which has been in active use for
> over 4 years now.
Actually, I was looking at Ledger/CL-Ledger just a few weeks back. Never
got around to installing it, but it has been on my todo list for
sometime. Perhaps now I'll be able to take a look. ;)
Chaitanya
On Feb 11, 11:32 am, Chaitanya Gupta <····@chaitanyagupta.com> wrote:
> This is great. I just checked out cambl from the repository. Will try it
> and see how it works out. Is there any mailing list for the project?
There are web forums for the Ledger project at:
http://forums.newartisans.com/
Anything posted here I usually respond to right away, since Apple Mail
notifies me of all new posts along with my regular e-mail. Or, if you
loathe web interfaces, just send me e-mail directly. I consider CAMBL
"done" at this time, and so am highly motivated to weed out any final
bugs before release.
There is also a Trac for CL-Ledger, in which you could register CAMBL
bugs and suggestions too:
http://trac.newartisans.com/ledger
You will need to register an account for both of these web services,
since allowing public posting and bug creation was resulting in far
too much spam.
John
Chaitanya Gupta <····@chaitanyagupta.com> writes:
> Printed representations aside (that can be taken care of with ~$), how
> do you perform monetary calculations in CL?
It depends on your need.
If you're just trying to work out how to manage your personal
mortgage, and can accept slight discrepancies in exchange for speed
and ease of use, floats would probably be fine. If you're trying to
model the dynamics of world economies, which is fuzzy anyway, again
floats are probably fine.
Any use of floats would NOT be adequate to banks, since the roundoff
would be too unpredictable. CL does not offer a packaged solution to
that but does offer some useful tools. e.g., you could use a DOLLARS
class that contained a slot that held an integer number of thousandths
of pennies. And then write whatever methods on the class you wanted
to be able to do. +$, *$, etc.
Even if there were a "fixed decimal" type or something like that,
you'd still have the issue of whether it was proper to do things like
multiply that times another fixed decimal. Using the DOLLARS class,
you could actually control it enough that it could add two DOLLARS
classes but forbid you to add a regular integer to that class, or that
could let you multiply DOLLARS times an integer, but not times
DOLLARS.
Kent M Pitman wrote:
>
> Any use of floats would NOT be adequate to banks, since the roundoff
> would be too unpredictable. CL does not offer a packaged solution to
> that but does offer some useful tools. e.g., you could use a DOLLARS
> class that contained a slot that held an integer number of thousandths
> of pennies. And then write whatever methods on the class you wanted
> to be able to do. +$, *$, etc.
>
> Even if there were a "fixed decimal" type or something like that,
> you'd still have the issue of whether it was proper to do things like
> multiply that times another fixed decimal. Using the DOLLARS class,
> you could actually control it enough that it could add two DOLLARS
> classes but forbid you to add a regular integer to that class, or that
> could let you multiply DOLLARS times an integer, but not times
> DOLLARS.
That's a very interesting way of handling it. I would certainly like to
try it out.
Also, is this technique being used widely? What could be the potential
drawbacks of this?
Chaitanya
Chaitanya Gupta <····@chaitanyagupta.com> writes:
> Kent M Pitman wrote:
>>
>> Any use of floats would NOT be adequate to banks, since the roundoff
>> would be too unpredictable. CL does not offer a packaged solution to
>> that but does offer some useful tools. e.g., you could use a DOLLARS
>> class that contained a slot that held an integer number of thousandths
>> of pennies. And then write whatever methods on the class you wanted
>> to be able to do. +$, *$, etc.
>>
>> Even if there were a "fixed decimal" type or something like that,
>> you'd still have the issue of whether it was proper to do things like
>> multiply that times another fixed decimal. Using the DOLLARS class,
>> you could actually control it enough that it could add two DOLLARS
>> classes but forbid you to add a regular integer to that class, or that
>> could let you multiply DOLLARS times an integer, but not times
>> DOLLARS.
>
> That's a very interesting way of handling it. I would certainly like to
> try it out.
>
> Also, is this technique being used widely? What could be the potential
> drawbacks of this?
>
It sounds reasonable for accounting purposes but is insufficient for
financial arithmetic- there are often rules for the application of
things like yield rates that specify a number of places of precision and
specific rules for rouding; for example, maintain 12 decimal places and
round the 13th (by implication do not employ precision to more than the
13th place to the right of the decimal). Intermediate products of some
calculations can easily consume 20 or more significant digits.
Simple arithmetic on such calculations is inappropriate. Cobol handles
this sort of thing quite well (intentionally I am sure). CL's numeric
types will easily handle the required precision but the rounding rules
will probably be somewhat troublesome. The class-based approach is
probably the way to handle it- perhaps an "interest" class where the #
of places of significance and the rounding policy are supplied.
Gregm
P� Thu, 14 Feb 2008 13:50:18 +0100, skrev Greg Menke <·······@comcast.net>:
>
> It sounds reasonable for accounting purposes but is insufficient for
> financial arithmetic- there are often rules for the application of
> things like yield rates that specify a number of places of precision and
> specific rules for rouding; for example, maintain 12 decimal places and
> round the 13th (by implication do not employ precision to more than the
> 13th place to the right of the decimal). Intermediate products of some
> calculations can easily consume 20 or more significant digits.
>
> Simple arithmetic on such calculations is inappropriate. Cobol handles
> this sort of thing quite well (intentionally I am sure). CL's numeric
> types will easily handle the required precision but the rounding rules
> will probably be somewhat troublesome. The class-based approach is
> probably the way to handle it- perhaps an "interest" class where the #
> of places of significance and the rounding policy are supplied.
>
> Gregm
Well the most straightforward way is Binary Coded Decimal (BCD).
One nibble (4 bits) is reserved for each digit 0-9. So you write a library
to access numbers this way.
(Many processors have support for this.) Writing such a library is a bit
cumbersome, but still easier than dealing with the imprecision explicitly
in bignums I would think. (Because humans think of precision in digits
not binary)
--------------
John Thingstad
On Feb 14, 8:08 am, "John Thingstad" <·······@online.no> wrote:
> På Thu, 14 Feb 2008 13:50:18 +0100, skrev Greg Menke <·······@comcast.net>:
> > Simple arithmetic on such calculations is inappropriate. Cobol handles
> > this sort of thing quite well (intentionally I am sure). CL's numeric
> > types will easily handle the required precision but the rounding rules
> > will probably be somewhat troublesome. The class-based approach is
> > probably the way to handle it- perhaps an "interest" class where the #
> > of places of significance and the rounding policy are supplied.
>
> > Gregm
>
> Well the most straightforward way is Binary Coded Decimal (BCD).
> One nibble (4 bits) is reserved for each digit 0-9. So you write a library
> to access numbers this way.
> (Many processors have support for this.) Writing such a library is a bit
> cumbersome, but still easier than dealing with the imprecision explicitly
> in bignums I would think. (Because humans think of precision in digits
> not binary)
What? Given exact rationals, you can implement the rounding rules
arithmetically. You don't need to depend on how those numbers are
implemented.
E.g. suppose you are to round to the nearest 0.01 such that:
0.xy4... -> 0.xy
0.xy5 -> (if (evenp y) 0.xy (+ 0.xy 0.01))
0.xy5... -> 0.xy + 0.01
You multiply the input by 100 to obtain xy.zw. To get the y digit, if
necessary, truncate the number to integer, and compute the residue
modulo 10. If the fractional part is less than half, take the
truncated number and divide by 100. If it's greater than half, add 1
and divide by 100. If it's exactly half, apply the test to y.
Here is some code, which has a few simplifying assumptions. One is
that the input is rational. The second is that EXPT gives us a
rational (and even if the exponent is negative). This works in CLISP
but is nonportable.
(defun round-even-odd-rule (input digits)
(let* ((multiplier (expt 10 digits))
(sign (signum input))
(scaled (* (abs input) multiplier)))
(multiple-value-bind (int frac) (truncate scaled)
(* sign
(/ (cond
((< frac 1/2) int)
((> frac 1/2) (1+ int))
(t (let ((leastdigit (mod int 10)))
(if (evenp leastdigit)
int (1+ int)))))
multiplier)))))
;; round to integer:
(round-even-odd-rule 1/2 0) -> 1
;; round to multiple of 10:
(round-even-odd-rule 1/2 1) -> 0
;; round 1.125 to nearest hundredth:
;; should be 1.12 since 2 is even.
(round-even-odd-rule 1125/1000 2) -> 28/25 ;; i.e. 112/100 or 1.12
;; round 1.135 to nearest hundredth:
;; should be 1.14 since 3 is odd:
(round-even-odd-rule 1135/1000 2) -> 57/50 ;; i.e. 114/100
;; round 7885 to nearest thousand:
(round-even-odd-rule 7885 -3) -> 8000
Of course, with BCD, to do the rounding, you just duplicate the input
number, and then work on the digits directly. Converting BCD between
internal representation and text is also trivial. (Have I, as I
suspect, just summed up the complete gamut of the advantages of BCD?)
P� Thu, 14 Feb 2008 21:14:16 +0100, skrev Kaz Kylheku <········@gmail.com>:
>
> What? Given exact rationals, you can implement the rounding rules
> arithmetically. You don't need to depend on how those numbers are
> implemented.
>
Of course not.
>
> ;; round 1.125 to nearest hundredth:
> ;; should be 1.12 since 2 is even.
> (round-even-odd-rule 1125/1000 2) -> 28/25 ;; i.e. 112/100 or 1.12
Fine but now you have to print it as 1.12 and don't mess up the precision..
>
> ;; round 1.135 to nearest hundredth:
> ;; should be 1.14 since 3 is odd:
> (round-even-odd-rule 1135/1000 2) -> 57/50 ;; i.e. 114/100
>
Again rational does the 'wrong' thing.. ie. it would have been just as
easy to just keep it as 114/100
We haven't covered reading values from the user/file yet..
Doing it with BCD still seems easier to me.
--------------
John Thingstad
"John Thingstad" <·······@online.no> writes:
> P� Thu, 14 Feb 2008 13:50:18 +0100, skrev Greg Menke <·······@comcast.net>:
>
>>
>> It sounds reasonable for accounting purposes but is insufficient for
>> financial arithmetic- there are often rules for the application of
>> things like yield rates that specify a number of places of precision and
>> specific rules for rouding; for example, maintain 12 decimal places and
>> round the 13th (by implication do not employ precision to more than the
>> 13th place to the right of the decimal). Intermediate products of some
>> calculations can easily consume 20 or more significant digits.
>>
>> Simple arithmetic on such calculations is inappropriate. Cobol handles
>> this sort of thing quite well (intentionally I am sure). CL's numeric
>> types will easily handle the required precision but the rounding rules
>> will probably be somewhat troublesome. The class-based approach is
>> probably the way to handle it- perhaps an "interest" class where the #
>> of places of significance and the rounding policy are supplied.
>>
>> Gregm
>
> Well the most straightforward way is Binary Coded Decimal (BCD).
> One nibble (4 bits) is reserved for each digit 0-9. So you write a
> library to access numbers this way.
> (Many processors have support for this.) Writing such a library is a bit
> cumbersome, but still easier than dealing with the imprecision
> explicitly in bignums I would think. (Because humans think of precision
> in digits not binary)
Probably the easiest way would be to try and do it how COBOL does- since
it was designed to handle this sort of problem. Its worth observing
that when doing this kind of math, conformance to the specified rounding
policy is far more important than speed, both in the operational sense,
and when the time comes to audit the system, and maybe even more
importantly when you have to start tracking down where the 1 cent error
came from...
This is perhaps an example of an "Anti-Greenspun Law", such as, "Any
sufficiently complex financial application contains an ad-hoc,
informally specified, bug ridden slow implementation of COBOL's fixed
place numeric types".
I've seen it happen on a number of occasions, and I've perpetrated it
myself.
Gregm
On Feb 10, 12:54 pm, Kent M Pitman <······@nhplace.com> wrote:
> Chaitanya Gupta <····@chaitanyagupta.com> writes:
> > Printed representations aside (that can be taken care of with ~$), how
> > do you perform monetary calculations in CL?
>
> It depends on your need.
>
> If you're just trying to work out how to manage your personal
> mortgage, and can accept slight discrepancies in exchange for speed
> and ease of use, floats would probably be fine. If you're trying to
> model the dynamics of world economies, which is fuzzy anyway, again
> floats are probably fine.
>
> Any use of floats would NOT be adequate to banks, since the roundoff
> would be too unpredictable. CL does not offer a packaged solution to
> that but does offer some useful tools. e.g., you could use a DOLLARS
> class that contained a slot that held an integer number of thousandths
> of pennies. And then write whatever methods on the class you wanted
> to be able to do. +$, *$, etc.
>
> Even if there were a "fixed decimal" type or something like that,
> you'd still have the issue of whether it was proper to do things like
> multiply that times another fixed decimal. Using the DOLLARS class,
> you could actually control it enough that it could add two DOLLARS
> classes but forbid you to add a regular integer to that class, or that
> could let you multiply DOLLARS times an integer, but not times
> DOLLARS.
Of course, if you use a rational to represent some currency amount
exactly, and another rational to represent some multiplier exactly
(e.g. 105/100 for 1.05 or 5% tax or whatever) then you can just
multiply these two together exactly using the * operator, and then
post-filter the result through a precise rounding rule.
If you were doing pencil-and-paper arithmetic, you'd do the same:
$123.95 ;; exact rational inputs
x 1.05
--------
6 1975
0
123 95
--------
130.1475 ;; exact rational result
Now apply specific rounding rule: e.g. round to nearest, with even-odd
rule to resolve the equidistant case. 0.147 is closer to 0.15 than to
0.14, so:
130.15 ;; exact rational rounded result
In article <············@registered.motzarella.org>,
Chaitanya Gupta <····@chaitanyagupta.com> wrote:
> This was posted on reddit --
> http://cafe.elharo.com/programming/spot-the-bug/
>
> Printed representations aside (that can be taken care of with ~$), how
> do you perform monetary calculations in CL?
>
> One way is to represent "cents" using integers. But what I would like to
> have is something like Python's decimal type [1]. I don't think standard
> CL supports anything like that. Is there any library, etc. which can do
> this?
>
> Or is Python's decimal type the wrong way to look? Is there anything
> better out there?
>
> Thanks,
>
> Chaitanya
>
> Note:
> 1. http://cafe.elharo.com/programming/spot-the-bug/
It might also be possible to use 'measures'
for representing and handling monetary data.
'MEASURES is a system to handle engineering numbers and measures in
Common Lisp.'
http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/lisp/code/syntax/measures/0.html
LOOM has probably a newer version of measures.
http://www.isi.edu/isd/LOOM/
Rainer Joswig wrote:
>
> It might also be possible to use 'measures'
> for representing and handling monetary data.
>
Measures looks very interesting. Thanks for the link.
Rainer Joswig <······@lisp.de> writes:
> It might also be possible to use 'measures'
> for representing and handling monetary data.
>
> 'MEASURES is a system to handle engineering numbers and measures in
> Common Lisp.'
>
> http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/lisp/code/syntax/measures/0.html
>
> LOOM has probably a newer version of measures.
> http://www.isi.edu/isd/LOOM/
It would certainly be possible to do that.
One additional advantage of using the measures package is that one could
then, in principle, also support multiple currencies and automatically
convert between them. The only drawback is that the measures package
assumes that conversion factors are constant, instead of fluctuating.
Although one could keep redefining the conversions to track the market.
Internally, the measures package uses rational numbers for everything
(converted from decimal input using RATIONALIZE), just in order to avoid
floating point rounding issues. People generally like it better if a
units and measures package has 1mm*3.2 => 3.2mm instead of 3.2000002mm ;)
--
Thomas A. Russ, USC/Information Sciences Institute
Chaitanya Gupta wrote:
> One way is to represent "cents" using integers. But what I would like to
> have is something like Python's decimal type [1].
> ...
> Note:
> 1. http://cafe.elharo.com/programming/spot-the-bug/
Oops. That should have been: http://docs.python.org/lib/module-decimal.html
Chaitanya
Chaitanya Gupta <····@chaitanyagupta.com> writes:
> Printed representations aside (that can be taken care of with ~$), how
> do you perform monetary calculations in CL?
>
> One way is to represent "cents" using integers. But what I would like to
> have is something like Python's decimal type [1]. I don't think standard
> CL supports anything like that. Is there any library, etc. which can do
> this?
Well, another solution that works with standard Common Lisp datatypes
would be to use rational numbers to represent your monetary values.
Dollars, for example, would be integer values and the cents would be
represented as the appropriate fractions. So, for example, you would
write $5.27 as 527/100 or (+ 5 27/100) or even (rationalize 5.27). If
you go the latter route, it is important to use RATIONALIZE rather than
RATIONAL, since the former will give you a nicer rational number.
--
Thomas A. Russ, USC/Information Sciences Institute
Thomas A. Russ wrote:
> Dollars, for example, would be integer values and the cents would be
> represented as the appropriate fractions. So, for example, you would
> write $5.27 as 527/100 or (+ 5 27/100) or even (rationalize 5.27). If
> you go the latter route, it is important to use RATIONALIZE rather than
> RATIONAL, since the former will give you a nicer rational number.
Thanks for the heads up on RATIONALIZE/RATIONAL -- I didn't even know
that these functions existed.
Chaitanya