[Oberon] Bug in Texts.WriteReal of PO
Hans Klaver
hklaver at dds.nl
Sat Oct 3 08:42:11 CEST 2020
Hi all,
Recently I encountered a (minor) bug in the output of the current version of Texts.WriteReal:
- if parameter n (the number of characters for the output string) is
less then 7 the rounding is not correct
- if parameter n = 7 the output is way off mark
- otherwise (n > 7) the output is correct
E.g. when the number 1554.70E5 is input and then written with different values for n we get the following output:
n Texts.WriteReal
--------------------
0 1.E+08
1 1.E+08
2 1.E+08
3 1.E+08
4 1.E+08
5 1.E+08
7 6.E+08
8 2.E+08
9 1.6E+08
10 1.55E+08
11 1.555E+08
12 1.5547E+08
13 1.55470E+08
14 1.554700E+08
15 1.554700E+08
Above for n < 9 only the line with n = 8 2.E+08 is correct.
I found two alternative fixes:
- Fix A has as welcome (imho) side effect that it also ensures at least one digit after the decimal point for n < 9; this is the output of the Oberon-90 version of Texts.WriteReal (by Jürg Gutknecht).
- Fix B only corrects the rounding for n < 7 and the erroneous output in case n = 7.
The module below shows the corrected procedure WriteReal with the two fixes in place. I prefer fix A because I think floating point output without at least one decimal digit is pointless.
I also took the opportunity to propose a better tabulation of the case x = 0.0
MODULE Test;
IMPORT In, Fonts, Texts, Oberon;
VAR W: Texts.Writer;
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 WriteReal* (VAR W: Texts.Writer; x: REAL; n: INTEGER); (* hk 2 Oct 2020 *)
CONST minLen = 7; maxLen = 14; (* excl. one leading space and sign *)
VAR e, i, k, m: INTEGER;
d: ARRAY 16 OF CHAR;
BEGIN e := ASR(ORD(x), 23) MOD 100H; (*binary exponent*)
IF e = 0 THEN
WHILE n > maxLen DO Texts.Write(W, " "); DEC(n) END; (* tabulate 0 *)
Texts.WriteString(W, " 0 ");
WHILE n > (minLen + 2) DO Texts.Write(W, " "); DEC(n) END
ELSIF e = 255 THEN Texts.WriteString(W, " NaN ")
ELSE
IF n < 9 THEN n := 9 END; (* fix A, also >= 1 digit after the decimal point *)
Texts.Write(W, " ");
WHILE n >= 15 DO DEC(n); Texts.Write(W, " ") END ;
(* 2 < n < 9 digits to be written*)
IF x < 0.0 THEN Texts.Write(W, "-"); x := -x ELSE Texts.Write(W, " ") END ;
e := (e - 127) * 77 DIV 256 - 6; (*decimal exponent*)
IF e >= 0 THEN x := x / Ten(e) ELSE x := Ten(-e) * x END ;
m := FLOOR(x + 0.5);
IF m >= 10000000 THEN INC(e); m := m DIV 10 END;
i := 0; k := 13-n;
(* IF k > 5 THEN k := 5 END; *) (* fix B *)
REPEAT
IF i = k THEN INC(m, 5) END ; (*rounding*)
d[i] := CHR(m MOD 10 + 30H); m := m DIV 10; INC(i)
UNTIL m = 0;
DEC(i); Texts.Write(W, d[i]); Texts.Write(W, ".");
IF i < n-7 THEN n := 0 ELSE n := 14 - n END ;
WHILE i > n DO DEC(i); Texts.Write(W, d[i]) END ;
Texts.Write(W, "E"); INC(e, 6);
IF e < 0 THEN Texts.Write(W, "-"); e := -e ELSE Texts.Write(W, "+") END ;
Texts.Write(W, CHR(e DIV 10 + 30H)); Texts.Write(W, CHR(e MOD 10 + 30H))
END
END WriteReal;
PROCEDURE Do*;
VAR x: REAL; n, k: INTEGER;
BEGIN
In.Open; In.Real(x); In.Int(k);
IF In.Done THEN
Texts.SetFont(W, Fonts.This("Courier10.Fnt"));
FOR n := 0 TO 15 DO
Texts.WriteInt(W, n, 2); Texts.WriteReal(W, x, n); WriteReal(W, x, n);
Texts.WriteRealFix(W, x, n, k); Texts.WriteLn(W)
END
ELSE
Texts.WriteString(W, "Parameter error")
END;
Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf)
END Do;
BEGIN Texts.OpenWriter(W)
END Test.Do^
1554.70E5 1
1554.70E5 2
Run Test.Do with the (start of) the first line selected to see the effect of the fix.
If you then run the command with the second line selected you can see another quirk of floating point output in module Texts, in this case in Texts.WriteRealFix, but the same may happen with Texts.WriteReal: integer overflow is not trapped, so you get wrong output; in this case the digits of a well known Mersenne prime, but in other instances less helpful real numbers.
On that I will write a separate post.
--
Hans Klaver
More information about the Oberon
mailing list