ISSUE No. 9 – APRIL 2006

Copyright © 2006 by Robin Vowels. All rights reserved for the authors.
  1. Editorial
  2. EXTERNAL PROCEDURES, by Robin Vowels
  3. Programming Tips - Counting
  4. Numerical Methods
  5. PL/I Division De-mystified
  6. Check this out
  7. Usefulle Webbe Lynx ...
  8. IBM PL/I Offerings in the U.S.
  9. Language Suggestions

  1. Editorial

    We welcome your contributions to this newsletter. It will continue only through your efforts. Thank you in advance.
    Please send any comments and contributions for the next newsletter to
    r|o|b|i|n|5|1 at dodo dot com dot au

    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.


    by Robin Vowels
    PL/I permits external calls. Old codes may do this often, as early machines were short on memory.
    To call an external procedure, the minimum was to include the declaration
    and to let PL/I do the rest, namely, to prepare the arguments for the procedure call or function reference, derived from the attribues of the actual arguments.
    It is important to note that PL/I performs no conversion for any arguments for this type of call. It is essential that the programmer always provides arguments of the correct type (to match the corresponding parameters).
    What happens in the case of arguments that are constants?
    PL/I prepares a temporary argument. The attributes of the temporary exactly match those of the argument.
    Thus, CALL S (8);
    produces an temporary argument having attributes FIXED DECIMAL (1),
    and CALL S (789);
    produces a temporary argument having attributes FIXED DECIMAL (3);
    If both these calls were in the same program, at least one of them is wrong because the arguments have different attributes.
    To avoid these kinds of problems, it is important to include (in the calling procedure) a complete entry declaration for the external procedure, thus:
    instead of the lazy short form DECLARE S EXTERNAL;

    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:

    SUB: PROCEDURE (A, B, C); DECLARE (A, B, C) CHARACTER (5); ... B = A; END SUB; At first glance, it would seem that the code was OK.
    What had gone wrong?
    The first call to SUB went OK.
    The second call to SUB crashed.
    In the first CALL, no temporary argments were created.
    In the second CALL, the second argument was a null string constant.
    PL/I produced a temporary for it.
    Q - What were the attributes of the temporary?
    A - They were CHARACTER (0).
    Q - What were the attributes of the second parameter?
    A - They were CHARACTER (5).
    In the subroutine, the value assigned to parameter B would be stored where this temporary was.
    The attributes did not match (no storage was provided for the temporary).
    Therefore storage was corrupted.
    Q - How could this programming error have been avoided?
    A - By including the declaration:
    Q - Was there another way to correct the programming error?
    A - Yes; change the declaration of B to CHAR(*).
    The compiler uses the length of the argument when performing the assignment to B; since that length is zero, no move is performed (this suggestion and explanation were provided by Peter Elderon).
    However, although this "fixes" the problem, there is still the potential for error in the future, because of the absence of a proper ENTRY declaration containing the attributes of the subrooutine.

    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.

  3. Programming Tips - Counting

    by Robin Vowels

    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');

  4. Numerical Methods

    by Robin Vowels

    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.

  5. PL/I Division De-mystified

    Peter Flass

    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
    Here "m" is the precision of the temporary result, "N" is the maximum number of bits or digits allowed for the data type, "p" is the number of bits or digits in the original value of the dividend, "q", and "s" are the scale factors of the dividend and the divisor.

    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

  6. Check this out

    A program that calculates the precision of a result of an arithmetic operation (an add, subtract, multiply, divide, or exponentiation) for non-float operands.
    Type in the attributes of each operand, and the program prints the attributes of the result.
  7. Usefulle Webbe Lynx ...

    Peter Flass is building a resource at: Peter Flass's PL/I home page.

    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.

  8. Current IBM PL/I offerings in the U.S.

    In 2005, IBM released Websphere PL/I for z/OS, AIX, and Windows. This has all the features of the Enterprise and VisualAge compilers.

    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:

    • remote edit/compile
    • redevelopment tools
    • cross platform development
    • year 2000 assessment strategies, find and fix, and test capabilities

    Also included as an extra bonus offering is VisualAge CICS® Enterprise Application Development, which enables CICS host application development on the workstation.

    • Product number 04L6564: VisualAge PL/1 Enterprise V2.1
    • 04L6565: VisualAge PL/I Enterprise V2.1 Upgrade from Prior IBM PL/I Workstation or Competitive Products Program Package CD-ROM
    • 04L6567: VisualAge PL/I Enterprise V2.1 Upgrade from Professional V2.0 Program Package CD-ROM
    • 04L6566: VisualAge PL/I Enterprise V2.1 Upgrade from Standard V2.0 Package CD-ROM

  9. Language Suggestions

    by Robin Vowels

    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.,

    Such a facility would require the user to write just four procedures (instead of forty in the above example).

Any comments and contributions for the next newsletter to
r|o|b|i|n|5|1 at dodo dot com dot au
(Team PL/I) please.