======= 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