[Oberon] Wrong output of Texts.WriteReal and Texts.WriteRealFix in PO

Hans Klaver hklaver at dds.nl
Sat Oct 3 22:38:11 CEST 2020


I wonder if it is known that the output of the present PO version of Texts.WriteReal and Texts.WriteRealFix for various borderline values can be completely false due to untrapped overflow of FLOOR(x)? Although it is acceptable that floating point representation of real numbers produces round-off errors, the output of obviously wrong values is undesirable. 

While looking into this I compared the output of these procedures with the same in my first Oberon (DOS Oberon V1.4, which I ordered from ETH Zurich in the early nineties). I don't have the source code of that early Oberon version, but I suppose it uses the source as published in the 1992 Project Oberon book. For the present PO version prof. Wirth rewrote these procedures, originally by J. Gutknecht.

I used the following test module:

MODULE Test1;
  IMPORT In, Fonts, Texts, Oberon;
  VAR W: Texts.Writer; 

  PROCEDURE Do*;
    VAR x: REAL; k: INTEGER;
  BEGIN
    In.Open;  In.Real(x);  In.Int(k);
    IF In.Done THEN 
      Texts.WriteReal(W, x, n); Texts.WriteRealFix(W, x, n, k)
    ELSE 
      Texts.WriteString(W, "Parameter error")
    END;
    Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf)
  END Do;

BEGIN Texts.OpenWriter(W); Texts.SetFont(W, Fonts.This("Courier10.Fnt"))
END Test1.Do^


Example input & output:
x            k  Texts.WriteReal      Texts.WriteRealFix    Texts.WriteRealFix (Ob.-90)
1554.70E5    1  1.554700E+08         155470003.2           155470000.
1554.70E5    2  1.554700E+08         21474836.47           155470000.
1554.70E6    0  1.554700E+09         1554700032.           Trap 7  Overflow
1.0E8        1  1.000000E+08         100000000.0           100000000.0
1.0E9        1  1.000000E+09         214748364.7           Trap 7  Overflow
1.0E9        0  1.000000E+09         1000000000.0          Trap 7  Overflow
221500000.0  2 -2.079967E+08        -21474836.47           221499984.

In the overview of results below I call a result containing the digits 2147483647
'overflow (Mersenne)' or 'overflow (-Mersenne)' because 2147483647 = MAX(INTEGER) = 2^(32)-1, which is a Mersenne prime. 

It can be seen from this table that the Oberon-90 version of Texts.WriteRealFix always gives a 'sane' result: either a proper approximation or a 'Trap 7 Overflow'. The Oberon-90 version of Texts.WriteReal never gave wrong results and never trapped in my tests.
Texts.WriteRealFix in Oberon-90 prints at most 9 significant digits when there are no leading zeros; with leading zeros there are at most 13 digits after the decimal point.

The present Oberon-07 versions of these procedures never trap, but regularly give wrong results, due to silent overflow of the FLOOR(x) function.

x            k  Texts.WriteReal      Texts.WriteRealFix    Texts.WriteRealFix (Ob.-90)     
1554.70E5    1  OK                   OK                    OK  (9 digits, no decimal)
1554.70E5    2  OK                   overflow (Mersenne)   OK  (9 digits, no decimal)
1554.70E6    0  OK                   OK                    Trap 7  Overflow
1554.70E7    0  OK                   overflow (Mersenne)   Trap 7  Overflow
1.55470E5    4  OK                   OK                    OK  (9 digits, no decimal)
1.55470E5    5  OK                   overflow (Mersenne)   OK  (9 digits, no decimal)
1.0E8        1  OK                   OK                    OK  (9 digits)
1.0E9        1  OK                   overflow (Mersenne)   Trap 7  Overflow
1.0E9        0  OK                   OK                    Trap 7  Overflow
221500000.0  3  overflow (negative)  overflow (-Mersenne)  OK  (9 digits, no decimal)
221500000.0  2  overflow (negative)  overflow (-Mersenne)  OK  (9 digits, no decimal)
221500000.0  1  overflow (negative)  overflow (negative)   OK  (9 digits, no decimal)
221500000.0  0  overflow (negative)  overflow (negative)   OK  (9 digits, no decimal)
22150000.0   0  OK                   OK                    OK
22150000.0   1  OK                   OK                    OK
22150000.0   2  OK                   overflow (Mersenne)   OK  (1 decimal digit)
22150000.0  11  OK                   overflow (Mersenne)   OK  (1 decimal digit)
2215000.0    2  OK                   OK                    OK
2215000.0    3  OK                   overflow (Mersenne)   OK  (2 decimal digits)
2147483647.0 0  overflow (-1.0)      overflow (  -1.)      Trap 7  Overflow                         
2147483648.0 1  overflow ( 0  )      overflow (   0 )      Trap 7  Overflow          
2147483649.0 0  overflow ( 1.0)      overflow (   1.)      Trap 7  Overflow
214748364.0  0  OK                   OK                    OK
2147484000.0 0  overflow (352.)      overflow (352.)       Trap 7  Overflow
214748400.0  0  overflow (-Mersenne) overflow (-Mersenne)  OK  (9 digits, no decimal)
214740000.0  0  OK                   OK                    OK  (9 digits, no decimal)
214740000.0  1  OK                   OK                    OK  (9 digits, no decimal)
214740000.0  2  OK                   overflow (Mersenne)   OK  (9 digits, no decimal)
0.000012345  9  OK                   .0000123              0.000012345)
0.000012345 11  OK                   .0000123              0.00001234500)
0.000012345 13  OK                   .0000123              0.0000123449984)
0.000012345 14  OK                   .0000123              0.0000123449984)
--------------
 Mersenne:  2147483647 (with decimal point)
-Mersenne: -2147483647 (with decimal point)


I tried to find a way to catch such overflow of FLOOR (so that an ASSERT(FALSE) run time trap can be produced), but was unable to find a satisfactory solution. The only special cases that I could catch were (FLOOR(x) = 2147483647) OR (FLOOR(x) = -2147483647), but this is not a real solution.

I also ported Jürg Gutknecht's Texts.WriteStringFix version to Oberon-07, but I could not think up an implementation of Reals.Convert(x, n, d) without using FLOOR(x)...

Does anyone have a suggestion? Could it be that the TRAP7 is not due to integer overflow but to an array index overflow?

--
Hans


-----------

(* Below is my port to Oberon-07 of Jürg Gutknecht's original Oberon-90 
   version of WriteRealFix from the book Project Oberon (1992) *)

  PROCEDURE Ten(n: INTEGER): REAL;
    VAR t, p: REAL;
  BEGIN t := 1.0; p := 10.0;   (*compute 10^n *)
    WHILE n > 0 DO
      IF ODD(n) THEN t := p * t END ;
      p := p*p; n := n DIV 2
    END ;
    RETURN t
  END Ten;

  PROCEDURE Convert (x: REAL; n: INTEGER; VAR d: ARRAY OF CHAR);
  (* Convert REAL x to a string of n characters.
     This version still is unsatisfactory, because FLOOR overflows silently.
   *)
    VAR i, k: INTEGER;
  BEGIN
    i := FLOOR(x);  k := 0;
    WHILE k < n DO
      d[k] := CHR(i MOD 10 + 30H); i := i DIV 10; INC(k)
    END 
  END Convert;

  PROCEDURE WriteRealFix* (VAR W: Texts.Writer; x: REAL; n, k: INTEGER);
    CONST maxD = 9;  (* maximal number of digits written, apart from leading zeros *)
    VAR e, i: INTEGER; sign: CHAR; x0: REAL;
      d: ARRAY maxD OF CHAR;  (* digits *)

    PROCEDURE seq (VAR W: Texts.Writer; ch: CHAR; n: INTEGER);
    (* Write a sequence of n characters ch *)
    BEGIN WHILE n > 0 DO Texts.Write(W, ch); DEC(n) END 
    END seq;

    PROCEDURE dig(VAR W: Texts.Writer; d: ARRAY OF CHAR; VAR i: INTEGER; n: INTEGER);
    (* Write n digits taken from string d *)
    BEGIN
      WHILE n > 0 DO
        DEC(i); Texts.Write(W, d[i]); DEC(n)
      END 
    END dig;
  
  BEGIN e := ASR(ORD(x), 23) MOD 100H;  (*binary exponent*)
    IF k < 0 THEN k := 0 END;
    IF e = 0 THEN seq(W, " ", n-k-2); Texts.Write(W, "0"); seq(W, " ", k+1) 
    ELSIF e = 255 THEN Texts.WriteString(W, " NaN"); seq(W, " ", n-4)
    ELSE e := (e - 127) * 77 DIV 256;  (* decimal exponent *)
      IF x < 0.0 THEN sign := "-"; x := -x ELSE sign := " " END;
      IF e >= 0 THEN (*x >= 1.0, 77/256 = log 2*) x := x/Ten(e)
      ELSE (*x < 1.0*) x := Ten(-e) * x 
      END;
      IF x >= 10.0 THEN x := 0.1*x; INC(e) END; 
      (* 1 <= x < 10 *)
      IF k+e >= maxD-1 THEN k := maxD-1-e
      ELSIF k+e < 0 THEN k := -e; x := 0.0 
      END;
      x0 := Ten(k+e); x := x0*x + 0.5; 
      IF x >= 10.0*x0 THEN INC(e) END; 
      (*e = no. of digits before decimal point*) 
      INC(e); i := k+e; Convert(x, i, d); 
      IF e > 0 THEN
        seq(W, " ", n-e-k-2); Texts.Write(W, sign); dig(W, d, i, e);
        Texts.Write(W, "."); dig(W, d, i, k) 
      ELSE 
        seq(W, " ", n-k-3); Texts.Write(W, sign); Texts.Write(W, "0"); Texts.Write(W, ".");
        seq(W, "0", -e); dig(W, d, i, k+e) 
      END
    END
  END WriteRealFix;



More information about the Oberon mailing list