1------------------------------------------------------------------------------
2--                                                                          --
3--                         GNAT RUN-TIME COMPONENTS                         --
4--                                                                          --
5--                        S Y S T E M . F O R E _ F                         --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--            Copyright (C) 2020, Free Software Foundation, Inc.            --
10--                                                                          --
11-- GNAT is free software;  you can  redistribute it  and/or modify it under --
12-- terms of the  GNU General Public License as published  by the Free Soft- --
13-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
14-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
15-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
16-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
17--                                                                          --
18-- As a special exception under Section 7 of GPL version 3, you are granted --
19-- additional permissions described in the GCC Runtime Library Exception,   --
20-- version 3.1, as published by the Free Software Foundation.               --
21--                                                                          --
22-- You should have received a copy of the GNU General Public License and    --
23-- a copy of the GCC Runtime Library Exception along with this program;     --
24-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
25-- <http://www.gnu.org/licenses/>.                                          --
26--                                                                          --
27-- GNAT was originally developed  by the GNAT team at  New York University. --
28-- Extensive contributions were provided by Ada Core Technologies Inc.      --
29--                                                                          --
30------------------------------------------------------------------------------
31
32package body System.Fore_F is
33
34   Maxdigs : constant Natural := Int'Width - 2;
35   --  Maximum number of decimal digits that can be represented in an Int.
36   --  The "-2" accounts for the sign and one extra digit, since we need the
37   --  maximum number of 9's that can be represented, e.g. for the 64-bit case,
38   --  Integer_64'Width is 20 since the maximum value is approximately 9.2E+18
39   --  and has 19 digits, but the maximum number of 9's that can be represented
40   --  in Integer_64 is only 18.
41
42   --  The first prerequisite of the implementation is that the scaled divide
43   --  does not overflow, which means that the absolute value of the bounds of
44   --  the subtype must be smaller than 10**Maxdigs * 2**(Int'Size - 1).
45   --  Otherwise Constraint_Error is raised by the scaled divide operation.
46
47   --  The second prerequisite is that the computation of the operands does not
48   --  overflow, which means that, if the small is larger than 1, it is either
49   --  an integer or its numerator and denominator must be both smaller than
50   --  the power 10**(Maxdigs - 1).
51
52   ----------------
53   -- Fore_Fixed --
54   ----------------
55
56   function Fore_Fixed (Lo, Hi, Num, Den : Int; Scale : Integer) return Natural
57   is
58      pragma Assert (Num < 0 and then Den < 0);
59      --  Accept only negative numbers to allow -2**(Int'Size - 1)
60
61      function Negative_Abs (Val : Int) return Int is
62        (if Val <= 0 then Val else -Val);
63      --  Return the opposite of the absolute value of Val
64
65      T : Int := Int'Min (Negative_Abs (Lo), Negative_Abs (Hi));
66      F : Natural;
67
68      Q, R : Int;
69
70   begin
71      --  Initial value of 2 allows for sign and mandatory single digit
72
73      F := 2;
74
75      --  The easy case is when Num is not larger than Den in magnitude,
76      --  i.e. if S = Num / Den, then S <= 1, in which case we can just
77      --  compute the product Q = T * S.
78
79      if Num >= Den then
80         Scaled_Divide (T, Num, Den, Q, R, Round => False);
81         T := Q;
82
83      --  Otherwise S > 1 and thus Scale <= 0, compute Q and R such that
84
85      --    T * Num = Q * (Den * 10**(-D)) + R
86
87      --  with
88
89      --    D = Integer'Max (-Maxdigs, Scale - 1)
90
91      --  then reason on Q if it is non-zero or else on R / Den.
92
93      --  This works only if Den * 10**(-D) does not overflow, which is true
94      --  if Den = 1. Suppose that Num corresponds to the maximum value of -D,
95      --  i.e. Maxdigs and 10**(-D) = 10**Maxdigs. If you change Den into 10,
96      --  then S becomes 10 times smaller and, therefore, Scale is incremented
97      --  by 1, which means that -D is decremented by 1 provided that Scale was
98      --  initially not smaller than 1 - Maxdigs, so the multiplication still
99      --  does not overflow. But you need to reach 10 to trigger this effect,
100      --  which means that a leeway of 10 is required, so let's restrict this
101      --  to a Num for which 10**(-D) <= 10**(Maxdigs - 1). To sum up, if S is
102      --  the ratio of two integers with
103
104      --    1 < Den < Num <= B
105
106      --  where B is a fixed limit, then the multiplication does not overflow.
107      --  B can be taken as the largest integer Small such that D = 1 - Maxdigs
108      --  i.e. such that Scale = 2 - Maxdigs, which is 10**(Maxdigs - 1) - 1.
109
110      else
111         declare
112            D : constant Integer := Integer'Max (-Maxdigs, Scale - 1);
113
114         begin
115            Scaled_Divide (T, Num, Den * 10**(-D), Q, R, Round => False);
116
117            if Q /= 0 then
118               T := Q;
119               F := F - D;
120            else
121               T := R / Den;
122            end if;
123         end;
124      end if;
125
126      --  Loop to increase Fore as needed to include full range of values
127
128      while T <= -10 or else T >= 10 loop
129         T := T / 10;
130         F := F + 1;
131      end loop;
132
133      return F;
134   end Fore_Fixed;
135
136end System.Fore_F;
137