a decimal class for Squeak?

Jan Bottorff janb at pmatrix.com
Wed Jan 6 01:49:18 UTC 1999


At 09:50 PM 1/4/99 +0100, Hans-Martin Mosner wrote:
>You probably don't need a decimal arithmetic class. What you need is
>decimal fractions, which are just a special case of the good old
>Fractions found in every self-respecting Smalltalk. With these,
>arithmetic using +, -, *, / is exact (which is probably what you're after
>anyway).

For business math use, exact answers may NOT be what's needed. What's
needed are consistent answers. You basically need control over the
precision at every step of a computation.

A quickie example goes like this. Assume your doing time billing. One
common function is to generate invoices. Let's say you have a billing item
of 17 minutes at a rate of $100/hour and another billing item of 17 minutes
at $100/hour. For each line item, you calculate and print the price,
$28.33. If you retain full precision of the item price calculations, you
will calculate a total price of $56.67, which is wrong. This was caused by
carrying over and doing math on extra precision that was not used for the
printed price. The correct answer is $56.66. What's needed is predictable
control over loss of precision. You need to use the slightly inaccurate
number that gets printed as the value for further calculations.

One solution might be a method that truncates the precision of a number.
For example:

	lineItem at:1 put:((aTime * aRate) truncatDecimalPrecision:2).

No new number class would be required, although a new class could do some
of the work. Like this:

	lineItemHolder := DecimalMath withPrecision:2.
	lineItemHolder value:(aTime * aRate). "or lineItemHolder multiple:aTime
by:aRate."
	aString := lineItemHolder asDecoratedString. "giving something like
1,234,567.00"

I suppose if aRate were already kept as a DecimalMath, you could do:

	lineItemHolder := aRate * aTime. "not sure if coersion would do the right
thing"

Another problem is rounding in this context. My memory is a bit fuzzy, but
believe a thing called banker's rounding should be used. As I remember, you
round values based on the next digit. For example, 7.555000 rounded to two
decimal places would be 7.56, but 7.655000 would round down to 7.65. The
lowest significant digit being odd or even controls this. A bit of research
should be done to determine the way to do this that matches everybody else,
so cross system calculations are consistent. Banker's rounding reduces
small accumulated errors when math is done on limited precision numbers.

It's also possible this should be controllable in a DecimalMath instance,
like:

	lineItemHolder := DecimalMath new.
	lineItemHolder setPrecision:2.
	lineItemHolder setBankersRounding:true.

I could also imagine many other features a DecimalMath class might have,
like setting the maximum value (throws an exception when the number no
longer fits in the database field). Could be the class should be called
SlightlyInaccurateButConsistentMath.

These are annoying little details about math, which for business use really
do need to be right. My belief is class definitions should be used as
places to collect knowledge like this. Many developers do not really want
to spend time understanding the details of some issue, they just want a
prepackaged box that "handles" things. A wonderful example is the Date
class in Smalltalk. For as long as I can remember, the Date class did the
right thing for leap year, for centuries to come.

- Jan

___________________________________________________________________
            Paradigm Matrix Inc., San Ramon California
   "video products and development services for Win32 platforms"
Internet: Jan Bottorff janb at pmatrix.com
          WWW          http://www.pmatrix.com
Phone: voice  (925) 803-9318
       fax    (925) 803-9397
PGP: public key  <http://www-swiss.ai.mit.edu/~bal/pks-toplev.html>
     fingerprint  52 CB FF 60 91 25 F9 44  6F 87 23 C9 AB 5D 05 F6
___________________________________________________________________





More information about the Squeak-dev mailing list