Common Lisp library for working with commoditized amounts and balances
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
doc
Makefile
README.org
cambl-test.asd
cambl-test.lisp
cambl.asd
cambl.lisp
fprog.asd
fprog.lisp

README.org

CAMBL

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.

Amounts

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.

FunctionDescription
amount[*]create an amount from a string
exact-amountcreate 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-amountread an exact amount from a stream
format-valueformat a value to a string
print-valueprint a value to a stream
addperform math using values; the values are
subtractchanged as necessary to preserve information
multiply(adding two amounts may result in a
dividebalance).
value-zeropwould the value display as zero?
value-zerop*is the value truly zero?
value-minuspwould the amount display as a negative amount?
value-minusp*is the amount truly negative?
value-pluspwould 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-equalcompare two values for exact match
value-equalpcompare two values only after rounding to what
would be shown to the user (approximate match)
– this is the same as value=
value-not-equalcompare if two values for not an exact match
value-not-equalpcompare two values after commodity rounding
value-lesspsame as value<
value-lessp*compare if a < b exactly, with full precision
value-lesseqpsame as value<=
value-lesseqp*exact version of value<=
value-greaterpsame as value>
value-greaterp*exact version of value>
value-greatereqpsame as value>=
value-greatereqp*exact version of value>=
amount-precisionreturn the internal precision of an amount
display-precisionreturn the “display” precision for an amount
or a commodity

Example 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 other amount 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.

Commodities

CAMBL offers several methods for accessing the commodity information relating to amounts:

FunctionDescription
amount-commoditythe COMMODITY referenced by an amount
display-precisiondisplay precision of an AMOUNT or COMMODITY
commodity-qualified-namethe name used print a COMMODITY