aboutsummaryrefslogtreecommitdiff
path: root/gcc/ada/libgnat/s-valrea.adb
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/ada/libgnat/s-valrea.adb')
-rw-r--r--gcc/ada/libgnat/s-valrea.adb198
1 files changed, 159 insertions, 39 deletions
diff --git a/gcc/ada/libgnat/s-valrea.adb b/gcc/ada/libgnat/s-valrea.adb
index 0ac3846..bc5465c 100644
--- a/gcc/ada/libgnat/s-valrea.adb
+++ b/gcc/ada/libgnat/s-valrea.adb
@@ -6,7 +6,7 @@
-- --
-- B o d y --
-- --
--- Copyright (C) 1992-2020, Free Software Foundation, Inc. --
+-- Copyright (C) 1992-2021, Free Software Foundation, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
@@ -29,6 +29,7 @@
-- --
------------------------------------------------------------------------------
+with System.Double_Real;
with System.Float_Control;
with System.Unsigned_Types; use System.Unsigned_Types;
with System.Val_Util; use System.Val_Util;
@@ -46,7 +47,7 @@ package body System.Val_Real is
-- If the mantissa of the floating-point type is almost as large as the
-- unsigned type, we do not have enough space for an extra digit in the
-- unsigned type so we handle the extra digit separately, at the cost of
- -- a potential roundoff error.
+ -- a bit more work in Integer_to_Real.
Precision_Limit : constant Uns :=
(if Need_Extra then 2**Num'Machine_Mantissa - 1 else 2**Uns'Size - 1);
@@ -76,6 +77,12 @@ package body System.Val_Real is
7 => 5836, 8 => 5461, 9 => 5168, 10 => 4932, 11 => 4736,
12 => 4570, 13 => 4427, 14 => 4303, 15 => 4193, 16 => 4095);
+ package Double_Real is new System.Double_Real (Num);
+ use type Double_Real.Double_T;
+
+ subtype Double_T is Double_Real.Double_T;
+ -- The double floating-point type
+
function Integer_to_Real
(Str : String;
Val : Uns;
@@ -85,6 +92,9 @@ package body System.Val_Real is
Minus : Boolean) return Num;
-- Convert the real value from integer to real representation
+ function Large_Powten (Exp : Natural) return Double_T;
+ -- Return 10.0**Exp as a double number, where Exp > Maxpow
+
---------------------
-- Integer_to_Real --
---------------------
@@ -110,9 +120,8 @@ package body System.Val_Real is
else raise Program_Error);
-- Maximum exponent of the base that can fit in Num
- B : constant Num := Num (Base);
-
R_Val : Num;
+ D_Val : Double_T;
S : Integer := Scale;
begin
@@ -125,75 +134,151 @@ package body System.Val_Real is
System.Float_Control.Reset;
end if;
- -- Do the conversion
+ -- Take into account the extra digit, i.e. do the two computations
- R_Val := Num (Val);
+ -- (1) R_Val := R_Val * Num (B) + Num (Extra)
+ -- (2) S := S - 1
- -- Take into account the extra digit, if need be. In this case, the
- -- three operands are exact, so using an FMA would be ideal.
+ -- In the first, the three operands are exact, so using an FMA would
+ -- be ideal, but we are most likely running on the x87 FPU, hence we
+ -- may not have one. That is why we turn the multiplication into an
+ -- iterated addition with exact error handling, so that we can do a
+ -- single rounding at the end.
if Need_Extra and then Extra > 0 then
- R_Val := R_Val * B + Num (Extra);
- S := S - 1;
+ declare
+ B : Unsigned := Base;
+ Acc : Num := 0.0;
+ Err : Num := 0.0;
+ Fac : Num := Num (Val);
+ DS : Double_T;
+
+ begin
+ loop
+ -- If B is odd, add one factor. Note that the accumulator is
+ -- never larger than the factor at this point (it is in fact
+ -- never larger than the factor minus the initial value).
+
+ if B rem 2 /= 0 then
+ if Acc = 0.0 then
+ Acc := Fac;
+ else
+ DS := Double_Real.Quick_Two_Sum (Fac, Acc);
+ Acc := DS.Hi;
+ Err := Err + DS.Lo;
+ end if;
+ exit when B = 1;
+ end if;
+
+ -- Now B is (morally) even, halve it and double the factor,
+ -- which is always an exact operation.
+
+ B := B / 2;
+ Fac := Fac * 2.0;
+ end loop;
+
+ -- Add Extra to the error, which are both small integers
+
+ D_Val := Double_Real.Quick_Two_Sum (Acc, Err + Num (Extra));
+
+ S := S - 1;
+ end;
+
+ -- Or else, if the Extra digit is zero, do the exact conversion
+
+ elsif Need_Extra then
+ D_Val := Double_Real.To_Double (Num (Val));
+
+ -- Otherwise, the value contains more bits than the mantissa so do the
+ -- conversion in two steps.
+
+ else
+ declare
+ Mask : constant Uns := 2**(Uns'Size - Num'Machine_Mantissa) - 1;
+ Hi : constant Uns := Val and not Mask;
+ Lo : constant Uns := Val and Mask;
+
+ begin
+ if Hi = 0 then
+ D_Val := Double_Real.To_Double (Num (Lo));
+ else
+ D_Val := Double_Real.Quick_Two_Sum (Num (Hi), Num (Lo));
+ end if;
+ end;
end if;
- -- Compute the final value
+ -- Compute the final value by applying the scaling, if any
+
+ if Val = 0 or else S = 0 then
+ R_Val := Double_Real.To_Single (D_Val);
- if R_Val /= 0.0 and then S /= 0 then
+ else
case Base is
-- If the base is a power of two, we use the efficient Scaling
-- attribute with an overflow check, if it is not 2, to catch
-- ludicrous exponents that would result in an infinity or zero.
when 2 =>
- R_Val := Num'Scaling (R_Val, S);
+ R_Val := Num'Scaling (Double_Real.To_Single (D_Val), S);
when 4 =>
if Integer'First / 2 <= S and then S <= Integer'Last / 2 then
S := S * 2;
end if;
- R_Val := Num'Scaling (R_Val, S);
+ R_Val := Num'Scaling (Double_Real.To_Single (D_Val), S);
when 8 =>
if Integer'First / 3 <= S and then S <= Integer'Last / 3 then
S := S * 3;
end if;
- R_Val := Num'Scaling (R_Val, S);
+ R_Val := Num'Scaling (Double_Real.To_Single (D_Val), S);
when 16 =>
if Integer'First / 4 <= S and then S <= Integer'Last / 4 then
S := S * 4;
end if;
- R_Val := Num'Scaling (R_Val, S);
+ R_Val := Num'Scaling (Double_Real.To_Single (D_Val), S);
- -- If the base is 10, we use a table of powers for accuracy's sake
+ -- If the base is 10, use a double implementation for the sake
+ -- of accuracy, to be removed when exponentiation is improved.
+
+ -- When the exponent is positive, we can do the computation
+ -- directly because, if the exponentiation overflows, then
+ -- the final value overflows as well. But when the exponent
+ -- is negative, we may need to do it in two steps to avoid
+ -- an artificial underflow.
when 10 =>
declare
- Powten : constant array (0 .. Maxpow) of Num;
+ Powten : constant array (0 .. Maxpow) of Double_T;
pragma Import (Ada, Powten);
for Powten'Address use Powten_Address;
begin
if S > 0 then
- while S > Maxpow loop
- R_Val := R_Val * Powten (Maxpow);
- S := S - Maxpow;
- end loop;
-
- R_Val := R_Val * Powten (S);
+ if S <= Maxpow then
+ D_Val := D_Val * Powten (S);
+ else
+ D_Val := D_Val * Large_Powten (S);
+ end if;
else
- while S < -Maxpow loop
- R_Val := R_Val / Powten (Maxpow);
- S := S + Maxpow;
- end loop;
-
- R_Val := R_Val / Powten (-S);
+ if S < -Maxexp then
+ D_Val := D_Val / Large_Powten (Maxexp);
+ S := S + Maxexp;
+ end if;
+
+ if S >= -Maxpow then
+ D_Val := D_Val / Powten (-S);
+ else
+ D_Val := D_Val / Large_Powten (-S);
+ end if;
end if;
+
+ R_Val := Double_Real.To_Single (D_Val);
end;
-- Implementation for other bases with exponentiation
@@ -205,17 +290,24 @@ package body System.Val_Real is
-- an artificial underflow.
when others =>
- if S > 0 then
- R_Val := R_Val * B ** S;
+ declare
+ B : constant Num := Num (Base);
- else
- if S < -Maxexp then
- R_Val := R_Val / B ** Maxexp;
- S := S + Maxexp;
- end if;
+ begin
+ R_Val := Double_Real.To_Single (D_Val);
- R_Val := R_Val / B ** (-S);
- end if;
+ if S > 0 then
+ R_Val := R_Val * B ** S;
+
+ else
+ if S < -Maxexp then
+ R_Val := R_Val / B ** Maxexp;
+ S := S + Maxexp;
+ end if;
+
+ R_Val := R_Val / B ** (-S);
+ end if;
+ end;
end case;
end if;
@@ -228,6 +320,34 @@ package body System.Val_Real is
when Constraint_Error => Bad_Value (Str);
end Integer_to_Real;
+ ------------------
+ -- Large_Powten --
+ ------------------
+
+ function Large_Powten (Exp : Natural) return Double_T is
+ Powten : constant array (0 .. Maxpow) of Double_T;
+ pragma Import (Ada, Powten);
+ for Powten'Address use Powten_Address;
+
+ R : Double_T;
+ E : Natural;
+
+ begin
+ pragma Assert (Exp > Maxpow);
+
+ R := Powten (Maxpow);
+ E := Exp - Maxpow;
+
+ while E > Maxpow loop
+ R := R * Powten (Maxpow);
+ E := E - Maxpow;
+ end loop;
+
+ R := R * Powten (E);
+
+ return R;
+ end Large_Powten;
+
---------------
-- Scan_Real --
---------------