1------------------------------------------------------------------------------
2--                                                                          --
3--                         GNAT RUN-TIME COMPONENTS                         --
4--                                                                          --
5--                 A D A . T E X T _ I O . F I X E D _ I O                  --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--            Copyright (C) 2020-2021, 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
32--  -------------------
33--  - Fixed point I/O -
34--  -------------------
35
36--  The following text documents implementation details of the fixed point
37--  input/output routines in the GNAT runtime. The first part describes the
38--  general properties of fixed point types as defined by the Ada standard,
39--  including the Information Systems Annex.
40
41--  Subsequently these are reduced to implementation constraints and the impact
42--  of these constraints on a few possible approaches to input/output is given.
43--  Based on this analysis, a specific implementation is selected for use in
44--  the GNAT runtime. Finally the chosen algorithms are analyzed numerically in
45--  order to provide user-level documentation on limits for range and precision
46--  of fixed point types as well as accuracy of input/output conversions.
47
48--  -------------------------------------------
49--  - General Properties of Fixed Point Types -
50--  -------------------------------------------
51
52--  Operations on fixed point types, other than input/output, are not important
53--  for the purpose of this document. Only the set of values that a fixed point
54--  type can represent and the input/output operations are significant.
55
56--  Values
57--  ------
58
59--  The set of values of a fixed point type comprise the integral multiples of
60--  a number called the small of the type. The small can be either a power of
61--  two, a power of ten or (if the implementation allows) an arbitrary strictly
62--  positive real value.
63
64--  Implementations need to support ordinary fixed point types with a precision
65--  of at least 24 bits, and (in order to comply with the Information Systems
66--  Annex) decimal fixed point types with at least 18 digits. For the rest, no
67--  requirements exist for the minimal small and range that must be supported.
68
69--  Operations
70--  ----------
71
72--  [Wide_[Wide_]]Image attribute (see RM 3.5(27.1/2))
73
74--          These attributes return a decimal real literal best approximating
75--          the value (rounded away from zero if halfway between) with a
76--          single leading character that is either a minus sign or a space,
77--          one or more digits before the decimal point (with no redundant
78--          leading zeros), a decimal point, and N digits after the decimal
79--          point. For a subtype S, the value of N is S'Aft, the smallest
80--          positive integer such that (10**N)*S'Delta is greater or equal to
81--          one, see RM 3.5.10(5).
82
83--          For an arbitrary small, this means large number arithmetic needs
84--          to be performed.
85
86--  Put (see RM A.10.9(22-26))
87
88--          The requirements for Put add no extra constraints over the image
89--          attributes, although it would be nice to be able to output more
90--          than S'Aft digits after the decimal point for values of subtype S.
91
92--  [Wide_[Wide_]]Value attribute (RM 3.5(39.1/2))
93
94--          Since the input can be given in any base in the range 2..16,
95--          accurate conversion to a fixed point number may require
96--          arbitrary precision arithmetic if there is no limit on the
97--          magnitude of the small of the fixed point type.
98
99--  Get (see RM A.10.9(12-21))
100
101--          The requirements for Get are identical to those of the Value
102--          attribute.
103
104--  ------------------------------
105--  - Implementation Constraints -
106--  ------------------------------
107
108--  The requirements listed above for the input/output operations lead to
109--  significant complexity, if no constraints are put on supported smalls.
110
111--  Implementation Strategies
112--  -------------------------
113
114--  * Floating point arithmetic
115--  * Arbitrary-precision integer arithmetic
116--  * Fixed-precision integer arithmetic
117
118--  Although it seems convenient to convert fixed point numbers to floating
119--  point and then print them, this leads to a number of restrictions.
120--  The first one is precision. The widest floating-point type generally
121--  available has 53 bits of mantissa. This means that Fine_Delta cannot
122--  be less than 2.0**(-53).
123
124--  In GNAT, Fine_Delta is 2.0**(-127), and Duration for example is a 64-bit
125--  type. This means that a floating-point type with 128 bits of mantissa needs
126--  to be used, which currently does not exist in any common architecture. It
127--  would still be possible to use multi-precision floating point to perform
128--  calculations using longer mantissas, but this is a much harder approach.
129
130--  The base conversions needed for input/output of (non-decimal) fixed point
131--  types can be seen as pairs of integer multiplications and divisions.
132
133--  Arbitrary-precision integer arithmetic would be suitable for the job at
134--  hand, but has the drawback that it is very heavy implementation-wise.
135--  Especially in embedded systems, where fixed point types are often used,
136--  it may not be desirable to require large amounts of storage and time
137--  for fixed I/O operations.
138
139--  Fixed-precision integer arithmetic has the advantage of simplicity and
140--  speed. For the most common fixed point types this would be a perfect
141--  solution. The downside however may be a restricted set of acceptable
142--  fixed point types.
143
144--  Implementation Choices
145--  ----------------------
146
147--  The current implementation in the GNAT runtime uses fixed-precision integer
148--  arithmetic for fixed point types whose Small is the ratio of two integers
149--  whose magnitude is bounded relatively to the size of the mantissa, with a
150--  three-tiered approach for 32-bit, 64-bit and 128-bit fixed point types. For
151--  other fixed point types, the implementation uses floating-point arithmetic.
152
153--  The exact requirements of the algorithms are analyzed and documented along
154--  with the implementation in their respective units.
155
156with Interfaces;
157with Ada.Text_IO.Fixed_Aux;
158with Ada.Text_IO.Float_Aux;
159with System.Img_Fixed_32;  use System.Img_Fixed_32;
160with System.Img_Fixed_64;  use System.Img_Fixed_64;
161with System.Img_Fixed_128; use System.Img_Fixed_128;
162with System.Img_LFlt;      use System.Img_LFlt;
163with System.Val_Fixed_32;  use System.Val_Fixed_32;
164with System.Val_Fixed_64;  use System.Val_Fixed_64;
165with System.Val_Fixed_128; use System.Val_Fixed_128;
166with System.Val_LFlt;      use System.Val_LFlt;
167
168package body Ada.Text_IO.Fixed_IO with SPARK_Mode => Off is
169
170   --  Note: we still use the floating-point I/O routines for types whose small
171   --  is not the ratio of two sufficiently small integers. This will result in
172   --  inaccuracies for fixed point types that require more precision than is
173   --  available in Long_Float.
174
175   subtype Int32  is Interfaces.Integer_32;  use type Int32;
176   subtype Int64  is Interfaces.Integer_64;  use type Int64;
177   subtype Int128 is Interfaces.Integer_128; use type Int128;
178
179   package Aux32 is new
180     Ada.Text_IO.Fixed_Aux (Int32, Scan_Fixed32, Set_Image_Fixed32);
181
182   package Aux64 is new
183     Ada.Text_IO.Fixed_Aux (Int64, Scan_Fixed64, Set_Image_Fixed64);
184
185   package Aux128 is new
186     Ada.Text_IO.Fixed_Aux (Int128, Scan_Fixed128, Set_Image_Fixed128);
187
188   package Aux_Long_Float is new
189     Ada.Text_IO.Float_Aux (Long_Float, Scan_Long_Float, Set_Image_Long_Float);
190
191   --  Throughout this generic body, we distinguish between the case where type
192   --  Int32 is OK, where type Int64 is OK and where type Int128 is OK. These
193   --  boolean constants are used to test for this, such that only code for the
194   --  relevant case is included in the instance; that's why the computation of
195   --  their value must be fully static (although it is not a static expression
196   --  in the RM sense).
197
198   OK_Get_32 : constant Boolean :=
199     Num'Base'Object_Size <= 32
200       and then
201         ((Num'Small_Numerator = 1 and then Num'Small_Denominator <= 2**31)
202           or else
203          (Num'Small_Denominator = 1 and then Num'Small_Numerator <= 2**31)
204           or else
205          (Num'Small_Numerator <= 2**27
206            and then Num'Small_Denominator <= 2**27));
207   --  These conditions are derived from the prerequisites of System.Value_F
208
209   OK_Put_32 : constant Boolean :=
210     Num'Base'Object_Size <= 32
211       and then
212         ((Num'Small_Numerator = 1 and then Num'Small_Denominator <= 2**31)
213           or else
214          (Num'Small_Denominator = 1 and then Num'Small_Numerator <= 2**31)
215           or else
216          (Num'Small_Numerator < Num'Small_Denominator
217            and then Num'Small_Denominator <= 2**27)
218           or else
219          (Num'Small_Denominator < Num'Small_Numerator
220            and then Num'Small_Numerator <= 2**25));
221   --  These conditions are derived from the prerequisites of System.Image_F
222
223   OK_Get_64 : constant Boolean :=
224     Num'Base'Object_Size <= 64
225       and then
226         ((Num'Small_Numerator = 1 and then Num'Small_Denominator <= 2**63)
227           or else
228          (Num'Small_Denominator = 1 and then Num'Small_Numerator <= 2**63)
229           or else
230          (Num'Small_Numerator <= 2**59
231            and then Num'Small_Denominator <= 2**59));
232   --  These conditions are derived from the prerequisites of System.Value_F
233
234   OK_Put_64 : constant Boolean :=
235     Num'Base'Object_Size <= 64
236       and then
237         ((Num'Small_Numerator = 1 and then Num'Small_Denominator <= 2**63)
238           or else
239          (Num'Small_Denominator = 1 and then Num'Small_Numerator <= 2**63)
240           or else
241          (Num'Small_Numerator < Num'Small_Denominator
242            and then Num'Small_Denominator <= 2**59)
243           or else
244          (Num'Small_Denominator < Num'Small_Numerator
245            and then Num'Small_Numerator <= 2**53));
246   --  These conditions are derived from the prerequisites of System.Image_F
247
248   OK_Get_128 : constant Boolean :=
249     Num'Base'Object_Size <= 128
250       and then
251         ((Num'Small_Numerator = 1 and then Num'Small_Denominator <= 2**127)
252           or else
253          (Num'Small_Denominator = 1 and then Num'Small_Numerator <= 2**127)
254           or else
255          (Num'Small_Numerator <= 2**123
256            and then Num'Small_Denominator <= 2**123));
257   --  These conditions are derived from the prerequisites of System.Value_F
258
259   OK_Put_128 : constant Boolean :=
260     Num'Base'Object_Size <= 128
261       and then
262         ((Num'Small_Numerator = 1 and then Num'Small_Denominator <= 2**127)
263           or else
264          (Num'Small_Denominator = 1 and then Num'Small_Numerator <= 2**127)
265           or else
266          (Num'Small_Numerator < Num'Small_Denominator
267            and then Num'Small_Denominator <= 2**123)
268           or else
269          (Num'Small_Denominator < Num'Small_Numerator
270            and then Num'Small_Numerator <= 2**122));
271   --  These conditions are derived from the prerequisites of System.Image_F
272
273   E : constant Natural :=
274         127 - 64 * Boolean'Pos (OK_Put_64) - 32 * Boolean'Pos (OK_Put_32);
275   --  T'Size - 1 for the selected Int{32,64,128}
276
277   F0 : constant Natural := 0;
278   F1 : constant Natural :=
279          F0 + 38 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F0) >= 1.0E+38);
280   F2 : constant Natural :=
281          F1 + 19 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F1) >= 1.0E+19);
282   F3 : constant Natural :=
283          F2 +  9 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F2) >= 1.0E+9);
284   F4 : constant Natural :=
285          F3 +  5 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F3) >= 1.0E+5);
286   F5 : constant Natural :=
287          F4 +  3 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F4) >= 1.0E+3);
288   F6 : constant Natural :=
289          F5 +  2 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F5) >= 1.0E+2);
290   F7 : constant Natural :=
291          F6 +  1 * Boolean'Pos (2.0**E * Num'Small * 10.0**(-F6) >= 1.0E+1);
292   --  Binary search for the number of digits - 1 before the decimal point of
293   --  the product 2.0**E * Num'Small.
294
295   For0 : constant Natural := 2 + F7;
296   --  Fore value for the fixed point type whose mantissa is Int{32,64,128} and
297   --  whose small is Num'Small.
298
299   ---------
300   -- Get --
301   ---------
302
303   procedure Get
304     (File  : File_Type;
305      Item  : out Num;
306      Width : Field := 0)
307   is
308      pragma Unsuppress (Range_Check);
309
310   begin
311      if OK_Get_32 then
312         Item := Num'Fixed_Value
313                   (Aux32.Get (File, Width,
314                               -Num'Small_Numerator,
315                               -Num'Small_Denominator));
316      elsif OK_Get_64 then
317         Item := Num'Fixed_Value
318                   (Aux64.Get (File, Width,
319                               -Num'Small_Numerator,
320                               -Num'Small_Denominator));
321      elsif OK_Get_128 then
322         Item := Num'Fixed_Value
323                   (Aux128.Get (File, Width,
324                                -Num'Small_Numerator,
325                                -Num'Small_Denominator));
326      else
327         Aux_Long_Float.Get (File, Long_Float (Item), Width);
328      end if;
329
330   exception
331      when Constraint_Error => raise Data_Error;
332   end Get;
333
334   procedure Get
335     (Item  : out Num;
336      Width : Field := 0)
337   is
338   begin
339      Get (Current_In, Item, Width);
340   end Get;
341
342   procedure Get
343     (From : String;
344      Item : out Num;
345      Last : out Positive)
346   is
347      pragma Unsuppress (Range_Check);
348
349   begin
350      if OK_Get_32 then
351         Item := Num'Fixed_Value
352                   (Aux32.Gets (From, Last,
353                                -Num'Small_Numerator,
354                                -Num'Small_Denominator));
355      elsif OK_Get_64 then
356         Item := Num'Fixed_Value
357                   (Aux64.Gets (From, Last,
358                                -Num'Small_Numerator,
359                                -Num'Small_Denominator));
360      elsif OK_Get_128 then
361         Item := Num'Fixed_Value
362                   (Aux128.Gets (From, Last,
363                                 -Num'Small_Numerator,
364                                 -Num'Small_Denominator));
365      else
366         Aux_Long_Float.Gets (From, Long_Float (Item), Last);
367      end if;
368
369   exception
370      when Constraint_Error => raise Data_Error;
371   end Get;
372
373   ---------
374   -- Put --
375   ---------
376
377   procedure Put
378     (File : File_Type;
379      Item : Num;
380      Fore : Field := Default_Fore;
381      Aft  : Field := Default_Aft;
382      Exp  : Field := Default_Exp)
383   is
384   begin
385      if OK_Put_32 then
386         Aux32.Put (File, Int32'Integer_Value (Item), Fore, Aft, Exp,
387                    -Num'Small_Numerator, -Num'Small_Denominator,
388                    For0, Num'Aft);
389      elsif OK_Put_64 then
390         Aux64.Put (File, Int64'Integer_Value (Item), Fore, Aft, Exp,
391                    -Num'Small_Numerator, -Num'Small_Denominator,
392                    For0, Num'Aft);
393      elsif OK_Put_128 then
394         Aux128.Put (File, Int128'Integer_Value (Item), Fore, Aft, Exp,
395                     -Num'Small_Numerator, -Num'Small_Denominator,
396                     For0, Num'Aft);
397      else
398         Aux_Long_Float.Put (File, Long_Float (Item), Fore, Aft, Exp);
399      end if;
400   end Put;
401
402   procedure Put
403     (Item : Num;
404      Fore : Field := Default_Fore;
405      Aft  : Field := Default_Aft;
406      Exp  : Field := Default_Exp)
407   is
408   begin
409      Put (Current_Out, Item, Fore, Aft, Exp);
410   end Put;
411
412   procedure Put
413     (To   : out String;
414      Item : Num;
415      Aft  : Field := Default_Aft;
416      Exp  : Field := Default_Exp)
417   is
418   begin
419      if OK_Put_32 then
420         Aux32.Puts (To, Int32'Integer_Value (Item), Aft, Exp,
421                     -Num'Small_Numerator, -Num'Small_Denominator,
422                     For0, Num'Aft);
423      elsif OK_Put_64 then
424         Aux64.Puts (To, Int64'Integer_Value (Item), Aft, Exp,
425                     -Num'Small_Numerator, -Num'Small_Denominator,
426                     For0, Num'Aft);
427      elsif OK_Put_128 then
428         Aux128.Puts (To, Int128'Integer_Value (Item), Aft, Exp,
429                      -Num'Small_Numerator, -Num'Small_Denominator,
430                      For0, Num'Aft);
431      else
432         Aux_Long_Float.Puts (To, Long_Float (Item), Aft, Exp);
433      end if;
434   end Put;
435
436end Ada.Text_IO.Fixed_IO;
437