The behaviour of ScaledDecimal in Squeak and Pharo is
- different from what the ANSI Smalltalk standard says
- different from what other Smalltalks do (not that they agree)
- completely different from decimal arithmetic in COBOL, PL/I, or SQL
- seriously confusing.
What you EXPECT is an exact rational number that is equal to an
integer divided by a power of 10, so that the printed
representation faithfully represents the value with no error.
What you GET in Pharo is a rational number that PRINTS with
a given number of decimal places but that is all.
Thus we expect that
�� x := 10/3 asScaledDecimal: 2.
�� x asString ==> '3.33s2'
�� y := x+x+x.
�� y asString ==> '9.99s2'
But no, y asString => '10.00s2'.
Here's what the comment in the VW class FixedPoint says:
"There are two possible ways to express FixedPoint numbers.�� One is as a scaled Integer, but the problem here is that you can lose precision during intermediate calculations.�� For example, a property that seems useful is that the calculation (1.000 / 7 * 7) should give you back the number 1.000.
For this reason, we have adopted an alternative representation, which is a slight variation on Fraction.�� The number is expressed as the ratio of a numerator to a denominator, with the addition of a precision that is used for printing and for rounding.�� The number is not usually rounded off to its scale, but if an intermediate result must be rounded to its scale before being used, the messages #roundedToScale and #truncatedToScale may be used."
The last sentence in the first paragraph is one I cannot agree with.
If I want a calculation where (1/7 * 7) gives me back 1, then I use
exact Fractions. If I am using ScaledDecimal, it is because I *want*
fixed point decimal numbers, with the properties appropriate to fixed
point decimal numbers.�� Wanting something that I could use to talk to
databases like MariaDB I found that I had to write my own FixedPoint
class, only to find that VW called ScaledDecimal FixedPoint.�� *Sigh*.
Unless and until the current ScaledDecimal is ripped out and buried
in an unmarked grave at a crossroad with a stake through its heart,
you may wish to add VW-compatibility methods
roundedToScale
���� |t|
���� t := 10 raisedToInteger: scale.
���� ^(numerator * t / denominator) rounded / t asScaledDecimal: scale
truncatedToScale
���� |t|
���� t := 10 raisedToInteger: scale.
���� ^(numerator * t / denominator) truncated / t asScaledDecimal: scale
and then use
�� aScaledDecimal roundedToScale = anotherOne roundedToScale
Note that the ANSI standard, which was agreed to by a whole bunch of
Smalltalk experts, says "A <scaledDecimal> converted to a <Fraction>
will be a fraction having the same numeric value but having an integer
numerator and a denominator which is ten raised to
the power of the <scaledDecimal>���s scale factor."
Try "(10.00s2 / 3) asFraction" in your Smalltalk, and if the
result has a denominator of 3, SOMEONE stuffed up.