In a continuing effort to move towards standards compliance, the Smalltalk team at GemStone has recently been looking at ScaledDecimal.

## Backwards Compatibility

GemStone/S has a class named ScaledDecimal and the current implementation, while not ANSI-compliant, is useful and will likely remain but with a new name (say, ScaledFraction). Instances of the legacy class could be created using

or by sending** Number>>#'asScaledFraction:'** ` `

to the legacy class.**#'fromString:'**

The remaining discussion concerns the new implementation of an ANSI-compliant scaledDecimal (or a mostly-compliant implementation; see the questions at the end).

## Defined Protocols

According to ANSI Smalltalk, the scaledDecimal protocol provides “a numeric representation of fixed point decimal numbers.” The protocol defines only one message,` `

, that answers an ‘integer which represents the total number of digits used to represent the fraction part of the receiver, including trailing zeros.” This implies that the value of any fractional digits beyond the scale is zero (since there is no representation for those digits). That is, multiplying any scaledDecimal by its scale would produce a number in which the fractional part is always exactly zero.**#'scale'**

A scaledDecimal can be created by explicitly defining a numeric literal using the format` `

(e.g.,**scaledMantissa 's' [fractionalDigits]** ` `

) where the resulting scaledDecimal has a scale of fractionalDigits (here 4 even though only two digits were provided to the right of the decimal). ANSI specifies that it is an error for the scaledMantissa to provide more digits to the right of the decimal than fractionalDigits. If the optional fractionalDigits are not present then the scale is the number of digits to the right of the decimal in scaledMantissa.**123.45s4**

A scaledDecimal can also be created by sending the message` #`

to a number and providing ‘a fractional precision’ as an argument. The fractional precision specified might be less than the count of digits to the right of the decimal point in a decimal representation of the number (which could be infinite in the case of a fraction such as one-third), in which case the result will be rounded. ANSI does not specify the rounding rule here but does provide one in the definition for**'asScaledDecimal:'** ` `

.**number>>#'rounded'**

ANSI specifies a refinement of` `

when the receiver is an integer. No mater what value is provided as the argument, ANSI states that the resulting scaledDecimal has a scale of zero. The usefulness of this is questionable and it would result in inconsistent results if one were, say, printing an expression (e.g.,**#****'asScaledDecimal:'** ` `

). Even VA Smalltalk (which is the most conforming I’ve found) does not follow this.**(x integerPart / 2) asScaledDecimal: 2**

## Scale for Result

Many messages defined in the number protocol can return a scaledDecimal, including` `

. In most of these cases, ANSI specifies that the scale of the result is at least the scale of the receiver. Thus, the actual scale is largely implementation defined. There are a few options:***, +, -, /, \\, integerPart, quo:, reciprocal, rem:, and roundTo:**

- Select the receiver’s scale. This allows the receiver to control the outcome, which is at least an easy rule and follows the OO paradigm of messages rather than operators but is inconsistent with much of the numeric protocol where the returned value is based on the receiver and argument, not just the receiver (an integer plus a fraction returns a fraction).
- Select the smaller scale (if allowed). On something like addition this would be a way to avoid implying that the result is more accurate than it actually is. For example,
**0.33s2 + 0.0001s4**

since the receiver, by definition, is ignoring anything more than two digits to the right of the decimal. Note that ANSI specifies that the result will have a scale of at least that of the receiver, so switching the order of the addition would change the result, leading to potential surprises, particularly when dealing with variables with passed-in or computed values.**0.33s2** - Select the larger scale.
- Vary the selection based on the operator. For example,
**0.1s * 0.2s****0.0s1****0.02s2**

Whatever rule was provided by default, the programmer could work around it by changing the scale of the receiver, the argument, or the result. Thus, the goal should be to identify the typical use or expected behavior to reduce the typing required and the surprise.

## Some Other Dialects

Since GemStone has libraries for a couple other Smalltalk dialects, it seems valuable to compare them.

**VisualWorks** 7.6 (from Cincom Smalltalk) seems to be the most distant from ANSI. The numeric literal with ‘s’ results in an instance of FixedPoint that is implemented much like GemStone’s legacy ScaledDecimal. That is, the internal representation is a Fraction and the scale is available to provide support for printing and explicit rounding. VW 7.6 does not support` `

but does have**#'asScaledDecimal:'** ` `

. Because scale does not actually mean the count of digits used to represent the fractional precision, the scale can be changed at any time without any impact on the precision. When a scaledDecimal is the result of a message to a number, the resulting scale is typically the maximum of the receiver’s and argument’s scale.**#'asFixedPoint:'**

**VA Smalltalk** 7.5 (from Instantiations) seems to have the most compliant implementation. All scaledDecimals are represented internally as binary-coded-decimal in 18-bytes with up to 31 digits of precision and a maximum scale of 30. This seems to be quite close to the typical Cobol implementation, right down to the sign byte of C or D (credit/debit!). The scale actually identifies the count of digits for the internal representation. When a scaledDecimal is the result of a message to a number, the resulting scale is adjusted based on the message. For example,` `

. That is, truncation/rounding is avoided whenever possible (up to the maximum precision/scale).**0.1s1 * 0.2s1 = 0.02s2**

## Notes

An implementation based on an integer with a decimal power of ten scale could be faster than the current fraction-based approach since some normalization could be avoided.

With ScaledFraction intermediate values are infinite precision and rounding/truncating is done when explicitly requested. One also could have a conforming ScaledDecimal implementation with minimal intermediate rounding/truncating (see, e.g., VA Smalltalk), though it seems that some limit (other than positive infinity) needs to be placed on the scale.

The fact that the fraction-based approach is useful does not mean that an implementation following ANSI would be of no value. The fact that no one is using it might be due to the fact that it doesn’t exist.

## Questions:

- How much disruption would be caused by moving pre-existing objects and behavior to a differently-named class? The impact would be on code with ‘s’ numeric literals, senders of
**#'asScaledDecimal:'** - Should specifying more than fractionalDigits digits to the right of the scaledMantissa’s decimal (e.g.,
**123.456s2** - When creating a scaledDecimal from a number using

what rounding rule should be used when the value is exactly half way between two allowed results? Round away from zero? Round to an even least significant digit?**#'asScaledDecimal:'** - When
**#'asScaledDecimal:'** - What rounding, if any, should take place on intermediate values? More specifically, when a scaledDecimal is returned from messages like multiplication in the number protocol, what should be the scale of the result?
- What maximum limit, if any, should be placed on scale? If one tries to avoid rounding intermediate results, then some limit is probably necessary. Would it be important to try to keep things in the SmallInteger range? GemStone has a limit on LargeIntegers of several thousand digits. It would seem reasonable to avoid spilling over to that accidentally.
- What name would be appropriate for the legacy implementation? Would following VW’s (mis-named) FixedPoint increase or reduce confusion? Would FractionWithScale be more descriptive?

Comments may be added here or sent to the GemStone user’s mailing list.

## 2 comments

Comments feed for this article

December 17, 2009 at 4:55 am

Smalltalk Vendors Ask for Feedback « Joachims Small World[…] Gemstone is out for some very detailed feedback on potential changes they’d like to incorporate into their ScaledDecimal base class: …Comments may be added here or sent to the GemStone user’s mailing […]

December 17, 2009 at 6:03 pm

Steve ReesMy reading of ScaledDecimal is that it is conceptually very similar to the BigDecimal class in Java (sorry). In other words, it is effectively a fraction where the denominator is 10 to the power of the scale and the numerator is an integer.

Interpreting in this fashion makes 1.234s2 nonsensical as this would represent 123.4/100. Clearly one could round this to 123/100, but why allow such a literal in the first place? Isn’t it better to report an error when the user attempts to compile this? I certainly think so.

Per the suggestion in the ANSI standard an unbounded precision is recommended. For example, a LargeInteger numerator and an Integer scale would fit the bill (this is also similar to what Java does).

My interpretation of the intent of the asScaledDecimal: message, (an interpretation that is certainly not explicitly stated in the ANSI docs) is that it provides an upper bound for the number of decimal places in the result.

I looked at the language in the definition of asScaledDecimal: on the protocol – “Answer a scaled decimal number, with a fractional precision of scale, which minimizes the difference between the answered value and the receiver”. The only thing this leaves open to the implementation is how to treat half values. Round half even seems a reasonable choice.

Regarding the application of asScaledDecimal: to integer values, my opinion is that for positive scale values it shouldn’t matter to client code that uses the result whether or not the scale of the result matches the scale argument. Using a value of zero actually helps reduce the storage requirement for the numerator as it does not have to be multiplied by 10 to the power of the scale. Clearly it would also be faster for the same reason.

Perhaps, though, the reasoning is more targeted at negative scale values. I can’t see anything in the spec that disallows them. The description of a scaled decimal literal explicitly refers to numbers to the right of the decimal point when discussing the fraction digits portion, but then a scaled decimal literal with negative scale would be a rather pointless and obscure thing.

So for integers asScaledDecimal: -3 would return a result with scale of zero, effectively zeroing the last three digits before the (notional) decimal point.

Imposing a limit on the scale that is consistent with the maximum precision of LargeInteger seems entirely reasonable.

For mathematical operations, I think the results should be exact, where possible. ScaledDecimal is an exact representation, unlike floats. It may well be the result of rounding applied to a prior result, but once rounded the value is exact. I can’t see any reason for discarding necessary precision of the result of multiplcation, or addition, for example. eg. 1.1s1 * 1.1s1 = 1.21s2. OTOH 1.1s1 * 1.10s2 could also be validly represented by 1.21s2. I think it would be wrong to round the first result to 1.2s1 as that would lose necessary precision in the result.

Division is more problematic. I would have preferred to see the standard allow a result for division by a ScaledDecimal, since this would allow for an exact result, however the standard doesn’t seem to allow that. So, where the result cannot be exactly represented by a ScaledDecimal rounding to a precision that is the sum of the scales of the two operand values would be the next best thing.

BTW Strongtalk doesn’t even have any kind of decimal representation – scaled, fixed or otherwise. Another thing to add to the list. Sigh!