(*********************************************************************)
(*                                                                   *)
(* Module ExNumbers Copyright © 1995 by Computer Inspirations        *)
(*                                                                   *)
(* Design : Michael Griebling                                        *)
(* Change : Original                                                 *)
(*                                                                   *)
(*********************************************************************)

MODULE ExNumbers;

IMPORT io, Cnv := Conversions, S := Strings;

CONST
  MaxExp * = 10000;
  MinExp * = -MaxExp;
  HighBoundsManArray * = 52; (* max possible digits--must be multiple of 4. *)

TYPE
  ExStatusType * = INTEGER;

CONST
  (* ExStatusType values *)
  Okay              *= 0;
  Overflow          *= 1;
  Underflow         *= 2;
  DivideByZero      *= 3;
  TooFewDigits      *= 4;
  TooManyDigits     *= 5;
  IllegalNumber     *= 6;
  UndefinedStorage  *= 7;
  IllegalOperator   *= 8;
  MismatchBraces    *= 9;

TYPE
  ExCompareType = INTEGER;

CONST
  (* ExCompareType values *)
  ExLess    *= 0;
  ExEqual   *= 1;
  ExGreater *= 2;

TYPE
  SignType = SHORTINT;

CONST
  (* SignType values *)
  positive *= 0;
  negative *= 1;

TYPE
  ManType   * = ARRAY (HighBoundsManArray DIV 4)+2 OF INTEGER;
  ExNumType * = RECORD
                  Man  -: ManType;
                  Sign -: SignType;
                  Zero -: BOOLEAN;
                  Exp  -: INTEGER;
                END;

VAR
  ExStatus * : ExStatusType;

  (* Useful constants *)
  e-, ln2-, ln10-, pi-, Ex0-, Ex1-: ExNumType;


CONST
  MaxLengthNumber = 2 * HighBoundsManArray;
  Dec = 10;

VAR
  MaxDigits, MaxQuads : INTEGER;


PROCEDURE SetMaxDigits *(D : INTEGER);
(* Set maximum digits in extended real numbers -- must be
   a multiple of 4 *)
BEGIN
  IF D < 4 THEN
    MaxDigits := 4;
    ExStatus := TooFewDigits;
  ELSIF D > HighBoundsManArray THEN
    MaxDigits := HighBoundsManArray;
    ExStatus := TooManyDigits;
  ELSE
    MaxDigits := D DIV 4;   (* Force a multiple of 4 *)
    IF D MOD 4 > 0 THEN INC(MaxDigits) END;
    MaxDigits := MaxDigits * 4;
  END;
  MaxQuads := MaxDigits DIV 4;
END SetMaxDigits;


PROCEDURE ExTimes10 *(VAR A : ExNumType);
(* A := A * 10 -- much faster than ExMult *)
BEGIN
  INC(A.Exp);
  IF A.Exp > MaxExp THEN
    ExStatus := Overflow;
  END;
END ExTimes10;


PROCEDURE ExDiv10 *(VAR A : ExNumType);
(* A := A / 10 -- much faster than ExDiv *)
BEGIN
  DEC(A.Exp);
  IF A.Exp < MinExp THEN
    ExStatus := Underflow;
  END;
END ExDiv10;


PROCEDURE IsZero *(A : ExNumType) : BOOLEAN;
VAR
  i : INTEGER;
  Zero : BOOLEAN;
BEGIN
  (* check for zero *)
  i := 0;
  Zero := TRUE;
  WHILE (i <= MaxQuads) AND Zero DO
    IF A.Man[i] # 0 THEN
      Zero := FALSE;
    END;
    INC(i);
  END;
  RETURN Zero;
END IsZero;


PROCEDURE ExShiftRight(Carry : INTEGER; VAR A : ExNumType);
(* shift all mantissa digits in A to the right one place.
   The most significant digit is replaced with the Carry. *)
VAR
  i : INTEGER;
BEGIN
  (* shift right *)
  FOR i := MaxQuads TO 1 BY -1 DO
    A.Man[i] := A.Man[i] DIV 10 + (A.Man[i-1] MOD 10) * 1000;
  END;

  (* put Carry in most significant position *)
  A.Man[0] := A.Man[0] DIV 10 + 1000 * Carry;
END ExShiftRight;


PROCEDURE ExShiftLeft(VAR A : ExNumType) : INTEGER;
(* shift all mantissa digits in A to the left one place.
   The digit shifted out of the number is returned.
   The least significant digit is replaced with zero. *)
VAR
  i, d : INTEGER;
BEGIN
  (* shift left *)
  d := A.Man[0] DIV 1000;
  FOR i := 0 TO MaxQuads DO
    A.Man[i] := (A.Man[i] MOD 1000) * 10 + A.Man[i+1] DIV 1000;
  END;

  (* put zero in least significant position *)
  A.Man[MaxQuads] := (A.Man[MaxQuads] MOD 1000) * 10;
  RETURN d;
END ExShiftLeft;


PROCEDURE ExChgSign *(VAR A : ExNumType);
(* A := -A *)
BEGIN
  IF A.Sign = positive THEN
    A.Sign := negative;
  ELSE
    A.Sign := positive;
  END;
END ExChgSign;


PROCEDURE ExAbs *(VAR A : ExNumType);
(* A := ABS(A) *)
BEGIN
  A.Sign := positive;
END ExAbs;


PROCEDURE ExNorm *(VAR A : ExNumType);
(* Normalise A *)
VAR d : INTEGER;
BEGIN
  (* normalise *)
  IF IsZero(A) THEN
    (* normalize zero *)
    A.Sign := positive;
    A.Exp := 0;
  ELSE
    (* shift mantissa to left until most significant digit is
       non-zero, increment exponent with each shift *)
    WHILE A.Man[0] DIV 1000 = 0 DO
      d := ExShiftLeft(A);
      ExDiv10(A);
    END;
  END;
END ExNorm;


PROCEDURE GetMaxDigits *() : INTEGER;
(* Get the current number of digits in extended real numbers *)
BEGIN
  RETURN MaxDigits;
END GetMaxDigits;


PROCEDURE GetExpMant *(x : ExNumType; VAR exp : INTEGER;
                       VAR mant : ExNumType);
(* Returned `mant' number will be between -10.0 and 10.0 *)
BEGIN
  exp := x.Exp;
  mant := x;
  mant.Exp := 0;
END GetExpMant;


PROCEDURE PutDigit(VAR A : INTEGER; Digit, Index : INTEGER);
BEGIN
  IF Index = 0 THEN
    A := A MOD 1000 + Digit * 1000;
  ELSIF Index = 1 THEN
    A := A DIV 1000 * 1000 + A MOD 100 + Digit * 100;
  ELSIF Index = 2 THEN
    A := A DIV 100 * 100 + A MOD 10 + Digit * 10;
  ELSE
    A := A DIV 10 * 10 + Digit;
  END;
END PutDigit;


PROCEDURE ExTrunc *(VAR A : ExNumType);
(* Truncate A so no decimal places are kept. *)
VAR
  i : INTEGER;
BEGIN
  IF A.Exp+1 < 0 THEN A := Ex0; RETURN END;
  FOR i := A.Exp+1 TO MaxDigits-1 DO
    (* zero these digits *)
    PutDigit(A.Man[i DIV 4], 0, i MOD 4);
  END;
END ExTrunc;


PROCEDURE ExFrac *(VAR A : ExNumType);
(* Keep only the fraction portion of A. *)
VAR
  i : INTEGER;
BEGIN
  FOR i := 0 TO A.Exp DO (* zero these digits *)
    PutDigit(A.Man[i DIV 4], 0, i MOD 4);
  END;
  ExNorm(A);             (* normalize the fraction *)
END ExFrac;


PROCEDURE ExToLongInt *(A : ExNumType) : LONGINT;
(* Convert the extended real number `A' into a INTEGER --
   saturating if necessary. *)
CONST
  MaxDigits = 10;
VAR
  Cnt : INTEGER;
  Int : LONGINT;
  Digit : INTEGER;
  Negative : BOOLEAN;
BEGIN
  Negative := FALSE;
  IF A.Sign = negative THEN
    Negative := TRUE;
    ExAbs(A);
  END;
  IF A.Exp < 0 THEN
    Int := 0;
  ELSIF A.Exp >= MaxDigits THEN
    Int := MAX(LONGINT);
  ELSE
    Int := 0;
    FOR Cnt := 0 TO A.Exp DO
      Digit := ExShiftLeft(A);
      IF Cnt = MaxDigits-1 THEN
        IF Int > MAX(LONGINT) DIV 10 THEN
          RETURN Int;
        END;
        IF (Int = MAX(LONGINT) DIV 10) & (Digit > 6) THEN
          Digit := 6;
        END;
      END;
      Int := Int * 10 + Digit;
    END;
  END;
  IF Negative THEN
    RETURN -Int;
  ELSE
    RETURN Int;
  END;
END ExToLongInt;


PROCEDURE ExCompare *(A, B : ExNumType) : ExCompareType;
(* Compares the two extended real numbers. *)
VAR
  Done : BOOLEAN;
  i : INTEGER;
BEGIN
  IF A.Sign # B.Sign THEN
    (* A and B have different signs *)
    IF A.Sign = positive THEN
      (* A and B have different signs and A is positive so A>B *)
      RETURN ExGreater;
    ELSE
      (* A and B have different signs and A is negative so A<B *)
      RETURN ExLess;
    END;
  ELSE
    (* A and B have the same sign *)
    IF (A.Exp # B.Exp) & NOT IsZero(B) & NOT IsZero(A) THEN
      IF A.Exp > B.Exp THEN
        (* A exponent > B exponent *)
        IF A.Sign = positive THEN
          RETURN ExGreater;
        ELSE
          RETURN ExLess;
        END;
      ELSE
        (* A exponent <= B exponent *)
        IF A.Sign = positive THEN
          RETURN ExLess;
        ELSE
          RETURN ExGreater;
        END;
      END;
    ELSE
      (* A & B have same sign and A exponent = B exponent *)
      Done := FALSE;
      i := 0;

      (* compare each digit until a difference is found or
         we reach the end *)
      WHILE (i <= MaxQuads) AND NOT Done DO
        IF A.Man[i] # B.Man[i] THEN
          Done := TRUE;
        ELSE
          INC(i);
        END;
      END;
      IF i > MaxQuads THEN
        (* end reached and all digits match *)
        RETURN ExEqual;
      ELSE
        (* compare different digits *)
        IF A.Man[i] < B.Man[i] THEN
          IF A.Sign = positive THEN
            RETURN ExLess;
          ELSE
            RETURN ExGreater;
          END;
        ELSE
          IF A.Sign = positive THEN
            RETURN ExGreater;
          ELSE
            RETURN ExLess;
          END;
        END;
      END;
    END;
  END;
END ExCompare;


PROCEDURE ExMin *(VAR A : ExNumType; B, C : ExNumType);
(* Return the smaller of B and C in A *)
BEGIN
  IF ExCompare(B, C) = ExGreater THEN
    A := C;
  ELSE
    A := B;
  END;
END ExMin;


PROCEDURE ExMax *(VAR A : ExNumType; B, C : ExNumType);
(* Return the larger of B and C in A *)
BEGIN
  IF ExCompare(B, C) = ExLess THEN
    A := C;
  ELSE
    A := B;
  END;
END ExMax;


PROCEDURE ExAddUtility(VAR A : ExNumType; B, C : ExNumType);
(* A := ABS(B) + ABS(C) *)
VAR
  i, j, joff, carry, quad, total : INTEGER;
  Exl1, Ex2 : ExNumType;
BEGIN
  IF IsZero(B) THEN
    A := C;
  ELSIF IsZero(C) THEN
    A := B;
  ELSE
    IF B.Exp > C.Exp THEN
      Exl1 := B;
      Ex2 := C;
    ELSE
      Exl1 := C;
      Ex2 := B;
    END;
    A := Ex0;
    A.Exp := Exl1.Exp;
    carry := 0;

    (* shift smallest number until quad-aligned relative to
       larger number *)
    j := (Exl1.Exp - Ex2.Exp) MOD 4;
    FOR i := j TO 1 BY -1 DO
      ExShiftRight(0, Ex2);
      INC(Ex2.Exp);
    END;
    joff := (Ex2.Exp - Exl1.Exp) DIV 4;

    (* add the two numbers together *)
    FOR i := MaxQuads TO 0 BY -1 DO
      (* j = index to Ex2 *)
      j := i + joff;

      (* check that j falls within array bounds *)
      IF (j >= 0) AND (j <= MaxQuads) THEN
        (* get quad digit from Ex2 *)
        quad := Ex2.Man[j];
      ELSE
        (* j is outside array bounds, use 0 for quad digit *)
        quad := 0;
      END;

      (* perform addition with carry *)
      total := Exl1.Man[i] + quad + carry;

      (* check for carry *)
      IF total >= 10000 THEN
        DEC(total, 10000);
        carry := 1;
      ELSE
        carry := 0;
      END;
      A.Man[i] := total;
    END;

    (* handle final carry *)
    IF carry = 1 THEN
      (* shift carry into top of mantissa *)
      ExShiftRight(carry, A);

      (* multiply by ten to update exponent *)
      ExTimes10(A);
    END;
  END;

  (* set ExStatus *)
  IF A.Exp > MaxExp THEN
    ExStatus := Overflow;
  END;
END ExAddUtility;


PROCEDURE ExSubUtility(VAR A : ExNumType; B, C : ExNumType);
(* A := ABS(B) - ABS(C) *)
VAR
  PositiveResult : BOOLEAN;
  i, j, joff, borrow, quad, result : INTEGER;
  Exl1, Ex2 : ExNumType;
BEGIN
  ExAbs(B);
  ExAbs(C);
  IF IsZero(B) THEN
    A := C;
  ELSIF IsZero(C) THEN
    A := B;
  ELSE
    IF B.Exp > C.Exp THEN
      Exl1 := B;
      Ex2 := C;
    ELSE
      Exl1 := C;
      Ex2 := B;
    END;
    PositiveResult := ExCompare(Exl1, Ex2) = ExGreater;
    A := Ex0;
    A.Exp := Exl1.Exp;
    borrow := 0;

    (* shift smallest number until quad-aligned relative to
       larger number *)
    j := (Exl1.Exp - Ex2.Exp) MOD 4;
    FOR i := j TO 1 BY -1 DO
      ExShiftRight(0, Ex2);
      INC(Ex2.Exp);
    END;
    joff := (Ex2.Exp - Exl1.Exp) DIV 4;

    (* subtract the two numbers *)
    FOR i := MaxQuads TO 0 BY -1 DO
      (* j = index to Ex2 *)
      j := i + joff;

      (* check that j falls within array bounds *)
      IF (j >= 0) AND (j <= MaxQuads) THEN
        (* get quad from Ex2 *)
        quad := Ex2.Man[j];
      ELSE
        (* j is outside array bounds, use 0 for quad *)
        quad := 0;
      END;

      (* perform subtraction with borrow *)
      IF PositiveResult THEN
        result := Exl1.Man[i] - quad - borrow;
      ELSE
        result := quad - Exl1.Man[i] - borrow;
      END;

      (* check for borrow *)
      IF result < 0 THEN
        INC(result, 10000);
        borrow := 1;
      ELSE
        borrow := 0;
      END;
      A.Man[i] := result;
    END;
  END;

  (* normalise *)
  ExNorm(A);

  (* adjust sign *)
  IF ExCompare(B, C) = ExLess THEN
    ExChgSign(A);
  END;
END ExSubUtility;


PROCEDURE ExAdd *(VAR A : ExNumType; B, C : ExNumType);
(* A = B + C *)
BEGIN
  IF B.Sign = C.Sign THEN
    (* B and C have the same sign -- just add *)
    ExAddUtility(A, B, C);
    IF B.Sign = negative THEN
      ExChgSign(A);
    END;
  ELSE
    (* B and C have different signs *)
    IF B.Sign = positive THEN
      ExSubUtility(A, B, C);
    ELSE
      ExSubUtility(A, C, B);
    END;
  END;
END ExAdd;


PROCEDURE ExSub *(VAR A : ExNumType; B, C : ExNumType);
(* A = B - C *)
BEGIN
  ExChgSign(C);   (* A = B + (-C) *)
  ExAdd(A, B, C);
END ExSub;


PROCEDURE ExRound *(VAR A : ExNumType; D : INTEGER);
(* A := Round(A) *)
VAR
  cindex, index, digit, i : INTEGER;
  Exl : ExNumType;
BEGIN
  IF D <= MaxDigits-1 THEN
    index := (D+1) DIV 4;
    digit := A.Man[index];
    cindex := (D + 1) MOD 4;
    IF cindex = 0 THEN
      digit := digit DIV 1000;
    ELSIF cindex = 1 THEN
      digit := digit DIV 100;
    ELSIF cindex = 2 THEN
      digit := digit DIV 10;
    END;
    IF digit MOD 10 >= 5 THEN
      (* round up *)
      Exl := Ex1;
      Exl.Exp := A.Exp - D;
      IF A.Sign = negative THEN
        ExChgSign(Exl);
      END;
      ExAdd(A, A, Exl);
    END;

    (* make remaining digits zero *)
    IF cindex = 0 THEN
      A.Man[index] := 0;
    ELSIF cindex = 1 THEN
      A.Man[index] := A.Man[index] DIV 1000 * 1000;
    ELSIF cindex = 2 THEN
      A.Man[index] := A.Man[index] DIV 100 * 100;
    ELSIF cindex = 3 THEN
      A.Man[index] := A.Man[index] DIV 10 * 10;
    END;
    FOR i := index+1 TO MaxQuads DO
      A.Man[i] := 0;
    END;
  END;
END ExRound;


PROCEDURE ExMult *(VAR A : ExNumType; B, C : ExNumType);
(* Return B * C *)
VAR
  i, j, carry : INTEGER;
  product : LONGINT;
  Exl : ExNumType;
BEGIN
  IF (ExCompare(B,Ex0) = ExEqual) OR (ExCompare(C,Ex0) = ExEqual) THEN
    (* multiplication by zero *)
    A := Ex0;
  ELSIF ExCompare(C,Ex1) = ExEqual THEN
    A := B;
  ELSIF ExCompare(B,Ex1) = ExEqual THEN
    A := C;
  ELSE
    (* real multiplication *)
    A := Ex0;
    FOR i := MaxQuads TO 0 BY -1 DO
      Exl := Ex0;
      Exl.Exp := B.Exp + C.Exp - i * 4 - 3;
      carry := 0;
      FOR j := MaxQuads TO 0 BY -1 DO
        product := LONG(B.Man[j]) * LONG(C.Man[i]) + LONG(carry);
        Exl.Man[j] := SHORT(product MOD 10000);
        carry := SHORT(product DIV 10000);
      END;

      (* check for final carry *)
      WHILE carry > 0 DO
        ExShiftRight(carry MOD 10, Exl);
        ExTimes10(Exl);
        carry := carry DIV 10;
      END;

      (* perform summation *)
      ExAddUtility(A, A, Exl);
    END;

    (* adjust product sign *)
    IF B.Sign # C.Sign THEN
      ExChgSign(A);
    END;
  END;
END ExMult;


PROCEDURE ExDiv *(VAR A : ExNumType; B, C : ExNumType);
(* A := B / C *)
VAR
  i, j : INTEGER;
  quotient : LONGINT;
  Exl1, Ex2 : ExNumType;
BEGIN
  IF IsZero(C) THEN
    (* attempt to divide by zero *)
    ExStatus := DivideByZero;
  ELSIF IsZero(B) THEN
    (* dividend = 0 *)
    A := Ex0;
  ELSIF ExCompare(C,Ex1) = ExEqual THEN
    (* divisor = 1 *)
    A := B;
  ELSE
    (* real division *)
    A := Ex0;
    A.Exp := B.Exp - C.Exp;

    (* adjust quotient sign *)
    IF B.Sign # C.Sign THEN
      ExChgSign(A);
    END;

    (* let Exl1 = ABS(B) / magnitude of B *)
    Exl1 := B;
    ExAbs(Exl1);
    Exl1.Exp := 0;

    (* let Ex2 = ABS(C) / magnitude of C *)
    Ex2 := C;
    ExAbs(Ex2);
    Ex2.Exp := 0;

    (* actual division *)
    FOR i := 0 TO MaxDigits-1 DO
      quotient := 0;
      WHILE ExCompare(Exl1, Ex2) >= ExEqual DO
        INC(quotient);
        ExSubUtility(Exl1, Exl1, Ex2);
      END;
      A.Man[i DIV 4] := A.Man[i DIV 4] * 10 + SHORT(quotient);
      ExDiv10(Ex2);
    END;

    (* normalize quotient *)
    ExNorm(A);
  END;
END ExDiv;


(* $CopyArrays- *)
PROCEDURE StrToExNum *(Str : ARRAY OF CHAR; VAR A : ExNumType);
(* Convert the string `Str' into an extended real number in A. *)
VAR
  Exp, NumbIndex, InCnt, EndCnt : INTEGER;
  ZeroFlag, NegativeExponent, LeftSide, InExponent : BOOLEAN;
  Done, NegExponent : BOOLEAN;
  ActiveChar : CHAR;

  PROCEDURE SetDigit(VAR Numb : INTEGER);
  BEGIN
    Numb := Numb * 10 + ORD(Str[InCnt]) - ORD('0');
  END SetDigit;

BEGIN
  (* initialize a few counters and stuff *)
  A := Ex0;
  InCnt := 0;             (* character counter *)
  Exp := 0;               (* working exponent *)
  LeftSide := TRUE;
  InExponent := FALSE;
  ZeroFlag := TRUE;
  NegativeExponent := FALSE;
  EndCnt := SHORT(S.Length(Str));
  NumbIndex := 0;

  (* set the sign of `A' to a negative -- if needed *)
  WHILE (InCnt < EndCnt) & (Str[InCnt] = ' ') DO INC(InCnt) END;
  IF Str[InCnt] = '-' THEN
    A.Sign := negative;
    INC(InCnt);
  END;
  WHILE InCnt < EndCnt DO
    ActiveChar := Str[InCnt];
    IF (ActiveChar >= '0') & (ActiveChar <= '9') THEN
      IF InExponent THEN
        SetDigit(Exp);
      ELSE
        IF NumbIndex < MaxDigits THEN  (* enter a digit *)
          SetDigit(A.Man[NumbIndex DIV 4]);
        END;
        IF ZeroFlag & (Str[InCnt] # '0') THEN
          ZeroFlag := FALSE;
        END;
        IF NOT ZeroFlag THEN
          INC(NumbIndex);
          IF LeftSide THEN INC(A.Exp) END;
        ELSE
          IF NOT LeftSide & (A.Exp <= 0) THEN DEC(A.Exp) END;
        END;
      END;
    ELSIF ActiveChar = '.' THEN
      IF ~LeftSide THEN ExStatus := IllegalNumber END;
      LeftSide := FALSE;
    ELSIF ActiveChar = 'E' THEN
      InExponent := TRUE;
      IF Str[InCnt+1] = '-' THEN
        NegativeExponent := TRUE;
        INC(InCnt);
      ELSIF Str[InCnt+1] = '+' THEN
        INC(InCnt);
      END;
    ELSIF ActiveChar = ' ' THEN
      (* do nothing if blanks are encountered *)
    ELSE
      ExStatus := IllegalNumber;
    END; (* IF *)
    INC(InCnt);
  END;

  (* fix up the last quad digits *)
  WHILE (NumbIndex DIV 4 <= MaxQuads) & (NumbIndex MOD 4 > 0) DO
    A.Man[NumbIndex DIV 4] := A.Man[NumbIndex DIV 4] * 10;
    INC(NumbIndex);
  END;

  (* Do some final fixes to the exponent *)
  IF NegativeExponent THEN
    DEC(A.Exp, Exp);
  ELSE
    INC(A.Exp, Exp);
  END;
  DEC(A.Exp);

  (* Ensure valid zero value *)
  IF IsZero(A) THEN A := Ex0 END;
END StrToExNum;


PROCEDURE GetDigit(VAR ExpStr : ARRAY OF CHAR; VAR StrCnt : INTEGER;
                   A : ExNumType; VAR ManIndex : INTEGER) : CHAR;
VAR Quad : LONGINT;
    Ok : BOOLEAN;
BEGIN
  (* Passing all parameters due to a bug in Oberon-2 when this
     was a local procedure *)
  INC(StrCnt);
  IF StrCnt = 4 THEN (* get a quad of digits *)
    Quad := A.Man[ManIndex];
    Ok := Cnv.IntToStr(Quad,ExpStr,Dec,5,'0');
    S.Delete(ExpStr, 0, 1);   (* remove leading digit *)
    INC(ManIndex);
    StrCnt := 0;
  END;
  RETURN ExpStr[StrCnt];
END GetDigit;


PROCEDURE ExNumToStr *(A : ExNumType; Decimal, ExpWidth : INTEGER;
                       VAR Str : ARRAY OF CHAR);
(* Convert the extended real number into a string `S'. *)
VAR
  pos, ManIndex, StrCnt, InCnt, Aexp, MaxExpWidth : INTEGER;
  ExpStr : ARRAY 41 OF CHAR;
  FixPoint, Ok : BOOLEAN;

  PROCEDURE ConcatChar(ch : CHAR);
  BEGIN
    Str[pos] := ch;
    INC(pos);
  END ConcatChar;

BEGIN
  (* initialize a few parameters *)
  pos := 0;
  StrCnt := 3;
  ManIndex := 0;
  ExpStr := '';

  (* force scientific notation for numbers too small or too large *)
  Aexp := ABS(A.Exp);
  MaxExpWidth := ExpWidth;
  IF ((ExpWidth = 0) AND (Aexp > MaxDigits)) OR (ExpWidth > 0) THEN
    (* force scientific notation *)
    IF Aexp > 9999 THEN ExpWidth := 5
    ELSIF Aexp > 999 THEN ExpWidth := 4
    ELSIF Aexp > 99 THEN ExpWidth := 3
    ELSIF Aexp > 9 THEN ExpWidth := 2
    ELSE ExpWidth := 1
    END;
  END;
  IF MaxExpWidth < ExpWidth THEN MaxExpWidth := ExpWidth END;

  (* add the negative sign to the number *)
  IF A.Sign = negative THEN ConcatChar('-') END;

  (* ensure we don't exceed the maximum digits *)
  FixPoint := Decimal # 0;
  IF (Decimal > MaxDigits) OR NOT FixPoint THEN
    Decimal := MaxDigits-1;
  END;

  (* convert the number into scientific notation *)
  IF MaxExpWidth > 0 THEN
    ExRound(A, Decimal);    (* round to appropriate decimal places *)
    ConcatChar(GetDigit(ExpStr, StrCnt, A, ManIndex)); (* leading digit *)
    ConcatChar('.');        (* decimal point *)
    FOR InCnt := 1 TO Decimal DO
      ConcatChar(GetDigit(ExpStr, StrCnt, A, ManIndex));     (* add following digits *)
    END;

    (* add the exponent *)
    ConcatChar('E');
    IF A.Exp >= 0 THEN ConcatChar('+') ELSE ConcatChar('-') END;
    ConcatChar(0X);                        (* terminate the string *)

    Ok := Cnv.IntToStr(Aexp,ExpStr,Dec,SHORT(MaxExpWidth),'0');
    S.Append(Str, ExpStr);
  ELSE
    (* format a non-scientific number *)
    ExRound(A, Decimal+A.Exp); (* round to decimal places *)
    IF A.Exp < 0 THEN
      ConcatChar('0');         (* leading digit *)
      ConcatChar('.');         (* decimal point *)
      FOR InCnt := 2 TO ABS(A.Exp) DO   (* pad with leading zeros *)
        ConcatChar('0');
      END;
      INC(Decimal, A.Exp+1);
    END;
    InCnt := 0;
    REPEAT
      ConcatChar(GetDigit(ExpStr, StrCnt, A, ManIndex));
      IF InCnt > A.Exp THEN
        DEC(Decimal);
      ELSIF InCnt = A.Exp THEN
        ConcatChar('.');
      END;
      INC(InCnt);
    UNTIL (InCnt = MaxDigits) OR (Decimal = 0);
    ConcatChar(0X);

    (* remove any trailing zeros and unneeded digits *)
    InCnt := pos - 2;
    WHILE (InCnt > 1) & (Str[InCnt] = '0') & NOT FixPoint DO
      Str[InCnt] := 0X;
      DEC(InCnt);
    END;
  END;
END ExNumToStr;


PROCEDURE WriteExNum *(A : ExNumType;
                       Width, Decimal, ExpWidth : INTEGER);
(* Write out A to the current output stream in a field of
   `Width' characters, with `Decimal' decimal places, and
   `ExpWidth' exponent width. *)
VAR
  Str : ARRAY MaxLengthNumber+1 OF CHAR;
  i, len : INTEGER;
BEGIN
  ExNumToStr(A, Decimal, ExpWidth, Str);
  len := SHORT(S.Length(Str));
  IF Width >= len THEN
    FOR i := 1 TO Width-len DO io.Write(" ") END;
  END;
  io.WriteString(Str);
END WriteExNum;


PROCEDURE ExNumb *(LeftMan : LONGINT; RightMan : LONGINT;
                   ExpShift : INTEGER; VAR A : ExNumType);
(* create an extended real number which has LeftMan to the left
   of the decimal point and RightMan to the right. The ExpShift
   quantity can shift the decimal point to the right for negative
   values; to the left for positive values. *)
VAR
  i : INTEGER;
BEGIN
  A := Ex0;
  IF LeftMan < 0 THEN
    A.Sign := negative;
    LeftMan := -LeftMan;
  END;
  WHILE RightMan # 0 DO
    ExShiftRight(SHORT(RightMan MOD 10), A);(* shift right 1 position *)
    RightMan := RightMan DIV 10;
  END;
  WHILE LeftMan # 0 DO
    ExShiftRight(SHORT(LeftMan MOD 10), A); (* shift right 1 position *)
    ExTimes10(A);                    (* adjust the exponent *)
    LeftMan := LeftMan DIV 10;
  END;
  ExDiv10(A);                        (* final exponent adjust *)
  INC(A.Exp, ExpShift);              (* shift the decimal point *)
  IF A.Exp > MaxExp THEN             (* signal any errors *)
    ExStatus := Overflow;
  ELSIF A.Exp < MinExp THEN
    ExStatus := Underflow;
  END;
END ExNumb;


BEGIN
  (* create extended number 0 *)
  Ex0.Sign := positive;
  FOR MaxDigits := 0 TO LEN(Ex0.Man)-1 DO
    Ex0.Man[MaxDigits] := 0;
  END;
  Ex0.Exp := 0;

  (* default to max number of digits *)
  SetMaxDigits(HighBoundsManArray);

  (* create some extended number constants *)
  ExNumb(1, 0, 0, Ex1);     (* 1.0 *)

  StrToExNum(
  "3.14159265358979323846264338327950288419716939937511", pi);
  StrToExNum(
  "2.71828182845904523536028747135266249775724709369996", e);
  StrToExNum(
  "0.69314718055994530941723212145817656807550013436026", ln2);
  StrToExNum(
  "2.30258509299404568401799145468436420760110148862877", ln10);
END ExNumbers.
