PROGRAM cretas;
 {$nomain}

{ 
  File:[22,310]CRETAS.PAS
  Author: Phil Hannay 21-Jun-89  (patterned after CINTAS.PAS)

  Last Edit: 22-JUN-1989 13:43:08 

  History:

}

 {[a+,b+,l-,k+,r+] Pasmat }

%include pas$ext:general.typ;

%include pas$ext:slen.ext;
%include pas$ext:sclear.ext;
%include pas$ext:schconcat.ext;

  PROCEDURE Cretas(    rea: real;
                   VAR asc: PACKED ARRAY [lo..hi: integer] OF char;
                   VAR pos: integer;
                       place: integer);
    EXTERNAL;

{*USER*

CRETAS converts an input real number in REA to an output string 
of ASCII characters in ASC.  The real number is assumed to be decimal
(base 10).  ASC MUST be a valid type0 or type1 string.

POS specifies starting point and justification of the output
in the string ASC.  The string ASC will always be cleared before
insertion of the real number conversion, so any previous contents
of ASC will be destroyed.

If POS is greater than 0 (zero), then the 
ascii characters will be left justified,
starting at position POS.  Leading blanks will be inserted 
before POS if POS is not the beginning of the string.

If POS is 0, then the ascii characters will
be right justified, using the entire string.  
Leading blanks will be inserted as needed in front of the converted
number.

If POS is less than 0,
then the ascii characters will be right justified ending at -(POS).
Once again, leading blanks will be inserted as needed in front of the
converted number.

PLACE will indicate the number of places desired after the decimal
point.  It must be zero or greater.  If PLACE is zero, there will be
only a trailing decimal point.  PLACE can NEVER be negative.

If the real number is negative, a leading minus "-" sign will precede
the ascii digits.  There will be no space between the minus sign and
the first digit.

A decimal point will ALWAYS be present.  Likewise, if the number is
less that 1.0, a leading zero before the decimal point will ALWAYS
be present.

Upon exit from this
procedure, POS will be left pointing to the rightmost character placed in the
number string.  This is generally of value only if POS was greater
than zero, indicating left justification was desired, as it now
points to the rightmost character that was put in the string.

If POS is returned as zero, there was a conversion error.

Real numbers can vary from 1E-38 to 1E+38.  You can specify a PLACE
value larger than 38, however, it will simply return zeros in places
beyond 38.

Remember that single precision
real numbers (4 bytes) will give you about 7 digits of precision, 
while double precision (8 bytes) will give you about 15 digits.

*ERROR CODES*

If POS is returned as zero, there was a conversion 
error.  The most likely cause is that the resulting 
ascii represented real number would not fit in the 
string ASC provided.  In this case, the string has 
been cleared, but nothing inserted.  You as the 
caller can determine how to handle this case.  
Less likely conversion errors are due to programmer
error, either with POS or PLACE values.  These 
errors will also result in the below documented 
error messages appearing to aid in program
debugging.

The following error messages can appear when using
CRETAS.  They normally indicate a programming error.

CRETAS -- ASC string is not a type0 or type1 string
  ( The ASC string parameter supplied must be a
    type0 or type1 string. )

CRETAS -- PLACE value (n) cannot be negative
  (A negative PLACE parameter "n" (number of places
   after decimal point) was used.)

CRETAS -- POS value (n) is not within string'
  (The POS parameter "n" is in error as it
   does not fall between the lower and upper
   bounds of the string array.)

}

{*WIZARD*

This routine handles only type0 and type1 compatible strings.

}


  PROCEDURE Cretas;

    LABEL
      999; { used for premature exit on error }

    VAR
      bpos, epos, i, left_place, strlen, maxlen, digit: integer;
      holdreal, factor: real;
      done, negative: boolean; 

    BEGIN

      epos:= 0; 
      { if EPOS remains zero, error - in most cases - generated number
        would not fit in string ASC that was provided }
      
      { make sure ASC is type0 or type1 string - program error if not }
      if (lo < 0) or (lo > 1)
        then begin
          { error - write a quick diagnostic message to help programmer }
          writeln('CRETAS -- ASC string is not a type0 or type1 string');
          GOTO 999;
          END;

      { make sure PLACE is positive - program error if not }
      if (place < 0)
        then begin
          { error - write a quick diagnostic message to help programmer }
          writeln('CRETAS -- PLACE value (',place:1,') cannot be negative');
          GOTO 999;
          END;

      { check if positive or negative value - if negative, we will need
        to have room for a minus sign }
      if rea < 0.0 
        then begin
          holdreal:= -(rea);
          negative:= true;
          end
        else begin
          negative:= false;
          holdreal:= rea;
          end;

      { Figure out how many places to the left of the decimal point that
        we will need.  We will leave FACTOR as 1.0 or the largest power of 10
        that is less that the real number supplied. }
      factor:= 1.0;
      left_place:= 1;
      done:= false;
      while not(done) do
        begin
        if factor < holdreal
          then begin
            left_place:= left_place + 1;
            if left_place < 40
              then factor:= factor * 10.0 else done:= true;
            end
          else begin
            if factor > 1.0 
              then begin
                factor:= factor / 10.0;
                left_place:= left_place - 1;
                end;
            done:= true;
            end;
        end;

      { now compute number of characters needed for ascii number }
      strlen:= left_place + 1 + place;  
      {left digits + decimal point + right digits}
      if negative then strlen:= strlen + 1; 
      { minus sign if negative }
 
      { we clear the string to insure that it is empty and determine maximum
        size }
      sclear(asc);
      maxlen:= hi;

      { make sure POS falls within string }
      if (abs(pos) > maxlen) 
        then begin
          { error - write a quick diagnostic message to help programmer }
          writeln('CRETAS -- POS value (',pos:1,') is not within string');
          GOTO 999;
          END;

      { calculate where first non-blank character will be, and where last
        non-blank character will be }
      if pos = 0
        then begin
          { right justify, use entire string }
          bpos:= maxlen - strlen + 1;
          epos:= maxlen;
          end
        else begin
          if pos > 0
            then begin
              { left justify, ending at POS }
              bpos:= pos;
              epos:= pos + strlen - 1;
              end
            else begin
              { pos < 0 : right justify, starting at POS }
              bpos:= (abs(pos) - strlen + 1);
              epos:= abs(pos);
              end
          end;

      { now make sure that the calculated beginning and ending digits
        fit in string ASC that was supplied, if not, bailout, returning
        a zero in POS indicating we could not do coversion in space
        provided }
      if (bpos < 1) or (epos > maxlen) 
        then begin
          epos:= 0;
          goto 999;
          end;

      { finally, we are ready to generate ascii number }

      { pad with blanks if required }
      for i:= 1 to (bpos-1) do schconcat(asc,' ');

      { minus sign if negative }
      if negative then schconcat(asc,'-');

      { now digits before decimal point }

      { FACTOR was left as the largest power of 10 less than the
        real number.  If real number was less than 1.0, FACTOR was left
        at 1.0.  We can use it to generate the digits, reducing
        FACTOR by 10 until we are back to FACTOR less than 1.0.  Note
        that if the real number was less than 1.0, FACTOR will be 1.0,
        and we will generate a single zero digit before the decimal
        point. }
      while factor >= 1.0 do
        begin
        digit:= trunc(holdreal/factor);
        schconcat(asc,chr(digit+ord('0')));
        holdreal:= holdreal - (digit * factor);
        factor:= factor / 10.0;
        end;           

      { add the decimal point }
      schconcat(asc,'.');

      { and the digits after the decimal point - FACTOR is 0.1, ready to
        go for generating digits - if factor reaches less than 10 to the
        minus 38th, we have reached the lower limit of the real number,
        and will just generate zeros thereafter. }
      for i:= 1 to place do
        begin
        digit:= trunc(holdreal/factor);
        schconcat(asc,chr(digit+ord('0')));
        if factor <= 10E-38
          then begin
            { no more resolution possible - just do zeros }
            holdreal:= 0;
            end
          else begin
            { reduce hold real and drop FACTOR by 1 power }
            holdreal:= holdreal - (digit * factor);
            factor:= factor / 10.0;
            end;
        end;

    999:
    { Done - ending position is EPOS.  If EPOS is still zero, we could
      not fit ascii number into the ASC string that was supplied.  And
      so we leave the string cleared (empty),  leaving it up to the 
      caller to figure out what to do. }
    Pos:= epos;
      
    END;
