Browse Source

+ new str_real which is completely TP compatible regarding output
format and which should have no rounding errors anymore

Jonas Maebe 25 years ago
parent
commit
0b02714817
2 changed files with 245 additions and 214 deletions
  1. 211 185
      rtl/inc/real2str.inc
  2. 34 29
      rtl/inc/system.inc

+ 211 - 185
rtl/inc/real2str.inc

@@ -18,102 +18,140 @@ type
   treal_type = (rt_s32real,rt_s64real,rt_s80real,rt_c64bit,rt_f16bit,rt_f32bit);
   treal_type = (rt_s32real,rt_s64real,rt_s80real,rt_c64bit,rt_f16bit,rt_f32bit);
   { corresponding to single   double   extended   fixed      comp for i386 }
   { corresponding to single   double   extended   fixed      comp for i386 }
 
 
-const
-   { do not use real constants else you get rouding errors }
-   i10 : longint = 10;
-   i2  : longint = 2;
-   i1  : longint = 1;
-(*
-   { we can use this conditional if the Inf const is defined
-     in processor specific code PM }
-{$ifdef FPC_HAS_INFINITY_CONST}
-   {$define FPC_INFINITY_FOR_REAL2STR}
-{$else not  FPC_HAS_INFINITY_CONST}
-   { To avoid problems with infinity just
-     issue it in byte representation to be endianness independant PM }
-{$ifndef FPC_INFINITY_FOR_REAL2STR}
-{$ifdef SUPPORT_EXTENDED}
-   { extended is not IEEE so its processor specific
-     so I only allow it for i386 PM }
-{$ifdef i386}
-   {$define FPC_INFINITY_FOR_REAL2STR}
-   InfArray : {extended} array[0..9] of byte = ($0,$0,$0,$0,$0,$0,$0,$80,$ff,$7f);
-{$endif i386}
-{$endif SUPPORT_EXTENDED}
-{$endif not FPC_INFINITY_FOR_REAL2STR}
-
-{$ifndef FPC_INFINITY_FOR_REAL2STR}
-{$ifdef SUPPORT_DOUBLE}
-   {$define FPC_INFINITY_FOR_REAL2STR}
-   InfArray : {double} array[0..9] of byte = ($0,$0,$0,$0,$0,$0,$f0,$7f);
-{$endif SUPPORT_DOUBLE}
-{$endif not FPC_INFINITY_FOR_REAL2STR}
-
-{$ifndef FPC_INFINITY_FOR_REAL2STR}
-{$ifdef SUPPORT_SINGLE}
-   {$define FPC_INFINITY_FOR_REAL2STR}
-   InfArray : {single} array[0..3] of byte = ($0,$0,$80,$7f);
-{$endif SUPPORT_SINGLE}
-{$endif not FPC_INFINITY_FOR_REAL2STR}
-
-{$ifndef FPC_INFINITY_FOR_REAL2STR}
-    {$warning don't know Infinity values }
-{$endif not FPC_INFINITY_FOR_REAL2STR}
-{$endif not FPC_HAS_INFINITY_CONST}
-*)
 Procedure str_real (len,f : longint; d : ValReal; real_type :treal_type; var s : string);
 Procedure str_real (len,f : longint; d : ValReal; real_type :treal_type; var s : string);
-{
-  These numbers are for the double type...
-  At the moment these are mapped onto a double but this may change
-  in the future !
-}
-var  maxlen : longint;   { Maximal length of string for float }
-     minlen : longint;   { Minimal length of string for float }
-     explen : longint;   { Length of exponent, including E and sign.
-                           Must be strictly larger than 2 }
-const
-      maxexp = 1e+35;   { Maximum value for decimal expressions }
-      minexp = 1e-35;   { Minimum value for decimal expressions }
-      zero   = '0000000000000000000000000000000000000000';
-
-type
 {$ifdef SUPPORT_EXTENDED}
 {$ifdef SUPPORT_EXTENDED}
+type
   TSplitExtended = packed record
   TSplitExtended = packed record
     case byte of
     case byte of
       0: (bytes: Array[0..9] of byte);
       0: (bytes: Array[0..9] of byte);
       1: (words: Array[0..4] of word);
       1: (words: Array[0..4] of word);
       2: (cards: Array[0..1] of cardinal; w: word);
       2: (cards: Array[0..1] of cardinal; w: word);
   end;
   end;
+const
+  maxPrec = 17;
 {$else}
 {$else}
 {$ifdef SUPPORT_DOUBLE}
 {$ifdef SUPPORT_DOUBLE}
+type
   TSplitDouble = packed record
   TSplitDouble = packed record
     case byte of
     case byte of
       0: (bytes: Array[0..7] of byte);
       0: (bytes: Array[0..7] of byte);
       1: (words: Array[0..3] of word);
       1: (words: Array[0..3] of word);
       2: (cards: Array[0..1] of cardinal);
       2: (cards: Array[0..1] of cardinal);
   end;
   end;
+const
+  maxPrec = 14;
 {$else}
 {$else}
 {$ifdef SUPPORT_SINGLE}
 {$ifdef SUPPORT_SINGLE}
+type
   TSplitSingle = packed record
   TSplitSingle = packed record
     case byte of
     case byte of
       0: (bytes: Array[0..3] of byte);
       0: (bytes: Array[0..3] of byte);
       1: (words: Array[0..1] of word);
       1: (words: Array[0..1] of word);
       2: (cards: Array[0..0] of cardinal);
       2: (cards: Array[0..0] of cardinal);
   end;
   end;
+const
+  maxPrec = 9;
 {$endif SUPPORT_SINGLE}
 {$endif SUPPORT_SINGLE}
 {$endif SUPPORT_DOUBLE}
 {$endif SUPPORT_DOUBLE}
 {$endif SUPPORT_EXTENDED}
 {$endif SUPPORT_EXTENDED}
 
 
-var correct : longint;  { Power correction }
-    currprec : longint;
-    il,il2,roundcorr : Valreal;
-    temp : string;
-    power : string[10];
-    sign : boolean;
-    i : integer;
-    dot : byte;
-    currp : pchar;
-    mantZero, expMaximal: boolean;
+type
+  { the value in the last position is used for rounding }
+  TIntPartStack = array[1..maxPrec+1] of valReal;
+
+var
+  roundCorr, corrVal: valReal;
+  intPart, spos, endpos, fracCount: longint;
+  correct, currprec: longint;
+  temp : string;
+  power : string[10];
+  sign : boolean;
+  dot : byte;
+  mantZero, expMaximal: boolean;
+
+  procedure RoundStr(var s: string; lastPos: byte);
+  var carry: longint;
+  begin
+    carry := 1;
+    repeat
+      s[lastPos] := chr(ord(s[lastPos])+carry);
+      carry := 0;
+      if s[lastPos] > '9' then
+        begin
+          s[lastPos] := '0';
+          carry := 1;
+        end;
+      dec(lastPos);
+    until carry = 0;
+  end;
+
+
+  procedure getIntPart(d: extended);
+  var
+    intPartStack: TIntPartStack;
+    count, stackPtr, endStackPtr, digits: longint;
+    overflow: boolean;
+  begin
+    { position in the stack (gets increased before first write) }
+    stackPtr := 0;
+    { number of digits processed }
+    digits := 0;
+    { did we wrap around in the stack? Necessary to know whether we should round }
+    overflow :=false;
+    { generate a list consisting of d, d/10, d/100, ... until d < 1.0 }
+    while d > 1.0-roundCorr do
+      begin
+        inc(stackPtr);
+        inc(digits);
+        if stackPtr > maxPrec+1 then
+          begin
+            stackPtr := 1;
+            overflow := true;
+          end;
+        intPartStack[stackPtr] := d;
+        d := d / 10.0;
+      end;
+    { if no integer part, exit }
+    if digits = 0 then
+      exit;
+    endStackPtr := stackPtr+1;
+    if endStackPtr > maxPrec + 1 then
+      endStackPtr := 1;
+ { now, all digits are calculated using trunc(d*10^(-n)-int(d*10^(-n-1))*10) }
+    corrVal := 0.0;
+ { the power of 10 with which the resulting string has to be "multiplied" }
+ { if the decimal point is placed after the first significant digit       }
+    correct := digits-1;
+    repeat
+      if (currprec > 0) then
+        begin
+          intPart:= trunc(intPartStack[stackPtr]-corrVal);
+          dec(currPrec);
+          inc(spos);
+          temp[spos] := chr(intPart+ord('0'));
+        end;
+      corrVal := int(intPartStack[stackPtr]) * 10.0;
+      dec(stackPtr);
+      if stackPtr = 0 then
+        stackPtr := maxPrec+1;
+    until (overflow and (stackPtr = endStackPtr)) or
+          (not overflow and (stackPtr = maxPrec+1)) or (currPrec = 0);
+    { round if we didn't use all available digits yet and if the }
+    { remainder is > 5                                           }
+    if overflow  and
+       (trunc(intPartStack[stackPtr]-corrVal) > 5.0 - roundCorr) then
+      roundStr(temp,spos);
+  end;
+
+var  maxlen : longint;   { Maximal length of string for float }
+     minlen : longint;   { Minimal length of string for float }
+     explen : longint;   { Length of exponent, including E and sign.
+                           Must be strictly larger than 2 }
+const
+      maxexp = 1e+35;   { Maximum value for decimal expressions }
+      minexp = 1e-35;   { Minimum value for decimal expressions }
+      zero   = '0000000000000000000000000000000000000000';
+
 begin
 begin
   case real_type of
   case real_type of
     rt_s32real :
     rt_s32real :
@@ -124,7 +162,16 @@ begin
       end;
       end;
     rt_s64real :
     rt_s64real :
       begin
       begin
+{ if the maximum suppported type is double, we can print out one digit }
+{ less, because otherwise we can't round properly and 1e-400 becomes   }
+{ 0.99999999999e-400 (JM)                                              }
+{$ifdef support_extended}
          maxlen:=23;
          maxlen:=23;
+{$else support_extended}
+{$ifdef support_double}
+         maxlen := 22;
+{$endif support_double}
+{$endif support_extended}
          minlen:=9;
          minlen:=9;
          explen:=5;
          explen:=5;
       end;
       end;
@@ -159,7 +206,6 @@ begin
   if len=-32767 then
   if len=-32767 then
     len:=maxlen;
     len:=maxlen;
   { determine sign. before precision, needs 2 less calls to abs() }
   { determine sign. before precision, needs 2 less calls to abs() }
-{  sign:=d<0;}
 {$ifndef big_endian}
 {$ifndef big_endian}
 {$ifdef SUPPORT_EXTENDED}
 {$ifdef SUPPORT_EXTENDED}
   { extended, format (MSB): 1 Sign bit, 15 bit exponent, 64 bit mantissa }
   { extended, format (MSB): 1 Sign bit, 15 bit exponent, 64 bit mantissa }
@@ -181,12 +227,12 @@ begin
   expMaximal := ((TSplitSingle(d).words[1] shr 7) and $ff) = 255;
   expMaximal := ((TSplitSingle(d).words[1] shr 7) and $ff) = 255;
   mantZero := (TSplitSingle(d).cards[0] and $7fffff = 0);
   mantZero := (TSplitSingle(d).cards[0] and $7fffff = 0);
 {$else SUPPORT_SINGLE}
 {$else SUPPORT_SINGLE}
-  {$error No floating type supported for real2str}
+  {$error No big endian floating type supported yet in real2str}
 {$endif SUPPORT_SINGLE}
 {$endif SUPPORT_SINGLE}
 {$endif SUPPORT_DOUBLE}
 {$endif SUPPORT_DOUBLE}
 {$endif SUPPORT_EXTENDED}
 {$endif SUPPORT_EXTENDED}
 {$else big_endian}
 {$else big_endian}
-  {$error NaN/Inf not yet supported for big endian machines in str_real}
+  {$error sign/NaN/Inf not yet supported for big endian CPU's in str_real}
 {$endif big_endian}
 {$endif big_endian}
   if expMaximal then
   if expMaximal then
     if mantZero then
     if mantZero then
@@ -196,120 +242,102 @@ begin
     else temp := 'NaN'
     else temp := 'NaN'
   else
   else
     begin
     begin
-      { the creates a cannot determine which overloaded function to call
-      if d is extended !!!
-      we should prefer real_to_real on real_to_longint !!
-      corrected in compiler }
-
       {  d:=abs(d); this converts d to double so we loose precision }
       {  d:=abs(d); this converts d to double so we loose precision }
       { for the same reason I converted d:=frac(d) to d:=d-int(d); (PM) }
       { for the same reason I converted d:=frac(d) to d:=d-int(d); (PM) }
       if sign then
       if sign then
         d:=-d;
         d:=-d;
-(*
-    {$ifdef FPC_INFINITY_FOR_REAL2STR}
-    {$ifndef FPC_HAS_INFINITY_CONST}
-        if d=ValReal(InfArray) then
-    {$else FPC_HAS_INFINITY_CONST}
-        if d=Inf then
-    {$endif FPC_HAS_INFINITY_CONST}
-          begin
-            if sign then
-              s:='-Inf'
-            else
-              s:='Inf';
-            exit;
-          end;
-    {$endif FPC_INFINITY_FOR_REAL2STR}
-*)      
       { determine precision : maximal precision is : }
       { determine precision : maximal precision is : }
-      currprec:=maxlen-explen-3;
+      currprec := maxlen-explen-2;
       { this is also the maximal number of decimals !!}
       { this is also the maximal number of decimals !!}
       if f>currprec then
       if f>currprec then
         f:=currprec;
         f:=currprec;
       { when doing a fixed-point, we need less characters.}
       { when doing a fixed-point, we need less characters.}
-      if (f<0) or ( (d<>0) and ((d>maxexp) or (d<minexp))) then
+      if (f<0) {or ((d<>0) and ((d>maxexp) and (d>minexp)))} then
         begin
         begin
         { determine maximal number of decimals }
         { determine maximal number of decimals }
           if (len>=0) and (len<minlen) then
           if (len>=0) and (len<minlen) then
             len:=minlen;
             len:=minlen;
           if (len>0) and (len<maxlen) then
           if (len>0) and (len<maxlen) then
-            currprec:=len-explen-3;
+            currprec:=len-explen-2;
         end;
         end;
-      { convert to standard form. }
-      correct:=0;
-      if d>=i10 then
-       begin
-         il:=i1;
-         il2:=i10;
-         repeat
-           il:=il2;
-           il2:=il*i10;
-           inc(correct);
-         until (d<il2);
-         d:=d/il;
-       end
-      else
-       if (d<1) and (d<>0) then
+
+      { leading zero, may be necessary for things like str(9.999:0:2) to }
+      { be able to insert an extra character at the start of the string  }
+      temp := ' 0';
+      { correction used with comparing to avoid rounding/precision errors }
+      roundCorr := (1/exp(maxPrec*ln(10)));
+      { position in the temporary output string }
+      spos := 2;
+      { get the integer part }
+      correct := 0;
+      GetIntPart(d);
+      { now process the fractional part }
+      d := frac(d);
+      { if integer part was zero, go to the first significant digit of the }
+      { fractional part                                                    }
+      { make sure we don't get an endless loop if d = 0                    }
+      if (spos = 2) and (d <> 0.0) then
         begin
         begin
-          while d<1 do
-           begin
-             d:=d*i10;
-             dec(correct);
-           end;
+         { take rounding errors into account }
+          while d < 1.0-roundCorr do
+            begin
+              d := d * 10.0;
+              dec(correct);
+            end;
+          { adjust the precision depending on how many digits we already }
+          { "processed" by multiplying by 10                             }
+          if currPrec >= abs(Correct) then
+            currPrec := currPrec - abs(correct)+1;
         end;
         end;
-      { RoundOff }
-      roundcorr:=extended(i1)/extended(i2);
-      if f<0 then
-        for i:=1 to currprec do roundcorr:=roundcorr/i10
-      else
+      { current length of the output string in endPos }
+      endPos := spos;
+      { if we have to round earlier than the amount of available precision, }
+      { only calculate digits up to that point                              }
+      if (f >= 0) and (currPrec > f) then
+        currPrec := f;
+      { always calculate at least 1 fractional digit for rounding }
+      if (currPrec >= 0) then
         begin
         begin
-          if correct+f<0 then
-           begin
-             for i:=1 to abs(correct+f) do
-              roundcorr:=roundcorr*i10;
-           end
-          else
-           begin
-             for i:=1 to correct+f do
-              roundcorr:=roundcorr/i10;
-           end;
+          if (currPrec > 0) then
+            { do some preliminary rounding (necessary, just like the }
+            { rounding afterwards)                                   }
+            begin
+              corrVal := 0.5;
+              for fracCount := 1 to currPrec do
+                corrVal := corrVal / 10.0;
+              d := d + corrVal;
+            end;
+          { 0.0 < d < 1.0 if we didn't round of earlier, otherwise 1 < d < 10 }
+          if d < 1.0-roundCorr then
+            corrVal := frac(d) * 10
+          else corrVal := d;
+          { calculate the necessary fractional digits }
+          for fracCount := 1 to currPrec do
+            begin
+              inc(spos);
+              temp[spos] := chr(trunc(corrVal)+ord('0'));
+              corrVal := frac(corrVal)*10.0;
+            end;
+          { round off. We left a zero at the start, so we can't underflow  }
+          { to the length byte                                             }
+          if (corrVal-5.0 > roundCorr) then
+            roundStr(temp,spos);
+          { new length of string }
+          endPos := spos;
         end;
         end;
-      d:=d+roundcorr;
-      { 0.99 + 0.05 > 1.0 ! Fix this by dividing the results >=10 first (PV) }
-      while (d>=10.0) do
-       begin
-         d:=d/i10;
-         inc(correct);
-       end;
-      { Now we have a standard expression : sign d *10^correct
-        where  1<d<10 or d=0 ... }
-      { get first character }
-      currp:=pchar(@temp[1]);
+      setLength(temp,endPos);
+      { delete leading zero if we didn't need it while rounding at the }
+      { string level                                                   }
+      if temp[2] = '0' then
+        delete(temp,2,1);
       if sign then
       if sign then
-        currp^:='-'
-      else
-        currp^:=' ';
-      inc(currp);
-      currp^:=chr(ord('0')+trunc(d));
-      inc(currp);
-      d:=d-int(d);
-      { Start making the string }
-      for i:=1 to currprec do
-       begin
-         d:=d*i10;
-         currp^:=chr(ord('0')+trunc(d));
-         inc(currp);
-         d:=d-int(d);
-       end;
-      temp[0]:=chr(currp-pchar(@temp[1]));
-      { Now we need two different schemes for the different
-        representations. }
-      if (f<0) or (correct>maxexp) then
+        temp[1] := '-';
+      if (f<0) or (correct>(round(ln(maxexp)/ln(10)))) then
         begin
         begin
           insert ('.',temp,3);
           insert ('.',temp,3);
           str(abs(correct),power);
           str(abs(correct),power);
           if length(power)<explen-2 then
           if length(power)<explen-2 then
-           power:=copy(zero,1,explen-2-length(power))+power;
+            power:=copy(zero,1,explen-2-length(power))+power;
           if correct<0 then
           if correct<0 then
             power:='-'+power
             power:='-'+power
           else
           else
@@ -318,42 +346,40 @@ begin
         end
         end
       else
       else
         begin
         begin
-          if not sign then
-           begin
-             delete (temp,1,1);
-             dot:=2;
-           end
-          else
-           dot:=3;
+          delete(temp,1,1);
+          dot := 2;
           { set zeroes and dot }
           { set zeroes and dot }
           if correct>=0 then
           if correct>=0 then
-           begin
-             if length(temp)<correct+dot+f then
-              temp:=temp+copy(zero,1,correct+dot+f-length(temp));
-             insert ('.',temp,correct+dot);
-           end
+            begin
+              if length(temp)<correct+dot+f-1 then
+                temp:=temp+copy(zero,1,correct+dot+f-length(temp));
+              insert ('.',temp,correct+dot);
+            end
           else
           else
-           begin
-             correct:=abs(correct);
-             insert(copy(zero,1,correct),temp,dot-1);
-             insert ('.',temp,dot);
-           end;
+            begin
+              correct:=abs(correct);
+              insert(copy(zero,1,correct),temp,dot-1);
+              insert ('.',temp,dot);
+            end;
           { correct length to fit precision }
           { correct length to fit precision }
           if f>0 then
           if f>0 then
-           temp[0]:=chr(pos('.',temp)+f)
+            setlength(temp,pos('.',temp)+f)
           else
           else
-           temp[0]:=chr(pos('.',temp)-1);
+            setLength(temp,pos('.',temp)-1);
         end;
         end;
     end;
     end;
-  if length(temp)<len then
-    s:=space(len-length(temp))+temp
-  else
-    s:=temp;
+    if length(temp)<len then
+      s:=space(len-length(temp))+temp
+    else s:=temp;
 end;
 end;
 
 
 {
 {
   $Log$
   $Log$
-  Revision 1.22  2000-02-09 16:59:31  peter
+  Revision 1.23  2000-02-26 15:49:40  jonas
+    + new str_real which is completely TP compatible regarding output
+      format and which should have no rounding errors anymore
+
+  Revision 1.22  2000/02/09 16:59:31  peter
     * truncated log
     * truncated log
 
 
   Revision 1.21  2000/02/09 12:17:51  peter
   Revision 1.21  2000/02/09 12:17:51  peter

+ 34 - 29
rtl/inc/system.inc

@@ -112,27 +112,6 @@ Procedure Rewrite(var f : TypedFile); [INTERNPROC: In_Rewrite_TypedFile];
 {$I set.inc}
 {$I set.inc}
 
 
 
 
-{****************************************************************************
-                  Subroutines for String handling
-****************************************************************************}
-
-{ Needs to be before RTTI handling }
-
-{$i sstrings.inc}
-
-{$i astrings.inc}
-
-{$ifdef haswidechar}
-{$i wstrings.inc}
-{$endif haswidechar}
-
-{****************************************************************************
-                         Run-Time Type Information (RTTI)
-****************************************************************************}
-
-{$i rtti.inc}
-
-
 {****************************************************************************
 {****************************************************************************
                                Math Routines
                                Math Routines
 ****************************************************************************}
 ****************************************************************************}
@@ -183,6 +162,35 @@ End;
 
 
 {$endif RTLLITE}
 {$endif RTLLITE}
 
 
+{ Include processor specific routines }
+{$I math.inc}
+
+{****************************************************************************
+                  Subroutines for String handling
+****************************************************************************}
+
+{ Needs to be before RTTI handling }
+
+{$i sstrings.inc}
+
+{$i astrings.inc}
+
+{$ifdef haswidechar}
+{$i wstrings.inc}
+{$endif haswidechar}
+
+{****************************************************************************
+                         Run-Time Type Information (RTTI)
+****************************************************************************}
+
+{$i rtti.inc}
+
+{ requires sstrings.inc for initval }
+{$ifdef INT64}
+{$I int64.inc}
+{$endif INT64}
+
+
 {****************************************************************************
 {****************************************************************************
                           Random function routines
                           Random function routines
 
 
@@ -266,13 +274,6 @@ begin
     Seed3 := (internRandom(65000) * internRandom(65000)) mod 765241;
     Seed3 := (internRandom(65000) * internRandom(65000)) mod 765241;
 end;
 end;
 
 
-{ Include processor specific routines }
-{$I math.inc}
-
-{$ifdef INT64}
-{$I int64.inc}
-{$endif INT64}
-
 {****************************************************************************
 {****************************************************************************
                             Memory Management
                             Memory Management
 ****************************************************************************}
 ****************************************************************************}
@@ -612,7 +613,11 @@ end;
 
 
 {
 {
   $Log$
   $Log$
-  Revision 1.83  2000-02-09 16:59:31  peter
+  Revision 1.84  2000-02-26 15:49:40  jonas
+    + new str_real which is completely TP compatible regarding output
+      format and which should have no rounding errors anymore
+
+  Revision 1.83  2000/02/09 16:59:31  peter
     * truncated log
     * truncated log
 
 
   Revision 1.82  2000/02/09 12:17:51  peter
   Revision 1.82  2000/02/09 12:17:51  peter