[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