------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                               U R E A L P                                --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 1992-2019, 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- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with Alloc;
with Output;  use Output;
with Table;
with Tree_IO; use Tree_IO;

package body Urealp is

   Ureal_First_Entry : constant Ureal := Ureal'Succ (No_Ureal);
   --  First subscript allocated in Ureal table (note that we can't just
   --  add 1 to No_Ureal, since "+" means something different for Ureals).

   type Ureal_Entry is record
      Num  : Uint;
      --  Numerator (always non-negative)

      Den : Uint;
      --  Denominator (always non-zero, always positive if base is zero)

      Rbase : Nat;
      --  Base value. If Rbase is zero, then the value is simply Num / Den.
      --  If Rbase is non-zero, then the value is Num / (Rbase ** Den)

      Negative : Boolean;
      --  Flag set if value is negative
   end record;

   --  The following representation clause ensures that the above record
   --  has no holes. We do this so that when instances of this record are
   --  written by Tree_Gen, we do not write uninitialized values to the file.

   for Ureal_Entry use record
      Num      at  0 range 0 .. 31;
      Den      at  4 range 0 .. 31;
      Rbase    at  8 range 0 .. 31;
      Negative at 12 range 0 .. 31;
   end record;

   for Ureal_Entry'Size use 16 * 8;
   --  This ensures that we did not leave out any fields

   package Ureals is new Table.Table (
     Table_Component_Type => Ureal_Entry,
     Table_Index_Type     => Ureal'Base,
     Table_Low_Bound      => Ureal_First_Entry,
     Table_Initial        => Alloc.Ureals_Initial,
     Table_Increment      => Alloc.Ureals_Increment,
     Table_Name           => "Ureals");

   --  The following universal reals are the values returned by the constant
   --  functions. They are initialized by the initialization procedure.

   UR_0       : Ureal;
   UR_M_0     : Ureal;
   UR_Tenth   : Ureal;
   UR_Half    : Ureal;
   UR_1       : Ureal;
   UR_2       : Ureal;
   UR_10      : Ureal;
   UR_10_36   : Ureal;
   UR_M_10_36 : Ureal;
   UR_100     : Ureal;
   UR_2_128   : Ureal;
   UR_2_80    : Ureal;
   UR_2_M_128 : Ureal;
   UR_2_M_80  : Ureal;

   Num_Ureal_Constants : constant := 10;
   --  This is used for an assertion check in Tree_Read and Tree_Write to
   --  help remember to add values to these routines when we add to the list.

   Normalized_Real : Ureal := No_Ureal;
   --  Used to memoize Norm_Num and Norm_Den, if either of these functions
   --  is called, this value is set and Normalized_Entry contains the result
   --  of the normalization. On subsequent calls, this is used to avoid the
   --  call to Normalize if it has already been made.

   Normalized_Entry : Ureal_Entry;
   --  Entry built by most recent call to Normalize

   -----------------------
   -- Local Subprograms --
   -----------------------

   function Decimal_Exponent_Hi (V : Ureal) return Int;
   --  Returns an estimate of the exponent of Val represented as a normalized
   --  decimal number (non-zero digit before decimal point), The estimate is
   --  either correct, or high, but never low. The accuracy of the estimate
   --  affects only the efficiency of the comparison routines.

   function Decimal_Exponent_Lo (V : Ureal) return Int;
   --  Returns an estimate of the exponent of Val represented as a normalized
   --  decimal number (non-zero digit before decimal point), The estimate is
   --  either correct, or low, but never high. The accuracy of the estimate
   --  affects only the efficiency of the comparison routines.

   function Equivalent_Decimal_Exponent (U : Ureal_Entry) return Int;
   --  U is a Ureal entry for which the base value is non-zero, the value
   --  returned is the equivalent decimal exponent value, i.e. the value of
   --  Den, adjusted as though the base were base 10. The value is rounded
   --  toward zero (truncated), and so its value can be off by one.

   function Is_Integer (Num, Den : Uint) return Boolean;
   --  Return true if the real quotient of Num / Den is an integer value

   function Normalize (Val : Ureal_Entry) return Ureal_Entry;
   --  Normalizes the Ureal_Entry by reducing it to lowest terms (with a base
   --  value of 0).

   function Same (U1, U2 : Ureal) return Boolean;
   pragma Inline (Same);
   --  Determines if U1 and U2 are the same Ureal. Note that we cannot use
   --  the equals operator for this test, since that tests for equality, not
   --  identity.

   function Store_Ureal (Val : Ureal_Entry) return Ureal;
   --  This store a new entry in the universal reals table and return its index
   --  in the table.

   function Store_Ureal_Normalized (Val : Ureal_Entry) return Ureal;
   pragma Inline (Store_Ureal_Normalized);
   --  Like Store_Ureal, but normalizes its operand first

   -------------------------
   -- Decimal_Exponent_Hi --
   -------------------------

   function Decimal_Exponent_Hi (V : Ureal) return Int is
      Val : constant Ureal_Entry := Ureals.Table (V);

   begin
      --  Zero always returns zero

      if UR_Is_Zero (V) then
         return 0;

      --  For numbers in rational form, get the maximum number of digits in the
      --  numerator and the minimum number of digits in the denominator, and
      --  subtract. For example:

      --     1000 / 99 = 1.010E+1
      --     9999 / 10 = 9.999E+2

      --  This estimate may of course be high, but that is acceptable

      elsif Val.Rbase = 0 then
         return UI_Decimal_Digits_Hi (Val.Num) -
                UI_Decimal_Digits_Lo (Val.Den);

      --  For based numbers, just subtract the decimal exponent from the
      --  high estimate of the number of digits in the numerator and add
      --  one to accommodate possible round off errors for non-decimal
      --  bases. For example:

      --     1_500_000 / 10**4 = 1.50E-2

      else -- Val.Rbase /= 0
         return UI_Decimal_Digits_Hi (Val.Num) -
                Equivalent_Decimal_Exponent (Val) + 1;
      end if;
   end Decimal_Exponent_Hi;

   -------------------------
   -- Decimal_Exponent_Lo --
   -------------------------

   function Decimal_Exponent_Lo (V : Ureal) return Int is
      Val : constant Ureal_Entry := Ureals.Table (V);

   begin
      --  Zero always returns zero

      if UR_Is_Zero (V) then
         return 0;

      --  For numbers in rational form, get min digits in numerator, max digits
      --  in denominator, and subtract and subtract one more for possible loss
      --  during the division. For example:

      --     1000 / 99 = 1.010E+1
      --     9999 / 10 = 9.999E+2

      --  This estimate may of course be low, but that is acceptable

      elsif Val.Rbase = 0 then
         return UI_Decimal_Digits_Lo (Val.Num) -
                UI_Decimal_Digits_Hi (Val.Den) - 1;

      --  For based numbers, just subtract the decimal exponent from the
      --  low estimate of the number of digits in the numerator and subtract
      --  one to accommodate possible round off errors for non-decimal
      --  bases. For example:

      --     1_500_000 / 10**4 = 1.50E-2

      else -- Val.Rbase /= 0
         return UI_Decimal_Digits_Lo (Val.Num) -
                Equivalent_Decimal_Exponent (Val) - 1;
      end if;
   end Decimal_Exponent_Lo;

   -----------------
   -- Denominator --
   -----------------

   function Denominator (Real : Ureal) return Uint is
   begin
      return Ureals.Table (Real).Den;
   end Denominator;

   ---------------------------------
   -- Equivalent_Decimal_Exponent --
   ---------------------------------

   function Equivalent_Decimal_Exponent (U : Ureal_Entry) return Int is

      type Ratio is record
         Num : Nat;
         Den : Nat;
      end record;

      --  The following table is a table of logs to the base 10. All values
      --  have at least 15 digits of precision, and do not exceed the true
      --  value. To avoid the use of floating point, and as a result potential
      --  target dependency, each entry is represented as a fraction of two
      --  integers.

      Logs : constant array (Nat range 1 .. 16) of Ratio :=
        (1 => (Num =>           0, Den =>            1),  -- 0
         2 => (Num =>  15_392_313, Den =>   51_132_157),  -- 0.301029995663981
         3 => (Num => 731_111_920, Den => 1532_339_867),  -- 0.477121254719662
         4 => (Num =>  30_784_626, Den =>   51_132_157),  -- 0.602059991327962
         5 => (Num => 111_488_153, Den =>  159_503_487),  -- 0.698970004336018
         6 => (Num =>  84_253_929, Den =>  108_274_489),  -- 0.778151250383643
         7 => (Num =>  35_275_468, Den =>   41_741_273),  -- 0.845098040014256
         8 => (Num =>  46_176_939, Den =>   51_132_157),  -- 0.903089986991943
         9 => (Num => 417_620_173, Den =>  437_645_744),  -- 0.954242509439324
        10 => (Num =>           1, Den =>            1),  -- 1.000000000000000
        11 => (Num => 136_507_510, Den =>  131_081_687),  -- 1.041392685158225
        12 => (Num =>  26_797_783, Den =>   24_831_587),  -- 1.079181246047624
        13 => (Num =>  73_333_297, Den =>   65_832_160),  -- 1.113943352306836
        14 => (Num => 102_941_258, Den =>   89_816_543),  -- 1.146128035678238
        15 => (Num =>  53_385_559, Den =>   45_392_361),  -- 1.176091259055681
        16 => (Num =>  78_897_839, Den =>   65_523_237)); -- 1.204119982655924

      function Scale (X : Int; R : Ratio) return Int;
      --  Compute the value of X scaled by R

      -----------
      -- Scale --
      -----------

      function Scale (X : Int; R : Ratio) return Int is
         type Wide_Int is range -2**63 .. 2**63 - 1;

      begin
         return Int (Wide_Int (X) * Wide_Int (R.Num) / Wide_Int (R.Den));
      end Scale;

   begin
      pragma Assert (U.Rbase /= 0);
      return Scale (UI_To_Int (U.Den), Logs (U.Rbase));
   end Equivalent_Decimal_Exponent;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize is
   begin
      Ureals.Init;
      UR_0       := UR_From_Components (Uint_0, Uint_1,         0, False);
      UR_M_0     := UR_From_Components (Uint_0, Uint_1,         0, True);
      UR_Half    := UR_From_Components (Uint_1, Uint_1,         2, False);
      UR_Tenth   := UR_From_Components (Uint_1, Uint_1,        10, False);
      UR_1       := UR_From_Components (Uint_1, Uint_1,         0, False);
      UR_2       := UR_From_Components (Uint_1, Uint_Minus_1,   2, False);
      UR_10      := UR_From_Components (Uint_1, Uint_Minus_1,  10, False);
      UR_10_36   := UR_From_Components (Uint_1, Uint_Minus_36, 10, False);
      UR_M_10_36 := UR_From_Components (Uint_1, Uint_Minus_36, 10, True);
      UR_100     := UR_From_Components (Uint_1, Uint_Minus_2,  10, False);
      UR_2_128   := UR_From_Components (Uint_1, Uint_Minus_128, 2, False);
      UR_2_M_128 := UR_From_Components (Uint_1, Uint_128,       2, False);
      UR_2_80    := UR_From_Components (Uint_1, Uint_Minus_80,  2, False);
      UR_2_M_80  := UR_From_Components (Uint_1, Uint_80,        2, False);
   end Initialize;

   ----------------
   -- Is_Integer --
   ----------------

   function Is_Integer (Num, Den : Uint) return Boolean is
   begin
      return (Num / Den) * Den = Num;
   end Is_Integer;

   ----------
   -- Mark --
   ----------

   function Mark return Save_Mark is
   begin
      return Save_Mark (Ureals.Last);
   end Mark;

   --------------
   -- Norm_Den --
   --------------

   function Norm_Den (Real : Ureal) return Uint is
   begin
      if not Same (Real, Normalized_Real) then
         Normalized_Real  := Real;
         Normalized_Entry := Normalize (Ureals.Table (Real));
      end if;

      return Normalized_Entry.Den;
   end Norm_Den;

   --------------
   -- Norm_Num --
   --------------

   function Norm_Num (Real : Ureal) return Uint is
   begin
      if not Same (Real, Normalized_Real) then
         Normalized_Real  := Real;
         Normalized_Entry := Normalize (Ureals.Table (Real));
      end if;

      return Normalized_Entry.Num;
   end Norm_Num;

   ---------------
   -- Normalize --
   ---------------

   function Normalize (Val : Ureal_Entry) return Ureal_Entry is
      J   : Uint;
      K   : Uint;
      Tmp : Uint;
      Num : Uint;
      Den : Uint;
      M   : constant Uintp.Save_Mark := Uintp.Mark;

   begin
      --  Start by setting J to the greatest of the absolute values of the
      --  numerator and the denominator (taking into account the base value),
      --  and K to the lesser of the two absolute values. The gcd of Num and
      --  Den is the gcd of J and K.

      if Val.Rbase = 0 then
         J := Val.Num;
         K := Val.Den;

      elsif Val.Den < 0 then
         J := Val.Num * Val.Rbase ** (-Val.Den);
         K := Uint_1;

      else
         J := Val.Num;
         K := Val.Rbase ** Val.Den;
      end if;

      Num := J;
      Den := K;

      if K > J then
         Tmp := J;
         J := K;
         K := Tmp;
      end if;

      J := UI_GCD (J, K);
      Num := Num / J;
      Den := Den / J;
      Uintp.Release_And_Save (M, Num, Den);

      --  Divide numerator and denominator by gcd and return result

      return (Num      => Num,
              Den      => Den,
              Rbase    => 0,
              Negative => Val.Negative);
   end Normalize;

   ---------------
   -- Numerator --
   ---------------

   function Numerator (Real : Ureal) return Uint is
   begin
      return Ureals.Table (Real).Num;
   end Numerator;

   --------
   -- pr --
   --------

   procedure pr (Real : Ureal) is
   begin
      UR_Write (Real);
      Write_Eol;
   end pr;

   -----------
   -- Rbase --
   -----------

   function Rbase (Real : Ureal) return Nat is
   begin
      return Ureals.Table (Real).Rbase;
   end Rbase;

   -------------
   -- Release --
   -------------

   procedure Release (M : Save_Mark) is
   begin
      Ureals.Set_Last (Ureal (M));
   end Release;

   ----------
   -- Same --
   ----------

   function Same (U1, U2 : Ureal) return Boolean is
   begin
      return Int (U1) = Int (U2);
   end Same;

   -----------------
   -- Store_Ureal --
   -----------------

   function Store_Ureal (Val : Ureal_Entry) return Ureal is
   begin
      Ureals.Append (Val);

      --  Normalize representation of signed values

      if Val.Num < 0 then
         Ureals.Table (Ureals.Last).Negative := True;
         Ureals.Table (Ureals.Last).Num := -Val.Num;
      end if;

      return Ureals.Last;
   end Store_Ureal;

   ----------------------------
   -- Store_Ureal_Normalized --
   ----------------------------

   function Store_Ureal_Normalized (Val : Ureal_Entry) return Ureal is
   begin
      return Store_Ureal (Normalize (Val));
   end Store_Ureal_Normalized;

   ---------------
   -- Tree_Read --
   ---------------

   procedure Tree_Read is
   begin
      pragma Assert (Num_Ureal_Constants = 10);

      Ureals.Tree_Read;
      Tree_Read_Int (Int (UR_0));
      Tree_Read_Int (Int (UR_M_0));
      Tree_Read_Int (Int (UR_Tenth));
      Tree_Read_Int (Int (UR_Half));
      Tree_Read_Int (Int (UR_1));
      Tree_Read_Int (Int (UR_2));
      Tree_Read_Int (Int (UR_10));
      Tree_Read_Int (Int (UR_100));
      Tree_Read_Int (Int (UR_2_128));
      Tree_Read_Int (Int (UR_2_M_128));

      --  Clear the normalization cache

      Normalized_Real := No_Ureal;
   end Tree_Read;

   ----------------
   -- Tree_Write --
   ----------------

   procedure Tree_Write is
   begin
      pragma Assert (Num_Ureal_Constants = 10);

      Ureals.Tree_Write;
      Tree_Write_Int (Int (UR_0));
      Tree_Write_Int (Int (UR_M_0));
      Tree_Write_Int (Int (UR_Tenth));
      Tree_Write_Int (Int (UR_Half));
      Tree_Write_Int (Int (UR_1));
      Tree_Write_Int (Int (UR_2));
      Tree_Write_Int (Int (UR_10));
      Tree_Write_Int (Int (UR_100));
      Tree_Write_Int (Int (UR_2_128));
      Tree_Write_Int (Int (UR_2_M_128));
   end Tree_Write;

   ------------
   -- UR_Abs --
   ------------

   function UR_Abs (Real : Ureal) return Ureal is
      Val : constant Ureal_Entry := Ureals.Table (Real);

   begin
      return Store_Ureal
               ((Num      => Val.Num,
                 Den      => Val.Den,
                 Rbase    => Val.Rbase,
                 Negative => False));
   end UR_Abs;

   ------------
   -- UR_Add --
   ------------

   function UR_Add (Left : Uint; Right : Ureal) return Ureal is
   begin
      return UR_From_Uint (Left) + Right;
   end UR_Add;

   function UR_Add (Left : Ureal; Right : Uint) return Ureal is
   begin
      return Left + UR_From_Uint (Right);
   end UR_Add;

   function UR_Add (Left : Ureal; Right : Ureal) return Ureal is
      Lval : Ureal_Entry := Ureals.Table (Left);
      Rval : Ureal_Entry := Ureals.Table (Right);
      Num  : Uint;

   begin
      --  Note, in the temporary Ureal_Entry values used in this procedure,
      --  we store the sign as the sign of the numerator (i.e. xxx.Num may
      --  be negative, even though in stored entries this can never be so)

      if Lval.Rbase /= 0 and then Lval.Rbase = Rval.Rbase then
         declare
            Opd_Min, Opd_Max   : Ureal_Entry;
            Exp_Min, Exp_Max   : Uint;

         begin
            if Lval.Negative then
               Lval.Num := (-Lval.Num);
            end if;

            if Rval.Negative then
               Rval.Num := (-Rval.Num);
            end if;

            if Lval.Den < Rval.Den then
               Exp_Min := Lval.Den;
               Exp_Max := Rval.Den;
               Opd_Min := Lval;
               Opd_Max := Rval;
            else
               Exp_Min := Rval.Den;
               Exp_Max := Lval.Den;
               Opd_Min := Rval;
               Opd_Max := Lval;
            end if;

            Num :=
              Opd_Min.Num * Lval.Rbase ** (Exp_Max - Exp_Min) + Opd_Max.Num;

            if Num = 0 then
               return Store_Ureal
                        ((Num      => Uint_0,
                          Den      => Uint_1,
                          Rbase    => 0,
                          Negative => Lval.Negative));

            else
               return Store_Ureal
                        ((Num      => abs Num,
                          Den      => Exp_Max,
                          Rbase    => Lval.Rbase,
                          Negative => (Num < 0)));
            end if;
         end;

      else
         declare
            Ln : Ureal_Entry := Normalize (Lval);
            Rn : Ureal_Entry := Normalize (Rval);

         begin
            if Ln.Negative then
               Ln.Num := (-Ln.Num);
            end if;

            if Rn.Negative then
               Rn.Num := (-Rn.Num);
            end if;

            Num := (Ln.Num * Rn.Den) + (Rn.Num * Ln.Den);

            if Num = 0 then
               return Store_Ureal
                        ((Num      => Uint_0,
                          Den      => Uint_1,
                          Rbase    => 0,
                          Negative => Lval.Negative));

            else
               return Store_Ureal_Normalized
                        ((Num      => abs Num,
                          Den      => Ln.Den * Rn.Den,
                          Rbase    => 0,
                          Negative => (Num < 0)));
            end if;
         end;
      end if;
   end UR_Add;

   ----------------
   -- UR_Ceiling --
   ----------------

   function UR_Ceiling (Real : Ureal) return Uint is
      Val : constant Ureal_Entry := Normalize (Ureals.Table (Real));
   begin
      if Val.Negative then
         return UI_Negate (Val.Num / Val.Den);
      else
         return (Val.Num + Val.Den - 1) / Val.Den;
      end if;
   end UR_Ceiling;

   ------------
   -- UR_Div --
   ------------

   function UR_Div (Left : Uint; Right : Ureal) return Ureal is
   begin
      return UR_From_Uint (Left) / Right;
   end UR_Div;

   function UR_Div (Left : Ureal; Right : Uint) return Ureal is
   begin
      return Left / UR_From_Uint (Right);
   end UR_Div;

   function UR_Div (Left, Right : Ureal) return Ureal is
      Lval : constant Ureal_Entry := Ureals.Table (Left);
      Rval : constant Ureal_Entry := Ureals.Table (Right);
      Rneg : constant Boolean     := Rval.Negative xor Lval.Negative;

   begin
      pragma Assert (Rval.Num /= Uint_0);

      if Lval.Rbase = 0 then
         if Rval.Rbase = 0 then
            return Store_Ureal_Normalized
                     ((Num      => Lval.Num * Rval.Den,
                       Den      => Lval.Den * Rval.Num,
                       Rbase    => 0,
                       Negative => Rneg));

         elsif Is_Integer (Lval.Num, Rval.Num * Lval.Den) then
            return Store_Ureal
                     ((Num      => Lval.Num / (Rval.Num * Lval.Den),
                       Den      => (-Rval.Den),
                       Rbase    => Rval.Rbase,
                       Negative => Rneg));

         elsif Rval.Den < 0 then
            return Store_Ureal_Normalized
                     ((Num      => Lval.Num,
                       Den      => Rval.Rbase ** (-Rval.Den) *
                                   Rval.Num *
                                   Lval.Den,
                       Rbase    => 0,
                       Negative => Rneg));

         else
            return Store_Ureal_Normalized
                     ((Num      => Lval.Num * Rval.Rbase ** Rval.Den,
                       Den      => Rval.Num * Lval.Den,
                       Rbase    => 0,
                       Negative => Rneg));
         end if;

      elsif Is_Integer (Lval.Num, Rval.Num) then
         if Rval.Rbase = Lval.Rbase then
            return Store_Ureal
                     ((Num      => Lval.Num / Rval.Num,
                       Den      => Lval.Den - Rval.Den,
                       Rbase    => Lval.Rbase,
                       Negative => Rneg));

         elsif Rval.Rbase = 0 then
            return Store_Ureal
                     ((Num      => (Lval.Num / Rval.Num) * Rval.Den,
                       Den      => Lval.Den,
                       Rbase    => Lval.Rbase,
                       Negative => Rneg));

         elsif Rval.Den < 0 then
            declare
               Num, Den : Uint;

            begin
               if Lval.Den < 0 then
                  Num := (Lval.Num / Rval.Num) * (Lval.Rbase ** (-Lval.Den));
                  Den := Rval.Rbase ** (-Rval.Den);
               else
                  Num := Lval.Num / Rval.Num;
                  Den := (Lval.Rbase ** Lval.Den) *
                         (Rval.Rbase ** (-Rval.Den));
               end if;

               return Store_Ureal
                        ((Num      => Num,
                          Den      => Den,
                          Rbase    => 0,
                          Negative => Rneg));
            end;

         else
            return Store_Ureal
                     ((Num      => (Lval.Num / Rval.Num) *
                                   (Rval.Rbase ** Rval.Den),
                       Den      => Lval.Den,
                       Rbase    => Lval.Rbase,
                       Negative => Rneg));
         end if;

      else
         declare
            Num, Den : Uint;

         begin
            if Lval.Den < 0 then
               Num := Lval.Num * (Lval.Rbase ** (-Lval.Den));
               Den := Rval.Num;
            else
               Num := Lval.Num;
               Den := Rval.Num * (Lval.Rbase ** Lval.Den);
            end if;

            if Rval.Rbase /= 0 then
               if Rval.Den < 0 then
                  Den := Den * (Rval.Rbase ** (-Rval.Den));
               else
                  Num := Num * (Rval.Rbase ** Rval.Den);
               end if;

            else
               Num := Num * Rval.Den;
            end if;

            return Store_Ureal_Normalized
                     ((Num      => Num,
                       Den      => Den,
                       Rbase    => 0,
                       Negative => Rneg));
         end;
      end if;
   end UR_Div;

   -----------
   -- UR_Eq --
   -----------

   function UR_Eq (Left, Right : Ureal) return Boolean is
   begin
      return not UR_Ne (Left, Right);
   end UR_Eq;

   ---------------------
   -- UR_Exponentiate --
   ---------------------

   function UR_Exponentiate (Real : Ureal; N : Uint) return Ureal is
      X    : constant Uint := abs N;
      Bas  : Ureal;
      Val  : Ureal_Entry;
      Neg  : Boolean;
      IBas : Uint;

   begin
      --  If base is negative, then the resulting sign depends on whether
      --  the exponent is even or odd (even => positive, odd = negative)

      if UR_Is_Negative (Real) then
         Neg := (N mod 2) /= 0;
         Bas := UR_Negate (Real);
      else
         Neg := False;
         Bas := Real;
      end if;

      Val := Ureals.Table (Bas);

      --  If the base is a small integer, then we can return the result in
      --  exponential form, which can save a lot of time for junk exponents.

      IBas := UR_Trunc (Bas);

      if IBas <= 16
        and then UR_From_Uint (IBas) = Bas
      then
         return Store_Ureal
                  ((Num      => Uint_1,
                    Den      => -N,
                    Rbase    => UI_To_Int (UR_Trunc (Bas)),
                    Negative => Neg));

      --  If the exponent is negative then we raise the numerator and the
      --  denominator (after normalization) to the absolute value of the
      --  exponent and we return the reciprocal. An assert error will happen
      --  if the numerator is zero.

      elsif N < 0 then
         pragma Assert (Val.Num /= 0);
         Val := Normalize (Val);

         return Store_Ureal
                  ((Num      => Val.Den ** X,
                    Den      => Val.Num ** X,
                    Rbase    => 0,
                    Negative => Neg));

      --  If positive, we distinguish the case when the base is not zero, in
      --  which case the new denominator is just the product of the old one
      --  with the exponent,

      else
         if Val.Rbase /= 0 then

            return Store_Ureal
                     ((Num      => Val.Num ** X,
                       Den      => Val.Den * X,
                       Rbase    => Val.Rbase,
                       Negative => Neg));

         --  And when the base is zero, in which case we exponentiate
         --  the old denominator.

         else
            return Store_Ureal
                     ((Num      => Val.Num ** X,
                       Den      => Val.Den ** X,
                       Rbase    => 0,
                       Negative => Neg));
         end if;
      end if;
   end UR_Exponentiate;

   --------------
   -- UR_Floor --
   --------------

   function UR_Floor (Real : Ureal) return Uint is
      Val : constant Ureal_Entry := Normalize (Ureals.Table (Real));
   begin
      if Val.Negative then
         return UI_Negate ((Val.Num + Val.Den - 1) / Val.Den);
      else
         return Val.Num / Val.Den;
      end if;
   end UR_Floor;

   ------------------------
   -- UR_From_Components --
   ------------------------

   function UR_From_Components
     (Num      : Uint;
      Den      : Uint;
      Rbase    : Nat := 0;
      Negative : Boolean := False)
      return     Ureal
   is
   begin
      return Store_Ureal
               ((Num      => Num,
                 Den      => Den,
                 Rbase    => Rbase,
                 Negative => Negative));
   end UR_From_Components;

   ------------------
   -- UR_From_Uint --
   ------------------

   function UR_From_Uint (UI : Uint) return Ureal is
   begin
      return UR_From_Components
               (abs UI, Uint_1, Negative => (UI < 0));
   end UR_From_Uint;

   -----------
   -- UR_Ge --
   -----------

   function UR_Ge (Left, Right : Ureal) return Boolean is
   begin
      return not (Left < Right);
   end UR_Ge;

   -----------
   -- UR_Gt --
   -----------

   function UR_Gt (Left, Right : Ureal) return Boolean is
   begin
      return (Right < Left);
   end UR_Gt;

   --------------------
   -- UR_Is_Negative --
   --------------------

   function UR_Is_Negative (Real : Ureal) return Boolean is
   begin
      return Ureals.Table (Real).Negative;
   end UR_Is_Negative;

   --------------------
   -- UR_Is_Positive --
   --------------------

   function UR_Is_Positive (Real : Ureal) return Boolean is
   begin
      return not Ureals.Table (Real).Negative
        and then Ureals.Table (Real).Num /= 0;
   end UR_Is_Positive;

   ----------------
   -- UR_Is_Zero --
   ----------------

   function UR_Is_Zero (Real : Ureal) return Boolean is
   begin
      return Ureals.Table (Real).Num = 0;
   end UR_Is_Zero;

   -----------
   -- UR_Le --
   -----------

   function UR_Le (Left, Right : Ureal) return Boolean is
   begin
      return not (Right < Left);
   end UR_Le;

   -----------
   -- UR_Lt --
   -----------

   function UR_Lt (Left, Right : Ureal) return Boolean is
   begin
      --  An operand is not less than itself

      if Same (Left, Right) then
         return False;

      --  Deal with zero cases

      elsif UR_Is_Zero (Left) then
         return UR_Is_Positive (Right);

      elsif UR_Is_Zero (Right) then
         return Ureals.Table (Left).Negative;

      --  Different signs are decisive (note we dealt with zero cases)

      elsif Ureals.Table (Left).Negative
        and then not Ureals.Table (Right).Negative
      then
         return True;

      elsif not Ureals.Table (Left).Negative
        and then Ureals.Table (Right).Negative
      then
         return False;

      --  Signs are same, do rapid check based on worst case estimates of
      --  decimal exponent, which will often be decisive. Precise test
      --  depends on whether operands are positive or negative.

      elsif Decimal_Exponent_Hi (Left) < Decimal_Exponent_Lo (Right) then
         return UR_Is_Positive (Left);

      elsif Decimal_Exponent_Lo (Left) > Decimal_Exponent_Hi (Right) then
         return UR_Is_Negative (Left);

      --  If we fall through, full gruesome test is required. This happens
      --  if the numbers are close together, or in some weird (/=10) base.

      else
         declare
            Imrk   : constant Uintp.Save_Mark  := Mark;
            Rmrk   : constant Urealp.Save_Mark := Mark;
            Lval   : Ureal_Entry;
            Rval   : Ureal_Entry;
            Result : Boolean;

         begin
            Lval := Ureals.Table (Left);
            Rval := Ureals.Table (Right);

            --  An optimization. If both numbers are based, then subtract
            --  common value of base to avoid unnecessarily giant numbers

            if Lval.Rbase = Rval.Rbase and then Lval.Rbase /= 0 then
               if Lval.Den < Rval.Den then
                  Rval.Den := Rval.Den - Lval.Den;
                  Lval.Den := Uint_0;
               else
                  Lval.Den := Lval.Den - Rval.Den;
                  Rval.Den := Uint_0;
               end if;
            end if;

            Lval := Normalize (Lval);
            Rval := Normalize (Rval);

            if Lval.Negative then
               Result := (Lval.Num * Rval.Den) > (Rval.Num * Lval.Den);
            else
               Result := (Lval.Num * Rval.Den) < (Rval.Num * Lval.Den);
            end if;

            Release (Imrk);
            Release (Rmrk);
            return Result;
         end;
      end if;
   end UR_Lt;

   ------------
   -- UR_Max --
   ------------

   function UR_Max (Left, Right : Ureal) return Ureal is
   begin
      if Left >= Right then
         return Left;
      else
         return Right;
      end if;
   end UR_Max;

   ------------
   -- UR_Min --
   ------------

   function UR_Min (Left, Right : Ureal) return Ureal is
   begin
      if Left <= Right then
         return Left;
      else
         return Right;
      end if;
   end UR_Min;

   ------------
   -- UR_Mul --
   ------------

   function UR_Mul (Left : Uint; Right : Ureal) return Ureal is
   begin
      return UR_From_Uint (Left) * Right;
   end UR_Mul;

   function UR_Mul (Left : Ureal; Right : Uint) return Ureal is
   begin
      return Left * UR_From_Uint (Right);
   end UR_Mul;

   function UR_Mul (Left, Right : Ureal) return Ureal is
      Lval : constant Ureal_Entry := Ureals.Table (Left);
      Rval : constant Ureal_Entry := Ureals.Table (Right);
      Num  : Uint                 := Lval.Num * Rval.Num;
      Den  : Uint;
      Rneg : constant Boolean     := Lval.Negative xor Rval.Negative;

   begin
      if Lval.Rbase = 0 then
         if Rval.Rbase = 0 then
            return Store_Ureal_Normalized
                     ((Num      => Num,
                       Den      => Lval.Den * Rval.Den,
                       Rbase    => 0,
                       Negative => Rneg));

         elsif Is_Integer (Num, Lval.Den) then
            return Store_Ureal
                     ((Num      => Num / Lval.Den,
                       Den      => Rval.Den,
                       Rbase    => Rval.Rbase,
                       Negative => Rneg));

         elsif Rval.Den < 0 then
            return Store_Ureal_Normalized
                     ((Num      => Num * (Rval.Rbase ** (-Rval.Den)),
                       Den      => Lval.Den,
                       Rbase    => 0,
                       Negative => Rneg));

         else
            return Store_Ureal_Normalized
                     ((Num      => Num,
                       Den      => Lval.Den * (Rval.Rbase ** Rval.Den),
                       Rbase    => 0,
                       Negative => Rneg));
         end if;

      elsif Lval.Rbase = Rval.Rbase then
         return Store_Ureal
                  ((Num      => Num,
                    Den      => Lval.Den + Rval.Den,
                    Rbase    => Lval.Rbase,
                    Negative => Rneg));

      elsif Rval.Rbase = 0 then
         if Is_Integer (Num, Rval.Den) then
            return Store_Ureal
                     ((Num      => Num / Rval.Den,
                       Den      => Lval.Den,
                       Rbase    => Lval.Rbase,
                       Negative => Rneg));

         elsif Lval.Den < 0 then
            return Store_Ureal_Normalized
                     ((Num      => Num * (Lval.Rbase ** (-Lval.Den)),
                       Den      => Rval.Den,
                       Rbase    => 0,
                       Negative => Rneg));

         else
            return Store_Ureal_Normalized
                     ((Num      => Num,
                       Den      => Rval.Den * (Lval.Rbase ** Lval.Den),
                       Rbase    => 0,
                       Negative => Rneg));
         end if;

      else
         Den := Uint_1;

         if Lval.Den < 0 then
            Num := Num * (Lval.Rbase ** (-Lval.Den));
         else
            Den := Den * (Lval.Rbase ** Lval.Den);
         end if;

         if Rval.Den < 0 then
            Num := Num * (Rval.Rbase ** (-Rval.Den));
         else
            Den := Den * (Rval.Rbase ** Rval.Den);
         end if;

         return Store_Ureal_Normalized
                  ((Num      => Num,
                    Den      => Den,
                    Rbase    => 0,
                    Negative => Rneg));
      end if;
   end UR_Mul;

   -----------
   -- UR_Ne --
   -----------

   function UR_Ne (Left, Right : Ureal) return Boolean is
   begin
      --  Quick processing for case of identical Ureal values (note that
      --  this also deals with comparing two No_Ureal values).

      if Same (Left, Right) then
         return False;

      --  Deal with case of one or other operand is No_Ureal, but not both

      elsif Same (Left, No_Ureal) or else Same (Right, No_Ureal) then
         return True;

      --  Do quick check based on number of decimal digits

      elsif Decimal_Exponent_Hi (Left) < Decimal_Exponent_Lo (Right) or else
            Decimal_Exponent_Lo (Left) > Decimal_Exponent_Hi (Right)
      then
         return True;

      --  Otherwise full comparison is required

      else
         declare
            Imrk   : constant Uintp.Save_Mark  := Mark;
            Rmrk   : constant Urealp.Save_Mark := Mark;
            Lval   : constant Ureal_Entry := Normalize (Ureals.Table (Left));
            Rval   : constant Ureal_Entry := Normalize (Ureals.Table (Right));
            Result : Boolean;

         begin
            if UR_Is_Zero (Left) then
               return not UR_Is_Zero (Right);

            elsif UR_Is_Zero (Right) then
               return not UR_Is_Zero (Left);

            --  Both operands are non-zero

            else
               Result :=
                  Rval.Negative /= Lval.Negative
                    or else Rval.Num /= Lval.Num
                    or else Rval.Den /= Lval.Den;
               Release (Imrk);
               Release (Rmrk);
               return Result;
            end if;
         end;
      end if;
   end UR_Ne;

   ---------------
   -- UR_Negate --
   ---------------

   function UR_Negate (Real : Ureal) return Ureal is
   begin
      return Store_Ureal
               ((Num      => Ureals.Table (Real).Num,
                 Den      => Ureals.Table (Real).Den,
                 Rbase    => Ureals.Table (Real).Rbase,
                 Negative => not Ureals.Table (Real).Negative));
   end UR_Negate;

   ------------
   -- UR_Sub --
   ------------

   function UR_Sub (Left : Uint; Right : Ureal) return Ureal is
   begin
      return UR_From_Uint (Left) + UR_Negate (Right);
   end UR_Sub;

   function UR_Sub (Left : Ureal; Right : Uint) return Ureal is
   begin
      return Left + UR_From_Uint (-Right);
   end UR_Sub;

   function UR_Sub (Left, Right : Ureal) return Ureal is
   begin
      return Left + UR_Negate (Right);
   end UR_Sub;

   ----------------
   -- UR_To_Uint --
   ----------------

   function UR_To_Uint (Real : Ureal) return Uint is
      Val : constant Ureal_Entry := Normalize (Ureals.Table (Real));
      Res : Uint;

   begin
      Res := (Val.Num + (Val.Den / 2)) / Val.Den;

      if Val.Negative then
         return UI_Negate (Res);
      else
         return Res;
      end if;
   end UR_To_Uint;

   --------------
   -- UR_Trunc --
   --------------

   function UR_Trunc (Real : Ureal) return Uint is
      Val : constant Ureal_Entry := Normalize (Ureals.Table (Real));
   begin
      if Val.Negative then
         return -(Val.Num / Val.Den);
      else
         return Val.Num / Val.Den;
      end if;
   end UR_Trunc;

   --------------
   -- UR_Write --
   --------------

   procedure UR_Write (Real : Ureal; Brackets : Boolean := False) is
      Val : constant Ureal_Entry := Ureals.Table (Real);
      T   : Uint;

   begin
      --  If value is negative, we precede the constant by a minus sign

      if Val.Negative then
         Write_Char ('-');
      end if;

      --  Zero is zero

      if Val.Num = 0 then
         Write_Str ("0.0");

      --  For constants with a denominator of zero, the value is simply the
      --  numerator value, since we are dividing by base**0, which is 1.

      elsif Val.Den = 0 then
         UI_Write (Val.Num, Decimal);
         Write_Str (".0");

      --  Small powers of 2 get written in decimal fixed-point format

      elsif Val.Rbase = 2
        and then Val.Den <= 3
        and then Val.Den >= -16
      then
         if Val.Den = 1 then
            T := Val.Num * (10 / 2);
            UI_Write (T / 10, Decimal);
            Write_Char ('.');
            UI_Write (T mod 10, Decimal);

         elsif Val.Den = 2 then
            T := Val.Num * (100 / 4);
            UI_Write (T / 100, Decimal);
            Write_Char ('.');
            UI_Write (T mod 100 / 10, Decimal);

            if T mod 10 /= 0 then
               UI_Write (T mod 10, Decimal);
            end if;

         elsif Val.Den = 3 then
            T := Val.Num * (1000 / 8);
            UI_Write (T / 1000, Decimal);
            Write_Char ('.');
            UI_Write (T mod 1000 / 100, Decimal);

            if T mod 100 /= 0 then
               UI_Write (T mod 100 / 10, Decimal);

               if T mod 10 /= 0 then
                  UI_Write (T mod 10, Decimal);
               end if;
            end if;

         else
            UI_Write (Val.Num * (Uint_2 ** (-Val.Den)), Decimal);
            Write_Str (".0");
         end if;

      --  Constants in base 10 or 16 can be written in normal Ada literal
      --  style, as long as they fit in the UI_Image_Buffer. Using hexadecimal
      --  notation, 4 bytes are required for the 16# # part, and every fifth
      --  character is an underscore. So, a buffer of size N has room for
      --     ((N - 4) - (N - 4) / 5) * 4 bits,
      --  or at least
      --     N * 16 / 5 - 12 bits.

      elsif (Val.Rbase = 10 or else Val.Rbase = 16)
        and then Num_Bits (Val.Num) < UI_Image_Buffer'Length * 16 / 5 - 12
      then
         pragma Assert (Val.Den /= 0);

         --  Use fixed-point format for small scaling values

         if (Val.Rbase = 10 and then Val.Den < 0 and then Val.Den > -3)
              or else (Val.Rbase = 16 and then Val.Den = -1)
         then
            UI_Write (Val.Num * Val.Rbase**(-Val.Den), Decimal);
            Write_Str (".0");

         --  Write hexadecimal constants in exponential notation with a zero
         --  unit digit. This matches the Ada canonical form for floating point
         --  numbers, and also ensures that the underscores end up in the
         --  correct place.

         elsif Val.Rbase = 16 then
            UI_Image (Val.Num, Hex);
            pragma Assert (Val.Rbase = 16);

            Write_Str ("16#0.");
            Write_Str (UI_Image_Buffer (4 .. UI_Image_Length));

            --  For exponent, exclude 16# # and underscores from length

            UI_Image_Length := UI_Image_Length - 4;
            UI_Image_Length := UI_Image_Length - UI_Image_Length / 5;

            Write_Char ('E');
            UI_Write (Int (UI_Image_Length) - Val.Den, Decimal);

         elsif Val.Den = 1 then
            UI_Write (Val.Num / 10, Decimal);
            Write_Char ('.');
            UI_Write (Val.Num mod 10, Decimal);

         elsif Val.Den = 2 then
            UI_Write (Val.Num / 100, Decimal);
            Write_Char ('.');
            UI_Write (Val.Num / 10 mod 10, Decimal);
            UI_Write (Val.Num mod 10, Decimal);

         --  Else use decimal exponential format

         else
            --  Write decimal constants with a non-zero unit digit. This
            --  matches usual scientific notation.

            UI_Image (Val.Num, Decimal);
            Write_Char (UI_Image_Buffer (1));
            Write_Char ('.');

            if UI_Image_Length = 1 then
               Write_Char ('0');
            else
               Write_Str (UI_Image_Buffer (2 .. UI_Image_Length));
            end if;

            Write_Char ('E');
            UI_Write (Int (UI_Image_Length - 1) - Val.Den, Decimal);
         end if;

      --  Constants in a base other than 10 can still be easily written in
      --  normal Ada literal style if the numerator is one.

      elsif Val.Rbase /= 0 and then Val.Num = 1 then
         Write_Int (Val.Rbase);
         Write_Str ("#1.0#E");
         UI_Write (-Val.Den);

      --  Other constants with a base other than 10 are written using one of
      --  the following forms, depending on the sign of the number and the
      --  sign of the exponent (= minus denominator value). See that we are
      --  replacing the division by a multiplication (updating accordingly the
      --  sign of the exponent) to generate an expression whose computation
      --  does not cause a division by 0 when base**exponent is very small.

      --    numerator.0*base**exponent
      --    numerator.0*base**-exponent

      --  And of course an exponent of 0 can be omitted.

      elsif Val.Rbase /= 0 then
         if Brackets then
            Write_Char ('[');
         end if;

         UI_Write (Val.Num, Decimal);
         Write_Str (".0");

         if Val.Den /= 0 then
            Write_Char ('*');
            Write_Int (Val.Rbase);
            Write_Str ("**");

            if Val.Den <= 0 then
               UI_Write (-Val.Den, Decimal);
            else
               Write_Str ("(-");
               UI_Write (Val.Den, Decimal);
               Write_Char (')');
            end if;
         end if;

         if Brackets then
            Write_Char (']');
         end if;

      --  Rationals where numerator is divisible by denominator can be output
      --  as literals after we do the division. This includes the common case
      --  where the denominator is 1.

      elsif Val.Num mod Val.Den = 0 then
         UI_Write (Val.Num / Val.Den, Decimal);
         Write_Str (".0");

      --  Other non-based (rational) constants are written in num/den style

      else
         if Brackets then
            Write_Char ('[');
         end if;

         UI_Write (Val.Num, Decimal);
         Write_Str (".0/");
         UI_Write (Val.Den, Decimal);
         Write_Str (".0");

         if Brackets then
            Write_Char (']');
         end if;
      end if;
   end UR_Write;

   -------------
   -- Ureal_0 --
   -------------

   function Ureal_0 return Ureal is
   begin
      return UR_0;
   end Ureal_0;

   -------------
   -- Ureal_1 --
   -------------

   function Ureal_1 return Ureal is
   begin
      return UR_1;
   end Ureal_1;

   -------------
   -- Ureal_2 --
   -------------

   function Ureal_2 return Ureal is
   begin
      return UR_2;
   end Ureal_2;

   --------------
   -- Ureal_10 --
   --------------

   function Ureal_10 return Ureal is
   begin
      return UR_10;
   end Ureal_10;

   ---------------
   -- Ureal_100 --
   ---------------

   function Ureal_100 return Ureal is
   begin
      return UR_100;
   end Ureal_100;

   -----------------
   -- Ureal_10_36 --
   -----------------

   function Ureal_10_36 return Ureal is
   begin
      return UR_10_36;
   end Ureal_10_36;

   ----------------
   -- Ureal_2_80 --
   ----------------

   function Ureal_2_80 return Ureal is
   begin
      return UR_2_80;
   end Ureal_2_80;

   -----------------
   -- Ureal_2_128 --
   -----------------

   function Ureal_2_128 return Ureal is
   begin
      return UR_2_128;
   end Ureal_2_128;

   -------------------
   -- Ureal_2_M_80 --
   -------------------

   function Ureal_2_M_80 return Ureal is
   begin
      return UR_2_M_80;
   end Ureal_2_M_80;

   -------------------
   -- Ureal_2_M_128 --
   -------------------

   function Ureal_2_M_128 return Ureal is
   begin
      return UR_2_M_128;
   end Ureal_2_M_128;

   ----------------
   -- Ureal_Half --
   ----------------

   function Ureal_Half return Ureal is
   begin
      return UR_Half;
   end Ureal_Half;

   ---------------
   -- Ureal_M_0 --
   ---------------

   function Ureal_M_0 return Ureal is
   begin
      return UR_M_0;
   end Ureal_M_0;

   -------------------
   -- Ureal_M_10_36 --
   -------------------

   function Ureal_M_10_36 return Ureal is
   begin
      return UR_M_10_36;
   end Ureal_M_10_36;

   -----------------
   -- Ureal_Tenth --
   -----------------

   function Ureal_Tenth return Ureal is
   begin
      return UR_Tenth;
   end Ureal_Tenth;

end Urealp;
