- THE PL/I NEWSLETTER -
|- THE PL/I NEWSLETTER -|
- THE PL/I NEWSLETTER -
The eighth issue may be downloaded from:
The PL/I Newsletter, No. 8, January 2005.
The seventh issue may be downloaded from: The PL/I Newsletter, No. 7, December 2004.
The sixth issue may be downloaded from: The PL/I Newsletter, No. 6, December 2003.
The fifth issue may be downloaded from: The PL/I Newsletter, No. 5, August 2002.
The fourth issue may be downloaded from: The PL/I Newsletter, No. 4, November 2001.
The third issue may be downloaded from: The PL/I Newsletter, No. 3, June 2001.
The second issue may be downloaded from: The PL/I Newsletter, No. 2, September 2000.
The first issue may be downloaded from: The PL/I Newsletter, No. 1, July 2000.
A recent case arose where the calling program contained
calls of the form
CALL SUB (S, T, U);
CALL SUB (S, '', U);
where S, T, and U were CHARACTER strings, and
SUB was an external procedure defined: DECLARE SUB EXTERNAL;
The purpose of the two CALL statements was to provide a null argument in certain cases where a different action was required in procedure SUB.
How did this code crash?
Subroutine SUB contained an assignment to the second parameter:
This aspect of EXTERNAL procedures seems to have been given little or no attention in early PL/I texts, nor has it been covered in Language Reference manuals.
Sometimes it is necessary to count the number of items
in a line of text, s.
Given that the items are separated by one or more blanks, the following simple statement will suffice:
Number = 1 + TALLY ( TRIM(s), 'b') - TALLY ( TRIM(s), 'bb');
(where b represents a blank)
If s is likely to be blank or null, it will be necessary to modify this to:
Number = (LENGTH(TRIM(s)) > 1) + TALLY ( TRIM(s), 'b') - TALLY ( TRIM(s), 'bb');
Two of the great names in numerical methods - James Wilkinson of the National Physical Laboratory, U.K., and C. Reinsch of the Mathematical Institute of TH, Munchen, Germany - combined to assemble a masterful suite of numerical procedures in Handbook for Automatic Computation, Volume II, Linear Algebra, published by Springer-Verlag.
The algorithms cover linear equations, least squares, linear
programming, and eigenvalue and eigenvector problems.
Note: in some of the following procedures, the original comment documentation and description of the parameters - which were missing - have been restored. As well, the original procedure names have been restored. The routines have not been re-tested.
Most of these routines have active links; the remainder will be added as soon as they have been converted.
PL/I offers a richer variety of basic data types than most languages. Consider just computational data. C has the floating-point types float, double, long double, integer types short [int] and long [int], and possibly char can be considered a computational data type.
PL/I computational data has base, scale, mode, and precision. Scale can be fixed or float, roughly corresponding to C. Mode can be real or complex; C has no complex data. Base can be binary or decimal; decimal data is unknown in C. Precision is the attribute that provides the most power, but also some degree of confusion. In place of C's short and long, for example, PL/I allows the programmer to specify the precision, the number of bits(binary), or digits(decimal) that are required to store the data, although the hardware can use more if appropriate. For example, a machine with a word size of 32 bits can store a fixed binary value up to FIXED BIN(31) [one bit is reserved for the sign and is not counted].
An often-unnoticed appendage to precision called the scale factor sometimes causes confusion. For fixed data precision is specified as a pair of numbers ( number-of-digits [, scale-factor ] ), where the scale-factor is assumed to be zero if not specified. Float data has no scale-factor, since by definition floating-point data includes its own scale. So far, so good. A number representing the number of items in inventory might be declared FIXED DECIMAL(8), or alternatively FIXED DECIMAL(8,0), which provides storage to handle quantities up to 99,999,999. The count must obviously be a whole number of items, and so the ",0". A number representing a bank balance might be declared FIXED DECIMAL(10,2), which would reserve storage to handle balances up to $99,999,999.99, dollars and cents. Non-US readers feel free to read this using your own currency conventions, PL/I supports them all.
The scale factor can cause some confusion when performing division as part of an arithmetic
expression. C INT data is, as the name indicates, always integer. The compiler assures
that expressions of type INT will always give integer results: in C 5/3=1. In PL/I, the scale factor is
always taken into account, even when it is zero. For addition, subtraction, and multiplication, if
all the operands have a scale factor of zero, the result will too. For division, however, PL/I always
tries to preserve the maximum possible significance of a fractional quotient. Many books on PL/I
fail to point out this fact. In practical terms, the compiler converts the dividend
(the number being divided) to a temporary value which has just enough digits to hold its declared
value, followed by zeroes to pad out the maximum possible length of that data type. The definition
from the standard is:
|m = N|
|n = N-p+q-s|
This conversion is defined by the language, not the implementation. The only possible differences among machines might be in the maximum sizes of data which can be handled (N). Here are a couple of examples for IBM S/390 architecture:
FIXED BINARY(15,0) divided by FIXED BINARY(15,0): The temporary result is FIXED BINARY(31,15). That's right, the quotient will have fifteen bits of fractional result.
FIXED DECIMAL(5,0) divided by FIXED DECIMAL(5,2): The temporary result is FIXED DECIMAL(15,8).
Most PL/I "Language Reference" or similar manuals will have a table providing formulas for results of expressions involving different types of operands like "Table 16: Results of Arithmetic Operations for Coded Arithmetic Operands" in IBM's SC26-3114.
Having gotten to this point, a natural question is "so what?" In many cases this may not matter.
For example 5/3 evaluates to 1.666.... Assigning this result to a FIXED variable with a scale factor of
zero will truncate to an integer (1), exactly as in C. Assigning the result to a FLOAT variable,
will result in the expected 1.666... to the limits of precision. Try this in C; the result will
be 1.00000..., because the expression 5/3 involves integers and evaluates to an INT before
assignment. To achieve the same result, C has to resort to casting, an art so arcane that I
had to try three times to get it correct for my test. A C cast in this case is a method of
forcing the conversion
of data during expression evaluation. C has to use a cast to force the expression
5/3 to be evaluated as FLOAT. Guess which one of these is correct C:
f = (float)(5/3); f = (float)5/3; or f = (float)(5)/3;
The other situation in which you have to be aware of what PL/I is doing is when the division is part of a larger expression. Compare the statement i = (5/3) * 3 in PL/I and C. C divides five by three and gets one (integer division), then multiplies one by three and gets the result "three". PL/I divides five by three and gets 1.6666... (scaled integer division), it then multiplies 3.00000 (three scaled to the same number of fractional digits as the result of the division) and gets 4.999.... Assigning this result to i truncates to an integer "four". This may or may not be the desired result, but in any case you'd best be aware of what's happening. Another unobvious point is that PL/I will use decimal operations, since the constants "5" and "3" are decimal constants, the exact equivalent of the C expression would be (101B/11B) * 11B, or (BINARY(5)/BINARY(3))*BINARY(3).
PL/I provides a way for the programmer to exercise further control over division by way of the DIVIDE built-in function. The syntax is DIVIDE( dividend, divisor, precision [, scale] ). Divisor and dividend are self-explanatory. Here "precision" is the required precision of the result of the division, and "scale" is the required scale factor. As usual with PL/I built-in functions, the base, scale, and mode of the result are determined by the attributes of the dividend and the divisor. If the result is FLOAT the scale must be omitted. If the result is FIXED and the scale is omitted, zero is assumed.
The result of DIVIDE(5,3,15) [or DIVIDE(5,3,15,0)] will have a no fractional digits; an integer result of "one", or the same as C's (5/3). To obtain the same result as the C expression (5/3) * 3, code: DIVIDE(5,3,15) * 3
The spring 2000 edition of the
COBOL and PL/I newsletter
has some topics about PL/I. The specific PL/I
topics may be viewed separately, or the entire newsletter in PDF
format may be downloaded.
Of particular interest is the use of PL/I as a programming language for the Common Gateway Interface (CGI).
The PL/I Connection newsletter.
The PL/I Connection Newsletter No. 11 , December 1997.
The PL/I Connection Newsletter No. 10 , April 1997.
IBM enhanced VisualAge® PL/I with powerful features to increase development productivity, simplify the maintenance of your legacy code, and provide seamless portability from your host to your workstation. It also provides the tools needed to identify your Year 2000 date-related fields on both OS/2 and Windows NT.
VisualAge PL/I Enterprise Edition Version 2.1 combines the two separate offerings from the previous release of VisualAge PL/I (Standard and Professional) into a single offering available on both the OS/2 and Windows NT platforms. By doing so, it provides these productivity features:
Also included as an extra bonus offering is VisualAge CICS® Enterprise Application Development, which enables CICS host application development on the workstation.
Built-in functions are generic, and typically operate on any data type and
for an array of any number of dimensions (up to the maximum permitted
by the compiler).
If a user attempts to write a generic function, it is necessary to provide the procedures to perform the desired operation on each of the data types that he wants.
As well, if the argument(s) can be arrays, the user must also provide procedures to perform the desired operation on arrays of any number of dimensions. Each procedure dealing with an array must also be duplicated to deal with each of the different types that the user wants.
Thus, if there are four data types (integer, decimal, float, float (16)), and the compiler supports arrays having 10 dimensions, then 40 procedures must be provided.
It would be better to have a new attribute GENERIC_ALL which specifies that any procedure in the family is assumed to apply to scalars and arrays of any dimension. e.g.,