IMPLEMENTATION MODULE Decimal;

FROM Streams IMPORT StdOut;
FROM Conversions IMPORT IntConversions, RealConversions;
IMPORT String;
IMPORT SYSTEM;

--------------------------------------------------------
  CLASS ReadOnlyDecimal;
    INHERITS Comparable;

    CONST
----------------------------------------------------------------------
-- CAUTION: some methods such as the conversions from string and to string
-- will not work anymore if this value is changed
----------------------------------------------------------------------
      Base = 10;
      RealBase = 10.0;

      MaxDivisionDigits = 50;

      DecimalSeparator = '.';
      ThousandSeparator = ',';
      
    VAR
--      TheDigits: INTEGER;
      TheDecimals: INTEGER;
      TheSign: INTEGER;
      TheDigits: ARRAY OF CHAR;
      TheExponent: INTEGER;
      TheStatus: INTEGER;

    REDEFINE METHOD CREATE (Digits, Decimals: INTEGER);
      BEGIN
-----------------------------------------------------------------------      
-- ensure that each decimal instance is equal to zero at its creation  
-----------------------------------------------------------------------    
    TheSign := Null;
    TheExponent := 0;
    TheStatus := NoError;
    TheDigits.CREATE(0);
--      TheDigits := Digits;
--      TheDecimals := Decimals;
      END CREATE;

    METHOD Check;
      VAR
        n: INTEGER;
      BEGIN
      ASSERT TheDigits <> VOID;
      n := TheDigits.SIZE;
      FOR i := 0 TO n - 1 DO
        ASSERT SYSTEM.ORD(TheDigits[i]) < Base;
        END; -- FOR
      ASSERT (TheSign = Positive) OR (TheSign = Null) OR (TheSign = Negative);
      ASSERT (TheSign = Null) IFF (n = 0);
      IF TheSign = Null THEN
        ASSERT TheExponent = 0;
        END; -- IF
      IF n > 0 THEN
        ASSERT SYSTEM.ORD(TheDigits[0]) > 0;
        ASSERT SYSTEM.ORD(TheDigits[n - 1]) > 0;
        END; -- IF
      END Check;

    METHOD Digits: INTEGER;
      BEGIN
      ASSERT FALSE;
--      RESULT := TheDigits;
      END Digits;
      
    METHOD Decimals: INTEGER;
      BEGIN
      ASSERT FALSE;
--      RESULT := TheDecimals;
      END Decimals;

    METHOD Sign: INTEGER;
      BEGIN
      RESULT := TheSign;
      ASSERT (RESULT = Positive) OR (RESULT = Null) OR (RESULT = Negative);
      END Sign;

    METHOD SetSign(Sign: INTEGER);
      BEGIN
      ASSERT (Sign = Positive) OR (Sign = Null) OR (Sign = Negative);
      TheSign := Sign;
      END SetSign;

    METHOD Exponent: INTEGER;
      BEGIN
      RESULT := TheExponent;
      END Exponent;

    METHOD SetExponent(Exponent: INTEGER);
      BEGIN
      TheExponent := Exponent;
      END SetExponent;

    METHOD MulBase;
      BEGIN
      TheExponent := TheExponent + 1;
      END MulBase;

    METHOD MulBaseN(n: INTEGER);
      BEGIN
      TheExponent := TheExponent + n;
      END MulBaseN;

    METHOD DivBase;
      BEGIN
      TheExponent := TheExponent - 1;
      END DivBase;

    METHOD DivBaseN(n: INTEGER);
      BEGIN
      TheExponent := TheExponent - n;
      END DivBaseN;

    METHOD Status: INTEGER;
      BEGIN
      RESULT := TheStatus;
      END Status;
          
    METHOD SetStatus(Status: INTEGER);
      BEGIN
      TheStatus := Status;
      END SetStatus;

    METHOD MinIndex: INTEGER;
      BEGIN
      ASSERT TheSign <> Null;
      RESULT := TheExponent;
      END MinIndex;      

    METHOD MaxIndex: INTEGER;
      BEGIN
      ASSERT TheSign <> Null;
      RESULT := TheDigits.SIZE - 1 + TheExponent;
      END MaxIndex;

    METHOD DigitCount: INTEGER;
      BEGIN
      RESULT := TheDigits.SIZE;
      END DigitCount;

    METHOD Digit(Index: INTEGER): INTEGER;
      BEGIN
      IF (Index >= MinIndex) AND (Index <= MaxIndex) THEN
        RESULT := SYSTEM.ORD(TheDigits[Index - MinIndex]);
---------------------------------------------------------------------
--     ELSE
--      RESULT := 0;
---------------------------------------------------------------------
        END; -- IF
      ASSERT RESULT >= 0;
      ASSERT RESULT < Base;
      END Digit;

    METHOD MaxDigit: INTEGER;
      BEGIN
      RESULT := Digit(MaxIndex);
      END MaxDigit;

    METHOD MinDigit: INTEGER;
      BEGIN
      RESULT := Digit(MinIndex);
      END MinDigit;

    METHOD SetDigit(Digit: INTEGER;
                    Index: INTEGER);
      BEGIN
      ASSERT Digit >= 0;
      ASSERT Digit < Base;
      ASSERT Index >= MinIndex;
      ASSERT Index <= MaxIndex;
      TheDigits[Index - MinIndex] := SYSTEM.CHR(Digit);      
      END SetDigit;

    METHOD SetDirectDigit(Digit: INTEGER;
                          i: INTEGER);
      BEGIN
      ASSERT Digit >= 0;
      ASSERT Digit < Base;
      TheDigits[i] := SYSTEM.CHR(Digit);      
      END SetDirectDigit;

    METHOD RealDigit(Index: INTEGER): REAL;
      BEGIN
      RESULT := SYSTEM.FLOAT(Digit(Index));
      END RealDigit;

    METHOD Integer: INTEGER;
      BEGIN
      ASSERT (TheSign = Null) OR (MinIndex >= 0);
      IF IsGreater(MaxInteger) OR IsSmaller(MinInteger) THEN
        TheStatus := TooLarge;
       ELSE
        TheStatus := NoError;
        IF TheSign <> Null THEN
          RESULT := Digit(MaxIndex);
          FOR i := MaxIndex - 1 TO 0 BY - 1 DO
            RESULT := RESULT * Base + Digit(i);
            END; -- FOR
          IF TheSign = Negative THEN
            RESULT := - RESULT;
            END; -- IF
------------------------------------------------------------------------
--       ELSE
--        RESULT := 0;
-----------------------------------------------------------------------
          END; -- IF
        END; -- IF
      ASSERT (TheStatus = NoError) OR (TheStatus = TooLarge);
      END Integer;
      
    METHOD Real: REAL;
      BEGIN
      IF TheSign <> Null THEN
        RESULT := RealDigit(MaxIndex);
        FOR i := MaxIndex - 1 TO MinIndex  BY - 1 DO
          RESULT := RESULT * RealBase + RealDigit(i);
          END; -- FOR
        IF TheExponent >= 0 THEN
          FOR i := 1 TO TheExponent DO
            RESULT := RESULT * RealBase;
            END; -- FOR
         ELSE
          FOR i := 1 TO SYSTEM.ABS(TheExponent) DO
            RESULT := RESULT / RealBase;
            END; -- FOR
          END; -- IF
        IF TheSign = Negative THEN
         RESULT := - RESULT;
         END; -- IF
       ELSE
        RESULT := 0.0;
        END; -- IF
      ASSERT (TheStatus = NoError) OR (TheStatus = TooLarge);
      END Real;

    METHOD Asc: ARRAY OF CHAR;
      VAR
         j: INTEGER;
        TheMaxIndex, TheMinIndex: INTEGER;
      BEGIN
      IF TheSign = Null THEN
        RESULT := "0.0";
       ELSE
        TheMaxIndex := SYSTEM.MAX(MaxIndex, 0);
        TheMinIndex := SYSTEM.MIN(MinIndex, - 1);
-----------------------------------------------------------------------
-- one element is added for the decimal dot
-----------------------------------------------------------------------
        RESULT.CREATE(TheMaxIndex - TheMinIndex + 2);
        FOR i := TheMaxIndex TO 0 BY - 1 DO
          RESULT[j] := SYSTEM.CHR(Digit(i) + SYSTEM.ORD('0'));
          j := j + 1;
          END; -- FOR
        RESULT[j] := DecimalSeparator;
        j := j + 1;
        FOR i := - 1 TO TheMinIndex BY - 1 DO
          RESULT[j] := SYSTEM.CHR(Digit(i) + SYSTEM.ORD('0'));
          j := j + 1;
          END; -- FOR
        IF TheSign = Negative THEN
          RESULT := "-" + RESULT;
          END; -- IF
        END; -- IF
      ASSERT RESULT <> VOID;
      END Asc;
      
    METHOD Plus(Other: ReadOnlyDecimal): Decimal;
      BEGIN
      ASSERT Other <> VOID;
      IF Other.Sign = Null THEN
        RESULT := Clone;
       ELSIF Sign = Null THEN
        RESULT := Other.Clone;
       ELSE
        IF Sign = Other.Sign THEN
          RESULT := AbsolutePlus(Other);
          RESULT.SetSign(Sign);
         ELSE
          RESULT := AbsoluteMinus(Other);
          IF Sign = Negative THEN
            RESULT.SetSign(- RESULT.Sign);
            END; -- IF
          END; -- IF
        END; -- IF
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END Plus;
      
    METHOD Minus(Other: ReadOnlyDecimal): Decimal;
      BEGIN
      ASSERT Other <> VOID;
      IF Other.Sign = Null THEN
        RESULT := Clone;
       ELSIF Sign = Null THEN
        RESULT := Other.Negated;
       ELSE
        IF Sign = Other.Sign THEN
          RESULT := AbsoluteMinus(Other);
          IF Sign = Negative THEN
            RESULT.SetSign(- RESULT.Sign);
            END; -- IF
         ELSE
          RESULT := AbsolutePlus(Other);
          RESULT.SetSign(Sign);
          END; -- IF
        END; -- IF
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END Minus;

    METHOD AbsoluteMinus(Other: ReadOnlyDecimal): Decimal;
      VAR
        TheCompare: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      ASSERT Sign <> Null;
      ASSERT Other.Sign <> Null;
      TheCompare := AbsoluteCompare(Other);
      CASE TheCompare OF
        Greater:
          RESULT := OrderedAbsoluteMinus(Other);
          RESULT.TheSign := Positive;
          END;
        Equal:
          RESULT.CREATE(0, 0);
          END;
        Smaller:
          RESULT := Other.OrderedAbsoluteMinus(THIS);
          RESULT.TheSign := Negative;
          END;
        END; -- CASE
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END AbsoluteMinus;

    METHOD AbsolutePlus(Other: ReadOnlyDecimal): Decimal;
      VAR
        TheDigit, TheOtherDigit: INTEGER;
        TheResultDigit, TheCarryDigit: INTEGER;
        TheSum: INTEGER;
        TheMinIndex, TheMaxIndex: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      ASSERT Sign <> Null;
      ASSERT Other.Sign <> Null;
      RESULT.CREATE(0, 0);
      TheMinIndex := SYSTEM.MIN(MinIndex, Other.MinIndex);
      TheMaxIndex := SYSTEM.MAX(MaxIndex, Other.MaxIndex);
      RESULT.TheSign := Positive;
---------------------------------------------------------------------
-- one digit must be added for the eventual final carry digit
-- if this digit is zero the result must be normalized;
---------------------------------------------------------------------
      RESULT.TheDigits.CREATE(TheMaxIndex - TheMinIndex + 2);
      RESULT.TheExponent := TheMinIndex;
----------------------------------------------------------------------
--    TheCarryDigit := 0;
----------------------------------------------------------------------
      FOR i := TheMinIndex TO TheMaxIndex DO
        TheDigit := Digit(i);
        TheOtherDigit := Other.Digit(i);
        TheSum := TheDigit + TheOtherDigit + TheCarryDigit;
        TheResultDigit := TheSum MOD Base;
        TheCarryDigit :=  TheSum / Base;
        ASSERT (TheCarryDigit = 0) OR (TheCarryDigit = 1);
        RESULT.SetDigit(TheResultDigit, i);
        END; -- FOR
      RESULT.SetDigit(TheCarryDigit, TheMaxIndex + 1);
      RESULT.Normalize;
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END AbsolutePlus;

    METHOD OrderedAbsoluteMinus(Other: ReadOnlyDecimal): Decimal;
      VAR
        TheDigit, TheOtherDigit: INTEGER;
        TheResultDigit, TheBorrowDigit: INTEGER;
        TheDif: INTEGER;
        TheMinIndex, TheMaxIndex: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      ASSERT Sign <> Null;
      ASSERT Other.Sign <> Null;
      RESULT.CREATE(0, 0);
      RESULT.TheSign := Positive;
      TheMinIndex := SYSTEM.MIN(MinIndex, Other.MinIndex);
      TheMaxIndex := SYSTEM.MAX(MaxIndex, Other.MaxIndex);
      RESULT.TheDigits.CREATE(TheMaxIndex - TheMinIndex + 1);
      RESULT.TheExponent := TheMinIndex;
--------------------------------------------------------------------------
--    TheBorrowDigit = 0;
--------------------------------------------------------------------------
      FOR i := TheMinIndex TO TheMaxIndex DO
        TheDigit := Digit(i);
        TheOtherDigit := Other.Digit(i);
        TheDif := TheDigit - TheOtherDigit - TheBorrowDigit;
------------------------------------------------------------------------
-- CAUTION: we cannot use the method below because the sign of the
-- result of the MOD operator when one of the operand is negative is
-- implementation-dependant
------------------------------------------------------------------------
--        TheResultDigit := TheDif MOD Base;
--        TheBorrowDigit := - (TheDif / Base);
------------------------------------------------------------------------
        ASSERT (TheDif < Base) AND (TheDif >= -Base);
        IF TheDif >= 0 THEN
          TheResultDigit := TheDif;
          TheBorrowDigit := 0;
         ELSE
          TheResultDigit := Base + TheDif;
          TheBorrowDigit := 1;
          END; -- IF
        ASSERT (TheBorrowDigit = 0) OR (TheBorrowDigit = 1);
        RESULT.SetDigit(TheResultDigit, i);
        END; -- FOR
      ASSERT TheBorrowDigit = 0;
      RESULT.Normalize;
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END OrderedAbsoluteMinus;
      
    METHOD Mult(Other: ReadOnlyDecimal): Decimal;
      BEGIN
      ASSERT Other <> VOID;
      IF (Sign = Null) OR (Other.Sign = Null) THEN
        RESULT.CREATE(0, 0);
       ELSE
        RESULT := AbsoluteMult(Other);
        RESULT.SetSign(Sign * Other.Sign);
        RESULT.SetExponent(RESULT.Exponent + Exponent + Other.Exponent);
        RESULT.Normalize;
        END; -- IF
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END Mult;

    METHOD AbsoluteMult(Other: ReadOnlyDecimal): Decimal;
      BEGIN
      ASSERT Other <> VOID;
      ASSERT Sign <> Null;
      ASSERT Other.Sign <> Null;
      RESULT.CREATE(0, 0);
      FOR i := Other.MinIndex TO Other.MaxIndex DO
        RESULT := RESULT.Plus(THIS.AbsoluteMultByDigit(Other, i));
        END; -- FOR
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END AbsoluteMult;

    METHOD AbsoluteMultByDigit(Other: ReadOnlyDecimal;
                               Index: INTEGER): Decimal;
      VAR
        TheDigit, TheOtherDigit: INTEGER;
        TheResultDigit, TheCarryDigit: INTEGER;
        TheProd: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      ASSERT Sign <> Null;
      ASSERT Other.Sign <> Null;
      RESULT.CREATE(0, 0);
      RESULT.TheSign := Positive;
      RESULT.TheExponent := Index - Other.MinIndex;
---------------------------------------------------------------------
-- one digit must be added for the eventual final carry digit
-- if this digit is zero the result must be normalized;
---------------------------------------------------------------------
      RESULT.TheDigits.CREATE(TheDigits.SIZE + 1);
-----------------------------------------------------------------------
--    TheCarryDigit := 0;
-----------------------------------------------------------------------
      FOR i := MinIndex TO MaxIndex DO
        TheDigit := Digit(i);
        TheOtherDigit := Other.Digit(Index);
        TheProd := TheDigit * TheOtherDigit + TheCarryDigit;
        TheResultDigit := TheProd MOD Base;
        TheCarryDigit := TheProd / Base;
        ASSERT TheCarryDigit < Base;
        RESULT.SetDirectDigit(TheResultDigit, i - MinIndex);
        END; -- FOR
      RESULT.SetDirectDigit(TheCarryDigit, MaxIndex - MinIndex + 1);
      RESULT.Normalize;
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END AbsoluteMultByDigit;

    METHOD Div(Other: ReadOnlyDecimal): Decimal;
      BEGIN
      ASSERT Other <> VOID;
      IF Sign = Null THEN
        RESULT.CREATE(0, 0);
       ELSIF Other.Sign = Null THEN
        RESULT.CREATE(0, 0);
        RESULT.SetStatus(DivideByZero);
       ELSE
        RESULT := AbsoluteDiv(Other);
        RESULT.SetSign(Sign * Other.Sign);
        RESULT.SetExponent(RESULT.Exponent + Exponent - Other.Exponent);
        END; -- IF
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END Div;

    METHOD AbsoluteDiv(Other: ReadOnlyDecimal): Decimal;
      BEGIN
      ASSERT Other <> VOID;
      RESULT := DoDiv1(Other);
      ASSERT RESULT <> VOID;
      DEBUG
        RESULT.Check;
        END; -- DEBUG
      END AbsoluteDiv;

--------------------------------------------------------------------
-- standard algorithm
--------------------------------------------------------------------
    METHOD DoDiv1(Other: ReadOnlyDecimal): Decimal;
      VAR
        u, v: ReadOnlyDecimal;
        q: Decimal;
        l, m, n: INTEGER;
        qj: INTEGER;
        TheTempDecimal: Decimal;
        Ok: BOOLEAN;
      BEGIN
      ASSERT Other <> VOID;
      u := THIS.CLONE;
      v := Other.CLONE;
      n := v.DigitCount;
      m := u.DigitCount - v.DigitCount;
      u.SetSign(Positive);
      u.SetExponent(0);
      u.MulBaseN(MaxDivisionDigits - m - 1);
      v.SetSign(Positive);
      v.SetExponent(0);
      q.CREATE(0, 0);
----      l := m + 1;
      l := MaxDivisionDigits;
      q.TheDigits.CREATE(l);
      q.SetSign(Positive);
      TheTempDecimal.CREATE(0, 0);
      DEBUG
----        StdOut.WriteString("m = ");
----        StdOut.WriteInt(m, 0);
----        StdOut.WriteLn;
----        StdOut.WriteString("n = ");
----        StdOut.WriteInt(n, 0);
----        StdOut.WriteLn;
        END; -- DEBUG
      FOR j := 0 TO l - 1 DO
        DEBUG
----          StdOut.WriteString("j = ");
----          StdOut.WriteInt(j, 0);
----          StdOut.WriteLn;
----          StdOut.WriteString("u = ");
----          StdOut.WriteString(u.Asc);
----          StdOut.WriteLn;
          END; -- DEBUG
        Ok := FALSE;
        FOR i := Base - 1 TO 0 BY - 1 WHILE NOT Ok DO
          TheTempDecimal.SetInteger(i);
          TheTempDecimal.MulBaseN(l - 1 - j);
          TheTempDecimal := TheTempDecimal.Mult(v);
          DEBUG
----            StdOut.WriteString("i = ");
----            StdOut.WriteInt(i, 0);
----            StdOut.WriteLn;
----            StdOut.WriteString("t = ");
----            StdOut.WriteString(TheTempDecimal.Asc);
----            StdOut.WriteLn;
            END; -- DEBUG
          IF TheTempDecimal.IsSmallerEqual(u) THEN
            u := u.Minus(TheTempDecimal);
            qj := i;
            Ok := TRUE;
            END; -- IF
          END; -- FOR
        q.SetDirectDigit(qj, l - 1 - j);
        DEBUG
----          StdOut.WriteString("q = ");
----          StdOut.WriteString(q.Asc);
----          StdOut.WriteLn;
          END; -- DEBUG
        END; -- FOR
      q.DivBaseN(MaxDivisionDigits - m - 1);
      RESULT := q;
      RESULT.Normalize;
      END DoDiv1;

--------------------------------------------------------------------
-- Knuth algorithm for fast division; 
-- CAUTION: still buggy for the time being
--------------------------------------------------------------------
    METHOD DoDiv2(First, Second: ReadOnlyDecimal): Decimal;
      VAR
        d: INTEGER;
        u, v: ReadOnlyDecimal;
        q: Decimal;
        m, n: INTEGER;
        qj: INTEGER;
        TheTempDecimal: Decimal;
        uj, uj1, uj2, v1, v2: INTEGER;
        BEGIN
      ASSERT First <> VOID;
      ASSERT Second <> VOID;
      u := First.CLONE;
      v := Second.CLONE;
      ASSERT u.Sign <> Negative;
      ASSERT v.Sign <> Negative;
      n := v.DigitCount;
      ASSERT n > 1;
      m := u.DigitCount - v.DigitCount;
      ASSERT v.MaxDigit <> 0;
      q.CREATE(0, 0);
      q.TheDigits.CREATE(m + 1);
      q.SetSign(Positive);
      TheTempDecimal.CREATE(0, 0);
-----------------------------------------------------------------------
-- normalize
-----------------------------------------------------------------------
      d := Base / (v.Digit(v.MaxIndex) + 1);
      TheTempDecimal.SetInteger(d);
      u := u.Mult(TheTempDecimal);
      v := v.Mult(TheTempDecimal);
      DEBUG
        StdOut.WriteString("initial u = ");
        StdOut.WriteLine(u.Asc);
        StdOut.WriteString("initial v = ");
        StdOut.WriteLine(v.Asc);
        END; -- DEBUG
-----------------------------------------------------------------------
-- initialize j
-----------------------------------------------------------------------
      FOR j := 0 TO m DO
-----------------------------------------------------------------------
-- calculate qj
-----------------------------------------------------------------------
        uj := u.Digit(u.MaxIndex - j);
        uj1 := u.Digit(u.MaxIndex - j - 1);
        uj2 := u.Digit(u.MaxIndex - j - 2);
        v1 := v.Digit(v.MaxIndex);
        v2 := v.Digit(v.MaxIndex - 1);
        qj := ComputeDigit(uj, uj1, uj2, v1, v2);
-----------------------------------------------------------------------
-- multiply and substract
-----------------------------------------------------------------------
        TheTempDecimal.SetInteger(qj);
        TheTempDecimal.MulBaseN(m - j - 1); -- ???
        u := u.Minus(v.Mult(TheTempDecimal));
-----------------------------------------------------------------------
-- test remainder
-----------------------------------------------------------------------
        IF u.Sign <> Negative THEN 
          q.SetDirectDigit(qj, m - j);
         ELSE
          q.SetDirectDigit(qj - 1, m - j);
-----------------------------------------------------------------------
-- add back
-----------------------------------------------------------------------
          TheTempDecimal := v.Clone;
          TheTempDecimal.MulBaseN(m - j - 1); -- ???
          u := u.Plus(TheTempDecimal);
          ASSERT u.Sign <> Negative;
          END; -- IF
-----------------------------------------------------------------------
-- loop on j
-----------------------------------------------------------------------
        DEBUG
          StdOut.WriteString("u = ");
          StdOut.WriteLine(u.Asc);
          StdOut.WriteString("v = ");
          StdOut.WriteLine(v.Asc);
          StdOut.WriteString("q = ");
          StdOut.WriteLine(q.Asc);
          StdOut.WriteString("t = ");
          StdOut.WriteLine(TheTempDecimal.Asc);
          END; -- DEBUG
        END; -- FOR
-----------------------------------------------------------------------
-- unnormalize
-----------------------------------------------------------------------
      q.Normalize; -- ???
      RESULT := q;
      END DoDiv2;

    METHOD ComputeDigit(uj, uj1, uj2, v1, v2: INTEGER): INTEGER;
      VAR
        qj: INTEGER;
      BEGIN
      IF uj = v1 THEN
        qj := Base - 1;
       ELSE
        qj := (uj * Base + uj1)/v1;
        WHILE v2 * qj > (uj * Base + uj1 - qj * v1) * Base + uj2 DO
          qj := qj - 1;
          END; -- WHILE
        END; -- IF
      RESULT := qj;
      ASSERT RESULT >= 0;
      ASSERT RESULT < Base;
      END ComputeDigit;

    METHOD Negated: Decimal;
      BEGIN
      RESULT := Clone;
      RESULT.SetSign(- RESULT.Sign);
      END Negated;

    METHOD Compare(Other: ReadOnlyDecimal): INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      IF Sign > Other.Sign THEN
        RESULT := Greater;
       ELSIF Sign < Other.Sign THEN
        RESULT := Smaller;
       ELSE
        IF Sign = Null THEN
          ASSERT Other.Sign = Null;
          RESULT := Equal;
        ELSE
         IF Sign = Positive THEN
           RESULT := AbsoluteCompare(Other);
          ELSE
           RESULT := - AbsoluteCompare(Other);
           END; -- IF
         END; -- IF
        END; -- IF
      ASSERT (RESULT = Smaller) OR (RESULT = Equal) OR (RESULT = Greater);
      END Compare;

    METHOD AbsoluteCompare(Other: ReadOnlyDecimal): INTEGER;
      VAR
        TheMaxIndex, TheMinIndex: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      ASSERT Sign <> Null;
      ASSERT Other.Sign <> Null;
      TheMaxIndex := SYSTEM.MAX(MaxIndex, Other.MaxIndex);
      TheMinIndex := SYSTEM.MIN(MinIndex, Other.MinIndex);
      RESULT := Equal;
      FOR i := TheMaxIndex TO TheMinIndex BY - 1 WHILE RESULT = Equal DO
        IF Digit(i) > Other.Digit(i) THEN
          RESULT := Greater;
         ELSIF Digit(i) < Other.Digit(i) THEN
          RESULT := Smaller;
          END; -- IF
        END; -- FOR
      ASSERT (RESULT = Smaller) OR (RESULT = Equal) OR (RESULT = Greater);
      END AbsoluteCompare;
      
    METHOD Equals(Other: ReadOnlyDecimal): BOOLEAN;
      BEGIN
      RESULT := IsEqual (Other);
      END Equals;
      
    REDEFINE METHOD IsGreater (Other: Comparable): BOOLEAN;
      BEGIN
      ASSERT Other <> VOID;
      WHAT Other OF
        IN ReadOnlyDecimal:
          RESULT := Compare(TAG) = Greater;
          END; -- IN
        END; -- WHAT
      END IsGreater;
      
    REDEFINE METHOD IsEqual (Other: Comparable): BOOLEAN;
      BEGIN
      IF Other = THIS THEN
        RESULT := TRUE;
       ELSE
        ASSERT Other <> VOID;
        WHAT Other OF
          IN ReadOnlyDecimal:
            RESULT := Compare(TAG) = Equal;
            END;
          END; -- IN
        END; -- WHAT
      END IsEqual;
      
    METHOD IsSmaller (Other: ReadOnlyDecimal): BOOLEAN;
      VAR
        Cmp: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      Cmp := Compare(Other);
      RESULT := (Cmp = Smaller);
      END IsSmaller;
      
    METHOD IsGreaterEqual (Other: ReadOnlyDecimal): BOOLEAN;
      VAR
        Cmp: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      Cmp := Compare(Other);
      RESULT := (Cmp = Equal) OR (Cmp = Greater);
      END IsGreaterEqual;
      
    METHOD IsSmallerEqual (Other: ReadOnlyDecimal): BOOLEAN;
      VAR
        Cmp: INTEGER;
      BEGIN
      ASSERT Other <> VOID;
      Cmp := Compare(Other);
      RESULT := (Cmp = Equal) OR (Cmp = Smaller);
      END IsSmallerEqual;

    REDEFINE METHOD CLONE(Other: ReadOnlyDecimal);
      BEGIN
      BASE(Other);
      ASSERT TheDigits <> VOID;
      Other.TheDigits := TheDigits.CLONE;
      END CLONE;

---------------------------------------------------------------------
-- when we clone a ReadOnlyDecimal we still want a Decimal as result
-- so we use this cloning method instead of the predefined one;
---------------------------------------------------------------------
    METHOD Clone: Decimal;
      BEGIN
      RESULT.CREATE(0, 0);
--      RESULT.TheDigits := TheDigits;
      RESULT.TheDecimals := TheDecimals;
      RESULT.TheSign := TheSign;
      RESULT.TheDigits := TheDigits.CLONE;
      RESULT.TheExponent := TheExponent;
      RESULT.TheStatus := TheStatus;
      END Clone;

    METHOD Normalize;
      VAR
        n: INTEGER;
        TheBigCount, TheSmallCount: INTEGER;
        BEGIN
      n := TheDigits.SIZE;
      FOR i := MaxIndex TO MinIndex BY - 1 WHILE Digit(i) = 0 DO
        TheBigCount := TheBigCount + 1;
        END; -- FOR
      FOR i := MinIndex TO MaxIndex WHILE Digit(i) = 0 DO
        TheSmallCount := TheSmallCount + 1;
        END; -- FOR
      IF TheBigCount + TheSmallCount = 2 * n THEN
        TheSign := Null;
        TheExponent := 0;
        TheDigits.CREATE(0);
       ELSE
        IF (TheBigCount > 0) OR (TheSmallCount > 0) THEN
          TheDigits := TheDigits.SLICE(TheSmallCount,
                                       n - TheBigCount - TheSmallCount);
          TheExponent := TheExponent + TheSmallCount;
          END; -- IF
        END; -- IF
      DEBUG
        Check;
        END; -- DEBUG
      END Normalize;

    CONST
------------------------------------------------------------------------
-- this should suffice, even for 64-bits INTEGERs
------------------------------------------------------------------------
      MaxDigitsInInteger = 25;    

-----------------------------------------------------------------------
-- this method is private to ensure that a ReadOnlyDecimal cannot
-- be given a value at an other time than creation time
-----------------------------------------------------------------------
    METHOD DoSetInteger (i: INTEGER);
      VAR
        TheTemp: INTEGER;
        TheDigit: INTEGER;
        TheIndex: INTEGER;
      BEGIN
      TheExponent := 0;
      IF i = 0 THEN
        TheSign := Null;
        TheDigits.CREATE(0);
       ELSE
        IF i > 0 THEN
          TheSign := Positive;
          TheTemp := i;
         ELSE
          TheSign := Negative;
          TheTemp := - i;
          END; -- IF
------------------------------------------------------------------------
-- all digits are created with the CHAR value corresponding to a zero
-- digit; after the decomposition of the INTEGER, the Decimal will
-- be normalized to remove unused zero digits;
------------------------------------------------------------------------
        TheDigits.CREATE(MaxDigitsInInteger);
        WHILE TheTemp <> 0 DO
          TheDigit := TheTemp MOD Base;
          TheTemp := TheTemp / Base;
          TheDigits[TheIndex] := SYSTEM.CHR(TheDigit);
          TheIndex := TheIndex + 1;
          END; -- WHILE
        Normalize;
        END; -- IF
      TheStatus := NoError;
      DEBUG
        Check;
        END; -- DEBUG
      END DoSetInteger;

-----------------------------------------------------------------------
-- same remark as above
-----------------------------------------------------------------------      
----    METHOD DoSetReal (r: REAL);
----      BEGIN
----      IF r = 0 THEN
----        TheSign := Null;
----        TheExponent := 0;
----        TheDigits.CREATE(0);
----       ELSE
----        IF r > 0 THEN
----          TheSign := Positive;
----          TheTemp := r;
----         ELSE
----          TheSign := Negative;
----          TheTemp := - r;
----          END; -- IF
----        Normalize;
----        END; -- IF
----      DEBUG
----        Check;
----        END; -- DEBUG
----      END DoSetReal;

    METHOD DoSetReal(r: REAL);
      BEGIN
      DoSetAsc(RealConversions.RealToString(r, 0, 40));
      END DoSetReal;

    METHOD IsNumChar(c: CHAR): BOOLEAN;
      BEGIN
      RESULT := (c >= '0') AND (c <= '9');
      END IsNumChar;
   
-----------------------------------------------------------------------
-- same remark as above
-----------------------------------------------------------------------      
    METHOD DoSetAsc (s: ARRAY OF CHAR);
      BEGIN
      ASSERT s <> VOID;
      Parse(s);
      Normalize;
      DEBUG
        Check;
        END; -- DEBUG
      END DoSetAsc;

    METHOD CodeChar(c: CHAR): CHAR;
      BEGIN
      ASSERT c >= '0';
      ASSERT c <= '9';
      RESULT := SYSTEM.CHR(SYSTEM.ORD(c) - SYSTEM.ORD('0'));
      END CodeChar;

    METHOD Filter(a: ARRAY OF CHAR;
                  c: CHAR): ARRAY OF CHAR;
      VAR
         j: INTEGER;
      BEGIN
      ASSERT a <> VOID;
      j := 0;
      RESULT.CREATE(a.SIZE);
      FOR i := 0 TO a.SIZE - 1 DO
        IF a[i] <> c THEN
          RESULT[j] := a[i];
          j := j + 1;
          END; -- IF
        END; -- FOR
      RESULT := RESULT.SLICE(0, j);
      ASSERT RESULT <> VOID;
      END Filter;
      
    METHOD Parse(t: ARRAY OF CHAR);
      VAR
        i, k: INTEGER;
        n: INTEGER;
        TheExponentSign: INTEGER;
        TheBeginPos1, TheEndPos1: INTEGER;
        TheBeginPos2, TheEndPos2: INTEGER;
        TheBeginPos3, TheEndPos3: INTEGER;
        TheDigitCount: INTEGER;
        TheDecimalCount: INTEGER;
        s: ARRAY OF CHAR;
      BEGIN
      ASSERT t <> VOID;
      s := Filter(t, ThousandSeparator);
      n := s.SIZE;
      WHILE (i < n) AND (s[i] = ' ') DO
        i := i + 1;
        END; -- WHILE
      IF (i < n) THEN
        CASE s[i] OF
          '+':
            TheSign := Positive;
            i := i + 1;
            END;
          '-':
            TheSign := Negative;
            i := i + 1;
            END;
         ELSE
          TheSign := Positive;
          END; -- CASE;
        END; -- IF
      TheBeginPos1 := i;
      WHILE (i < n) AND IsNumChar(s[i]) DO
        i := i + 1;
        END; -- WHILE
      TheEndPos1 := i;
      IF (i < n) AND (s[i] = DecimalSeparator) THEN
        i := i + 1;
        TheBeginPos2 := i;
        WHILE (i < n) AND IsNumChar(s[i]) DO
          i := i + 1;
          END; -- WHILE
        TheEndPos2 := i;
        END; -- IF
      IF (i < n) AND (SYSTEM.UCASE(s[i]) = 'E') THEN
        i := i + 1;
        IF (i < n) THEN
          CASE s[i] OF
            '+':
              i := i + 1;
              TheExponentSign := 1;
              END;
            '-':
              i := i + 1;
              TheExponentSign := - 1;
              END;
           ELSE  
            TheExponentSign := 1;
            END; -- CASE
          END; -- IF
        TheBeginPos3 := i;
        WHILE (i < n) AND IsNumChar(s[i]) DO
          i := i + 1;
          END; -- WHILE
        TheEndPos3 := i;
        END; -- IF
      TheDecimalCount := TheEndPos2 - TheBeginPos2;
      TheDigitCount := TheEndPos1 - TheBeginPos1 + TheDecimalCount;
      TheDigits.CREATE(TheDigitCount);
      k := TheDigitCount - 1;
      FOR j := TheBeginPos1 TO TheEndPos1 - 1 DO
        TheDigits[k] := CodeChar(s[j]);
        k := k - 1;
        END; -- FOR
      FOR j := TheBeginPos2 TO TheEndPos2 - 1 DO
        TheDigits[k] := CodeChar(s[j]);
        k := k - 1;
        END; -- FOR
      ASSERT k = - 1;
      TheExponent := - TheDecimalCount;
      IF TheEndPos3 > TheBeginPos3 THEN
        TheExponent := TheExponent +
                       TheExponentSign * PartialToUnsignedInt(s, TheBeginPos3, TheEndPos3 - 1);
        END; -- IF
      END Parse;

---------------------------------------------------------------------
-- these methods are also present in ourlib but we cannot use them
---------------------------------------------------------------------      
    METHOD GetDigit(c: CHAR): INTEGER;
      BEGIN
      RESULT := SYSTEM.ORD(c) - SYSTEM.ORD('0');
      ASSERT RESULT >= 0;
      ASSERT RESULT < Base;
      END GetDigit;

    METHOD PartialToUnsignedInt(a: ARRAY OF CHAR;
                                BeginPos, EndPos: INTEGER): INTEGER;
      BEGIN
      ASSERT a <> VOID;
      ASSERT BeginPos >= 0;
      ASSERT BeginPos < a.SIZE;
      ASSERT EndPos >= 0;
      ASSERT EndPos < a.SIZE;
      ASSERT BeginPos <= EndPos;
      RESULT := GetDigit(a[BeginPos]);
      FOR i := BeginPos + 1 TO EndPos DO
        RESULT := RESULT * Base + GetDigit(a[i]);
        END; -- FOR
      ASSERT RESULT >= 0;
      END PartialToUnsignedInt;

    END ReadOnlyDecimal;
-----------------------------------------------------        
  CLASS Decimal;
    INHERITS ReadOnlyDecimal;

    METHOD SetInteger (i: INTEGER);
      BEGIN
      DoSetInteger(i);
      END SetInteger;
      
    METHOD SetReal (r: REAL);
      BEGIN
      DoSetReal(r);
      END SetReal;
      
    METHOD SetAsc (s: ARRAY OF CHAR);
      BEGIN
      DoSetAsc(s);
      END SetAsc;
      
    END Decimal;
-----------------------------------------------------------
  ONCE CLASS Zero;
    INHERITS ReadOnlyDecimal;

    REDEFINE METHOD CREATE;
      BEGIN
      DoSetInteger(0);
      END CREATE;
          
    END Zero;
-----------------------------------------------------------    
  ONCE CLASS One;
    INHERITS ReadOnlyDecimal;
      
    REDEFINE METHOD CREATE;
      BEGIN
      DoSetInteger(1);
      END CREATE;
      
    END One;
------------------------------------------------------------
  ONCE CLASS MaxInteger;
    INHERITS ReadOnlyDecimal;

    REDEFINE METHOD CREATE;
      BEGIN
      DoSetInteger(2147483647);
      END CREATE;

    END MaxInteger;
-------------------------------------------------------------
  ONCE CLASS MinInteger;
    INHERITS ReadOnlyDecimal;

    REDEFINE METHOD CREATE;
      BEGIN
      DoSetInteger(-2147483647);
      END CREATE;

    END MinInteger;                
    
END Decimal;    
