======= LSN013.Decimal =======
Here is the recent e-mail on Decimal and Ada 9X.
It corresponds pretty closely to what is in MD version 3.0.
However, here we provide more of the rationale.
-Tuck
==================================
>From stt@inmet.inmet.com Sat Jun 1 07:53:00 1991
id AA19059; Sat, 1 Jun 91 07:52:27 -0400
From: stt@inmet.inmet.com (Tucker Taft)
To: ada9x-drrt@ajpo.sei.cmu.edu, isrg@d74sun.mitre.org
Subject: Decimal and Ada 9X
!topic Decimal for Ada 9X
!from Tucker Taft 91-06-01
!discussion
Here is yet another proposal for the support of decimal
in Ada 9X:
Add to package Standard:
type Decimal(Precision : Positive; Scale : Integer) is private;
-- Precision is total number of decimal digits
-- Scale is number of digits after decimal point
-- Negative scale is used for subtypes accurate only to "thousands", etc.
function "+"(Right : Decimal) return Decimal;
function "-"(Right : Decimal) return Decimal;
function "+"(Left, Right : Decimal) return Decimal;
function "-"(Left, Right : Decimal) return Decimal;
function "*"(Left : Decimal; Right : any-integer) return Decimal;
function "*"(Left : any-integer; Right : Decimal) return Decimal;
function "/"(Left : Decimal; Right : any-integer) return Decimal;
function "="(Left, Right : Decimal) return Boolean;
function "/="(Left, Right : Decimal) return Boolean;
function "<"(Left, Right : Decimal) return Boolean;
function "<="(Left, Right : Decimal) return Boolean;
function ">"(Left, Right : Decimal) return Boolean;
function ">="(Left, Right : Decimal) return Boolean;
The Scale of the result of "+" and "-" is the max of the Scale
of the operands. The other arithmetic operations have the Scale of the
Decimal operand as the Scale of the result.
The Precision of the result is sufficient to represent the result,
or CONSTRAINT_ERROR is raised.
On assignment or explicit conversion to a constrained subtype,
the Scale of the target may be greater or less, with truncation
toward zero if less.
CONSTRAINT_ERROR is raised if the Precision of the target subtype
cannot represent the result.
Both universal integer and universal real are implicitly convertible
to Decimal. The Scale and Precision of a universal real is determined by
representing the value in decimal, and ignoring leading and trailing zeros.
If the decimal representation is non-terminating, then the Precision
is considered to equal INTEGER'LAST, which will cause a CONSTRAINT_ERROR
unless immediately assigned or explicitly converted.
Decimal types are explicitly convertible to and from other numeric
types, with truncation toward zero on conversion to a decimal subtype
with less scale than the original value.
Decimal types may be passed by reference. The Scale and Precision
of the actual must match the formal for OUT and IN OUT parameters
if the formal is of a constrained subtype. For IN parameters,
an implicit subtype conversion is performed on the actual
parameter if the formal is constrained, with truncation toward
zero if necessary.
In the I.S. annex, a requirement will be specified that Precision
of at least 18 must be supported, and Scale of +/- 18.
The following additional package is provided (at least in the I.S.
annex):
package Decimal_Operations is
function Round(
Value : Decimal'CLASS;
Scale : Integer) return Decimal'CLASS;
function Multiply(
Left, Right : Decimal'CLASS;
Scale : Integer) return Decimal'CLASS;
function Multiply_Rounded(
Left, Right : Decimal'CLASS;
Scale : Integer) return Decimal'CLASS;
function Divide(
Left, Right : Decimal'CLASS;
Scale : Integer) return Decimal'CLASS;
function Divide_Rounded(
Left, Right : Decimal'CLASS;
Scale : Integer) return Decimal'CLASS;
procedure Divide_With_Remainder(
Left, Right : Decimal'CLASS;
Quotient : OUT Decimal'CLASS;
Remainder : OUT Decimal'CLASS);
end Decimal_Operations;
These operations are defined for the universal decimal type
(Decimal'CLASS) so that they may be used with any derivatives
of decimal.
Round rounds its input value to the given scale,
using the algorithm:
Result := Truncate(Value + 0.5*Sign(Value)*10**(-Scale), Scale);
In English, it adds 0.5 shifted to the specified scale and of
the sign of the input value, and then truncates to the specified scale.
The Multiply and Divide operations calculate the result to the specified
scale, with truncation toward zero if necessary.
The Multiply_Rounded and Divide_Rounded operations calculate the
result to the specified scale, with rounding as described above
if necessary.
The Divide_With_Remainder procedure calculates the quotient
to the scale of the Quotient OUT parameter, with truncation toward
zero, and places in Remainder the value: Left - Right * Quotient,
truncated if necessary toward zero.
Note that using Ada 9X discriminant replacement,
types with a constrained Scale but an unconstrained
Precision can be defined as follows:
type Batting_Average(Precision : Positive) is new Decimal(Precision, 3);
-- Accurate to 0.001
type Grand(Precision : Positive) is new Decimal(Precision, -3);
-- Accurate to 1000s
type Dollars(Precision : Positive) is new Decimal(Precision, 2);
-- Dollars and Cents
RATIONALE
One apparently obvious way to support decimal is using
Ada fixed point. However, there are a number of problems
with fixed point as a representation of decimal:
1) Fixed point is a "real" type, with approximations inherent
in its semantics. Decimal must be an "exact" type, with no
approximations allowed. In this sense it is more like Integers
than Fixed. Note that in SQL, Decimal is grouped with Integer
under the general category of Exact numbers, whereas floating
point is under the category of Approximate numbers.
2) Universal real is defined at run-time to use at least the
longest floating point type. In many cases, this will not provide
enough precision to represent 18 decimal digits. On the other
hand, the longest decimal representation could not represent
the full exponent range of floating point types. Therefore, there
would be no appropriate run-time representation for universal
real if decimal types were included among the real types.
3) Fixed point types are required to be passed by copy.
For many decimal representations, it will be preferable
to pass by reference when possible.
4) Distinct decimal fixed-point types would have to be declared
for distinct scales/deltas or base-type precisions/ranges.
This means that more explicit type declarations, explicit
type conversions, and explicit generic instantiations will
be necessary to use decimal numbers.
5) Writing a generic with a fixed-point formal type parameter
is already pretty difficult. A common trick is to immediately
convert the fixed-point values to the longest floating point type,
and then do the work in floating point, converting back on return.
These tricks won't work if the actual type might be a decimal type.
6) Implementing a generic using universal sharing will fail miserably
if fixed point types cannot be represented with a single longest
real type. (I don't see this as a major problem, though I know
some Ada vendors who might!).
7) A pragma may be necessary to specify the use of binary vs.
packed decimal representations if both decimal and normal fixed
point are fixed point types. If decimal types are distinct,
then fixed-point can always use binary, and decimal can always
use packed-decimal (or whatever is the most common decimal representation
in the prevailing COBOL compiler or SQL database).
8) etc...
Probably any one of the reasons can be worked around (except perhaps
"etc..."!), but together they make a pretty strong argument for having
a separate class of types for decimal numbers.
So what should this type look like? Ideally, our decimal type would:
a) be convenient to use, with rounding only when explicitly requested
b) allow subtypes to vary by both precision and scale
c) conform with the style of declarations used in COBOL, PL/I, and/or SQL.
d) not require new syntax or reserved words
e) have numeric literals
f) be explicitly convertible to other numeric types
g) allow pass by reference
h) be efficiently implementable for I.S.-oriented compilers
i) not require a significant implementation burden for
compilers which are not intended for I.S. applications
j) etc...
We believe that this proposal accomplishes most of these goals
(except for that darn "etc..."). By putting Decimal in Standard,
it should be easier for I.S.-oriented compilers to special-case them.
By making them at least look like regular discriminated private
types, a non-I.S.-oriented compiler should be able to implement
them as such relatively easily (and inefficiently, perhaps).
The precision/scale pair of discriminants corresponds exactly to
the pair used in PL/I and SQL for specifying such types. It also
relates to the old familiar Fortran format statement (e.g. F9.2),
and has an obvious correspondence to the COBOL picture clause.
As usual, comments welcome...
-Tuck
============ This was a followup message =============
>From stt@inmet.inmet.com Thu Jun 6 15:10:56 1991
id AA12514; Thu, 6 Jun 91 15:10:56 -0400
From: stt@inmet.inmet.com (Tucker Taft)
To: ada9x-drrt@ajpo.sei.cmu.edu, isrg@d74sun.mitre.org
Subject: Re: Decimal and Ada 9X
I have looked over Ken Fussichen's COBOL examples
which use Decimal and have concluded a couple of things:
1) The examples make no use of multiply or divide.
Therefore not providing a direct "decimal * decimal"
or "decimal / decimal" operator in Standard seems OK, and
allows us to avoid trying to guess what is the correct resulting Scale.
I presume "decimal * int", "int * decimal", and "decimal / int"
are worth providing, and that the Multiply/Divide routines in the
separate package which have an explicit Scale parameter are adequate.
2) They use both COMP-3 (packed decimal) and the default representation
(which is unpacked on IBM). A vendor would either have to
recognize a representation clause/pragma to make this distinction,
or (my preference), provide separate declarations of Unpacked_Decimal
and other representations in a separate package with appropriate
conversions, for use in records destined for I/O devices.
3) Most of the code just moves things around. If one wanted to
make a separately declared Unpacked_Decimal type more useful,
one could provide a few operations, such as Move/Assign which
does extension or truncation, plus perhaps an Add and Subtract
procedure. Much more than that for Unpacked_Decimal seems unnecessary,
presuming conversions to and from Standard.Decimal are provided.
Most decimal hardware only supports arithmetic on packed, plus
conversions to and from unpacked, so it seems consistent to include
in Standard only the hardware-supported type.
4) A decimal object declaration doubles as a space specification for
a record component. This seems to make the Precision/digits-style
specification for magnitude preferable to a "range" style.
Furthermore, careful IBM COBOL programmers use only an odd number
for their total number of packed decimal digits, since only odd digit
counts allow efficient hardware overflow detection. Both of these
considerations imply that it is preferable to specify the total
number of digits, rather than simply the number to the left of
the decimal place. Further it implies that the COBOL programmer's model
is an array of decimal digits, rather than an abstract numeric value with
some maximum range.
The bottom line is that, based on these few COBOL examples, the
Decimal(Precision, Scale) style of declaration seems appropriate.
It would be useful to have a standardized package with Unpacked_Decimal,
and perhaps other less common formats (e.g. Unsigned_Packed),
with appropriate conversions.
-Tuck