cambl
2018-12-10
A library for working with financial amounts involving multiple commodities.
Upstream URL
Author
Maintainer
License
*Commoditized Amounts and Balances*\\
This library provides a convenient facility for working with commoditized values. It does not allow compound units -- and so is not suited for scientific operations -- but does work rather nicely for the purpose of financial calculations.
The primary entry point to using this library is the creation of VALUES, which may be of many different types. These VALUES are then used as arguments to a set of basic mathematical OPERATIONS, which also yield VALUES. However, the type of the result may not be the same type as either of the operands.
For example, let's say you want to create a figure representing 10 US dollars. You then add an INTEGER to this amount, the number 200. The result of such an operation will be an amalgam of the two units involved: Dollars, and No-units. This dual-commodity value is represented as a BALANCE object, whose printed form shows each commodity amount on its own line. For example:
(let ((value (cambl:amount "$100.00")))
(princ (cambl:format-value (cambl:add value 200))))
=> 200
$100.00
This library aims to provide convenient access to commoditized math; that is: math involving commodity units. Unlike scientific units, there is no concept of "compound" units. If you divide 1kg by 1m, you do not get "1 kg/m", but "1 kg" (the unit of the second operand is ignored for multiplication and division). The intended use of this library is in situations such as computing financial transactions.
1Amounts
There are just a few main entry points to the CAMBL library for dealing with amounts (which is mainly how you'll use it). Here is a quick list, following by a description in the context of a REPL session. Note that where the name contains value, it will work for integers, amounts and balances. If it contains amount or balance, it only operates on entities of that type.
Function | Description |
---|---|
amount[*] | create an amount from a string |
exact-amount | create an amount from a string which overrides |
its commodity's display precision; this feature | |
"sticks" through any math operations | |
parse-amount[*] | parse an amount from a string (alias for 'amount') |
read-amount[*] | read an amount from a stream |
read-exact-amount | read an exact amount from a stream |
format-value | format a value to a string |
print-value | print a value to a stream |
add | perform math using values; the values are |
subtract | changed as necessary to preserve information |
multiply | (adding two amounts may result in a |
divide | balance). |
value-zerop | would the value display as zero? |
value-zerop* | is the value truly zero? |
value-minusp | would the amount display as a negative amount? |
value-minusp* | is the amount truly negative? |
value-plusp | would it display as an amount greater than zero? |
value-plusp* | is it truly greater than zero? |
value= | compare two values |
value/= | |
value< | compare values (not meaningful for all values, |
value<= | such as balances) |
value> | |
value>= | |
value-equal | compare two values for exact match |
value-equalp | compare two values only after rounding to what |
would be shown to the user (approximate match) | |
-- this is the same as value= | |
value-not-equal | compare if two values for not an exact match |
value-not-equalp | compare two values after commodity rounding |
value-lessp | same as value< |
value-lessp* | compare if a < b exactly, with full precision |
value-lesseqp | same as value<= |
value-lesseqp* | exact version of value<= |
value-greaterp | same as value> |
value-greaterp* | exact version of value> |
value-greatereqp | same as value>= |
value-greatereqp* | exact version of value>= |
amount-precision | return the internal precision of an amount |
display-precision | return the "display" precision for an amount |
or a commodity |
2Example Session
Interacting with CAMBL begins with creating an amount. This is done most easily from a string, but it can also be read from a stream:
(cambl:amount "$100.00")
=> #<CAMBL:AMOUNT "$100.00" :KEEP-PRECISION-P NIL>
(with-input-from-string (in "$100.00")
(cambl:read-amount in))
=> #<CAMBL:AMOUNT "$100.00" :KEEP-PRECISION-P NIL>
When you parse an amount using one of these two functions, CAMBL creates a COMMODITY class for you, whose symbol name is "$". This class remembers details about how you used the commodity, such as the input precision and the format of the commodity symbol. Some of these details can be inspected by looking at the amount's commodity directly:
(cambl:amount-commodity (cambl:amount "$100.00"))
=> #<CAMBL::COMMODITY #S(CAMBL::COMMODITY-SYMBOL
:NAME $
:NEEDS-QUOTING-P NIL
:PREFIXED-P T
:CONNECTED-P T)
:THOUSAND-MARKS-P NIL :DISPLAY-PRECISION 2>
Here you can see that the commodity for $100.00 is $, and it knows that the commodity should be prefixed to the amount, and that it gets connected to the amount. This commodity was used without any "thousand marks" (i.e. $1000.00 vs $1,000.00), and it has a maximum display precision of TWO observed so far. If we print such an amount, we'll see the same style as was input:
(cambl:format-value (cambl:amount "$100.00"))
=> "$100.00"
(cambl:print-value (cambl:amount "$100.00"))
=> NIL
$100.00
CAMBL observed how you used the "$" commodity, and now reports back all dollar figures after the same fashion. Even though there are no cents in the amounts above, CAMBL will still record a full two digits of precision (this becomes important during division, to guard against fractional losses during repeated rounding).
(cambl:amount-precision (cambl:amount "$100.00"))
=> 2
CAMBL remembers the greatest precision it has seen thus far, but never records a lesser precision. So if you parse $100.00 and then $100, both values will be printed as $100.00.
There are three functions for creating amounts, but they have some
subtle differences where precision is concerned. They are: amount
,
amount*
, and exact-amount
. Here are the differences:
(cambl:amount "$100.00")
=> #<CAMBL:AMOUNT "$100.00" :KEEP-PRECISION-P NIL>
- amount has an internal precision of 2
- commodity $ has a display precision of 2 (if no otheramount using a higher precision was observed so far)
- when printing, amount uses the commodity's precision
(cambl:format-value *)
=> "$100.00"
(cambl:format-value ** :full-precision-p t)
=> "$100.00"
(cambl:amount* "$100.0000")
=> #<CAMBL:AMOUNT "$100.0000" :KEEP-PRECISION-P NIL>
- amount has an internal precision of 4
- commodity $ still has a display precision of 2 (from above)
- when printing, amount uses the commodity's precision
(cambl:format-value *)
=> "$100.00"
(cambl:format-value ** :full-precision-p t)
=> "$100.0000"
(cambl:exact-amount "$100.0000")
=> #<CAMBL:AMOUNT "$100.0000" :KEEP-PRECISION-P T>
- amount has an internal precision of 4
- commodity $ still has a display precision of 2 (from above)
- when printing, amount uses its internal precision
(cambl:format-value *)
=> "$100.0000"
(cambl:format-value ** :full-precision-p t)
=> "$100.0000"
There are similar variants for the stream reading functions:
read-amount
read-amount*
read-exact-amount
Internally, an amount's quantity is stored as a rational number, and
therefore has a perfect precision. The internal precision field of an
amount tries to keep track of the number of decimals to display when
printing a value with the FULL-PRECISION-P
option.
By default, uncommoditized amounts (which are in fact just rational
numbers), are displayed with a precision of 3. This can be changed by
setting CAMBL:*DEFAULT-DISPLAY-PRECISION*
. When they are used in
math operations, their considered internal precision to compute the
result's internal precision is at most CAMBL:*EXTRA-PRECISION*
(which is 6 by default).
NOTE: The KEEP-PRECISION-P
property of an amount carries through
any math operations involving that amount, so that the final result is
always displayed using its own internal percision.
The point of all this is that amounts are displayed as the user expects them to be, but internally never lose information. In fact, if you divide two high internal precision amounts together, you'll get a new amount with a very high internal precision, but which still displays as expected:
(setf *tmp* (cambl:divide (cambl:amount "$100.00")
(cambl:amount "50000000")))
(cambl:format-value *tmp* :full-precision-p t)
=> "$0.0000020000000000"
(cambl:format-value *tmp*)
=> "$0.00"
You'll notice here that the amount displayed is not
$0.00000200000000002. This is because CAMBL does not try to capture
every digit resulting from a division; rather, it keeps six more
digits of precision than is strictly necessary so that even after
millions of calculations, not a penny is lost. If you find this is not
enough slack for your calculations, you can set
CAMBL:*EXTRA-PRECISION*
to a higher or lower value.
3Commodities
CAMBL offers several methods for accessing the commodity information relating to amounts:
Function | Description |
---|---|
amount-commodity | the COMMODITY referenced by an amount |
display-precision | display precision of an AMOUNT or COMMODITY |
commodity-qualified-name | the name used print a COMMODITY |