blob: 3c7208215c426bf0135f4abc1640571bc29995d9 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME COMPONENTS --
-- --
-- ADA.NUMERICS.BIG_NUMBERS.BIG_REALS --
-- --
-- B o d y --
-- --
-- Copyright (C) 2019-2023, 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 System.Unsigned_Types; use System.Unsigned_Types;
package body Ada.Numerics.Big_Numbers.Big_Reals is
use Big_Integers;
procedure Normalize (Arg : in out Big_Real);
-- Normalize Arg by ensuring that Arg.Den is always positive and that
-- Arg.Num and Arg.Den always have a GCD of 1.
--------------
-- Is_Valid --
--------------
function Is_Valid (Arg : Big_Real) return Boolean is
(Is_Valid (Arg.Num) and Is_Valid (Arg.Den));
---------
-- "/" --
---------
function "/" (Num, Den : Valid_Big_Integer) return Valid_Big_Real is
Result : Big_Real;
begin
if Den = To_Big_Integer (0) then
raise Constraint_Error with "divide by zero";
end if;
Result.Num := Num;
Result.Den := Den;
Normalize (Result);
return Result;
end "/";
---------------
-- Numerator --
---------------
function Numerator (Arg : Valid_Big_Real) return Valid_Big_Integer is
(Arg.Num);
-----------------
-- Denominator --
-----------------
function Denominator (Arg : Valid_Big_Real) return Big_Positive is
(Arg.Den);
---------
-- "=" --
---------
function "=" (L, R : Valid_Big_Real) return Boolean is
(L.Num = R.Num and then L.Den = R.Den);
---------
-- "<" --
---------
function "<" (L, R : Valid_Big_Real) return Boolean is
(L.Num * R.Den < R.Num * L.Den);
-- The denominator is guaranteed to be positive since Normalized is
-- always called when constructing a Valid_Big_Real
----------
-- "<=" --
----------
function "<=" (L, R : Valid_Big_Real) return Boolean is (not (R < L));
---------
-- ">" --
---------
function ">" (L, R : Valid_Big_Real) return Boolean is (R < L);
----------
-- ">=" --
----------
function ">=" (L, R : Valid_Big_Real) return Boolean is (not (L < R));
-----------------------
-- Float_Conversions --
-----------------------
package body Float_Conversions is
package Conv is new
Big_Integers.Unsigned_Conversions (Long_Long_Unsigned);
-----------------
-- To_Big_Real --
-----------------
-- We get the fractional representation of the floating-point number by
-- multiplying Num'Fraction by 2.0**M, with M the size of the mantissa,
-- which gives zero or a number in the range [2.0**(M-1)..2.0**M), which
-- means that it is an integer N of M bits. The floating-point number is
-- thus equal to N / 2**(M-E) where E is its Num'Exponent.
function To_Big_Real (Arg : Num) return Valid_Big_Real is
A : constant Num'Base := abs (Arg);
E : constant Integer := Num'Exponent (A);
F : constant Num'Base := Num'Fraction (A);
M : constant Natural := Num'Machine_Mantissa;
N, D : Big_Integer;
begin
pragma Assert (Num'Machine_Radix = 2);
-- This implementation does not handle radix 16
pragma Assert (M <= 64);
-- This implementation handles only 80-bit IEEE Extended or smaller
N := Conv.To_Big_Integer (Long_Long_Unsigned (F * 2.0**M));
-- If E is smaller than M, the denominator is 2**(M-E)
if E < M then
D := To_Big_Integer (2) ** (M - E);
-- Or else, if E is larger than M, multiply the numerator by 2**(E-M)
elsif E > M then
N := N * To_Big_Integer (2) ** (E - M);
D := To_Big_Integer (1);
-- Otherwise E is equal to M and the result is just N
else
D := To_Big_Integer (1);
end if;
return (if Arg >= 0.0 then N / D else -N / D);
end To_Big_Real;
-------------------
-- From_Big_Real --
-------------------
-- We get the (Frac, Exp) representation of the real number by finding
-- the exponent E such that it lies in the range [2.0**(E-1)..2.0**E),
-- multiplying the number by 2.0**(M-E) with M the size of the mantissa,
-- and converting the result to integer N in the range [2**(M-1)..2**M)
-- with rounding to nearest, ties to even, and finally call Num'Compose.
-- This does not apply to the zero, for which we return 0.0 early.
function From_Big_Real (Arg : Big_Real) return Num is
M : constant Natural := Num'Machine_Mantissa;
One : constant Big_Real := To_Real (1);
Two : constant Big_Real := To_Real (2);
Half : constant Big_Real := One / Two;
TwoI : constant Big_Integer := To_Big_Integer (2);
function Log2_Estimate (V : Big_Real) return Natural;
-- Return an integer not larger than Log2 (V) for V >= 1.0
function Minus_Log2_Estimate (V : Big_Real) return Natural;
-- Return an integer not larger than -Log2 (V) for V < 1.0
-------------------
-- Log2_Estimate --
-------------------
function Log2_Estimate (V : Big_Real) return Natural is
Log : Natural := 1;
Pow : Big_Real := Two;
begin
while V >= Pow loop
Pow := Pow * Pow;
Log := Log + Log;
end loop;
return Log / 2;
end Log2_Estimate;
-------------------------
-- Minus_Log2_Estimate --
-------------------------
function Minus_Log2_Estimate (V : Big_Real) return Natural is
Log : Natural := 1;
Pow : Big_Real := Half;
begin
while V <= Pow loop
Pow := Pow * Pow;
Log := Log + Log;
end loop;
return Log / 2;
end Minus_Log2_Estimate;
-- Local variables
V : Big_Real := abs (Arg);
E : Integer := 0;
L : Integer;
A, B, Q, X : Big_Integer;
N : Long_Long_Unsigned;
R : Num'Base;
begin
pragma Assert (Num'Machine_Radix = 2);
-- This implementation does not handle radix 16
pragma Assert (M <= 64);
-- This implementation handles only 80-bit IEEE Extended or smaller
-- Protect from degenerate case
if Numerator (V) = To_Big_Integer (0) then
return 0.0;
end if;
-- Use a binary search to compute exponent E
while V < Half loop
L := Minus_Log2_Estimate (V);
V := V * (Two ** L);
E := E - L;
end loop;
-- The dissymetry with above is expected since we go below 2
while V >= One loop
L := Log2_Estimate (V) + 1;
V := V / (Two ** L);
E := E + L;
end loop;
-- The multiplication by 2.0**(-E) has already been done in the loops
V := V * To_Big_Real (TwoI ** M);
-- Now go into the integer domain and divide
A := Numerator (V);
B := Denominator (V);
Q := A / B;
N := Conv.From_Big_Integer (Q);
-- Round to nearest, ties to even, by comparing twice the remainder
X := (A - Q * B) * TwoI;
if X > B or else (X = B and then (N mod 2) = 1) then
N := N + 1;
-- If the adjusted quotient overflows the mantissa, scale up
if N = 2**M then
N := 1;
E := E + 1;
end if;
end if;
R := Num'Compose (Num'Base (N), E);
return (if Numerator (Arg) >= To_Big_Integer (0) then R else -R);
end From_Big_Real;
end Float_Conversions;
-----------------------
-- Fixed_Conversions --
-----------------------
package body Fixed_Conversions is
package Float_Aux is new Float_Conversions (Long_Float);
subtype LLLI is Long_Long_Long_Integer;
subtype LLLU is Long_Long_Long_Unsigned;
Too_Large : constant Boolean :=
Num'Small_Numerator > LLLU'Last
or else Num'Small_Denominator > LLLU'Last;
-- True if the Small is too large for Long_Long_Long_Unsigned, in which
-- case we convert to/from Long_Float as an intermediate step.
package Conv_I is new Big_Integers.Signed_Conversions (LLLI);
package Conv_U is new Big_Integers.Unsigned_Conversions (LLLU);
-----------------
-- To_Big_Real --
-----------------
-- We just compute V * N / D where V is the mantissa value of the fixed
-- point number, and N resp. D is the numerator resp. the denominator of
-- the Small of the fixed-point type.
function To_Big_Real (Arg : Num) return Valid_Big_Real is
N, D, V : Big_Integer;
begin
if Too_Large then
return Float_Aux.To_Big_Real (Long_Float (Arg));
end if;
N := Conv_U.To_Big_Integer (Num'Small_Numerator);
D := Conv_U.To_Big_Integer (Num'Small_Denominator);
V := Conv_I.To_Big_Integer (LLLI'Integer_Value (Arg));
return V * N / D;
end To_Big_Real;
-------------------
-- From_Big_Real --
-------------------
-- We first compute A / B = Arg * D / N where N resp. D is the numerator
-- resp. the denominator of the Small of the fixed-point type. Then we
-- divide A by B and convert the result to the mantissa value.
function From_Big_Real (Arg : Big_Real) return Num is
N, D, A, B, Q, X : Big_Integer;
begin
if Too_Large then
return Num (Float_Aux.From_Big_Real (Arg));
end if;
N := Conv_U.To_Big_Integer (Num'Small_Numerator);
D := Conv_U.To_Big_Integer (Num'Small_Denominator);
A := Numerator (Arg) * D;
B := Denominator (Arg) * N;
Q := A / B;
-- Round to nearest, ties to away, by comparing twice the remainder
X := (A - Q * B) * To_Big_Integer (2);
if X >= B then
Q := Q + To_Big_Integer (1);
elsif X <= -B then
Q := Q - To_Big_Integer (1);
end if;
return Num'Fixed_Value (Conv_I.From_Big_Integer (Q));
end From_Big_Real;
end Fixed_Conversions;
---------------
-- To_String --
---------------
function To_String
(Arg : Valid_Big_Real;
Fore : Field := 2;
Aft : Field := 3;
Exp : Field := 0) return String
is
Zero : constant Big_Integer := To_Big_Integer (0);
Ten : constant Big_Integer := To_Big_Integer (10);
function Leading_Padding
(Str : String;
Min_Length : Field;
Char : Character := ' ') return String;
-- Return padding of Char concatenated with Str so that the resulting
-- string is at least Min_Length long.
function Trailing_Padding
(Str : String;
Length : Field;
Char : Character := '0') return String;
-- Return Str with trailing Char removed, and if needed either
-- truncated or concatenated with padding of Char so that the resulting
-- string is Length long.
function Image (N : Natural) return String;
-- Return image of N, with no leading space.
function Numerator_Image
(Num : Big_Integer;
After : Natural) return String;
-- Return image of Num as a float value with After digits after the "."
-- and taking Fore, Aft, Exp into account.
-----------
-- Image --
-----------
function Image (N : Natural) return String is
S : constant String := Natural'Image (N);
begin
return S (2 .. S'Last);
end Image;
---------------------
-- Leading_Padding --
---------------------
function Leading_Padding
(Str : String;
Min_Length : Field;
Char : Character := ' ') return String is
begin
if Str = "" then
return Leading_Padding ("0", Min_Length, Char);
else
return [1 .. Integer'Max (Integer (Min_Length) - Str'Length, 0)
=> Char] & Str;
end if;
end Leading_Padding;
----------------------
-- Trailing_Padding --
----------------------
function Trailing_Padding
(Str : String;
Length : Field;
Char : Character := '0') return String is
begin
if Str'Length > 0 and then Str (Str'Last) = Char then
for J in reverse Str'Range loop
if Str (J) /= '0' then
return Trailing_Padding
(Str (Str'First .. J), Length, Char);
end if;
end loop;
end if;
if Str'Length >= Length then
return Str (Str'First .. Str'First + Length - 1);
else
return Str &
[1 .. Integer'Max (Integer (Length) - Str'Length, 0)
=> Char];
end if;
end Trailing_Padding;
---------------------
-- Numerator_Image --
---------------------
function Numerator_Image
(Num : Big_Integer;
After : Natural) return String
is
Tmp : constant String := To_String (Num);
Str : constant String (1 .. Tmp'Last - 1) := Tmp (2 .. Tmp'Last);
Index : Integer;
begin
if After = 0 then
return Leading_Padding (Str, Fore) & "."
& Trailing_Padding ("0", Aft);
else
Index := Str'Last - After;
if Index < 0 then
return Leading_Padding ("0", Fore)
& "."
& Trailing_Padding ([1 .. -Index => '0'] & Str, Aft)
& (if Exp = 0 then "" else "E+" & Image (Natural (Exp)));
else
return Leading_Padding (Str (Str'First .. Index), Fore)
& "."
& Trailing_Padding (Str (Index + 1 .. Str'Last), Aft)
& (if Exp = 0 then "" else "E+" & Image (Natural (Exp)));
end if;
end if;
end Numerator_Image;
begin
if Arg.Num < Zero then
declare
Str : String := To_String (-Arg, Fore, Aft, Exp);
begin
if Str (1) = ' ' then
for J in 1 .. Str'Last - 1 loop
if Str (J + 1) /= ' ' then
Str (J) := '-';
exit;
end if;
end loop;
return Str;
else
return '-' & Str;
end if;
end;
else
-- Compute Num * 10^Aft so that we get Aft significant digits
-- in the integer part (rounded) to display.
return Numerator_Image
((Arg.Num * Ten ** Aft) / Arg.Den, After => Exp + Aft);
end if;
end To_String;
-----------------
-- From_String --
-----------------
function From_String (Arg : String) return Valid_Big_Real is
Ten : constant Big_Integer := To_Big_Integer (10);
Frac : Big_Integer;
Exp : Integer := 0;
Pow : Natural := 0;
Index : Natural := 0;
Last : Natural := Arg'Last;
begin
for J in reverse Arg'Range loop
if Arg (J) in 'e' | 'E' then
if Last /= Arg'Last then
raise Constraint_Error with "multiple exponents specified";
end if;
Last := J - 1;
Exp := Integer'Value (Arg (J + 1 .. Arg'Last));
Pow := 0;
elsif Arg (J) = '.' then
Index := J - 1;
exit;
elsif Arg (J) /= '_' then
Pow := Pow + 1;
end if;
end loop;
if Index = 0 then
raise Constraint_Error with "invalid real value";
end if;
declare
Result : Big_Real;
begin
Result.Den := Ten ** Pow;
Result.Num := From_String (Arg (Arg'First .. Index)) * Result.Den;
Frac := From_String (Arg (Index + 2 .. Last));
if Result.Num < To_Big_Integer (0) then
Result.Num := Result.Num - Frac;
else
Result.Num := Result.Num + Frac;
end if;
if Exp > 0 then
Result.Num := Result.Num * Ten ** Exp;
elsif Exp < 0 then
Result.Den := Result.Den * Ten ** (-Exp);
end if;
Normalize (Result);
return Result;
end;
end From_String;
--------------------------
-- From_Quotient_String --
--------------------------
function From_Quotient_String (Arg : String) return Valid_Big_Real is
Index : Natural := 0;
begin
for J in Arg'First + 1 .. Arg'Last - 1 loop
if Arg (J) = '/' then
Index := J;
exit;
end if;
end loop;
if Index = 0 then
raise Constraint_Error with "no quotient found";
end if;
return Big_Integers.From_String (Arg (Arg'First .. Index - 1)) /
Big_Integers.From_String (Arg (Index + 1 .. Arg'Last));
end From_Quotient_String;
---------------
-- Put_Image --
---------------
procedure Put_Image (S : in out Root_Buffer_Type'Class; V : Big_Real) is
-- This is implemented in terms of To_String. It might be more elegant
-- and more efficient to do it the other way around, but this is the
-- most expedient implementation for now.
begin
Strings.Text_Buffers.Put_UTF_8 (S, To_String (V));
end Put_Image;
---------
-- "+" --
---------
function "+" (L : Valid_Big_Real) return Valid_Big_Real is
Result : Big_Real;
begin
Result.Num := L.Num;
Result.Den := L.Den;
return Result;
end "+";
---------
-- "-" --
---------
function "-" (L : Valid_Big_Real) return Valid_Big_Real is
(Num => -L.Num, Den => L.Den);
-----------
-- "abs" --
-----------
function "abs" (L : Valid_Big_Real) return Valid_Big_Real is
(Num => abs L.Num, Den => L.Den);
---------
-- "+" --
---------
function "+" (L, R : Valid_Big_Real) return Valid_Big_Real is
Result : Big_Real;
begin
Result.Num := L.Num * R.Den + R.Num * L.Den;
Result.Den := L.Den * R.Den;
Normalize (Result);
return Result;
end "+";
---------
-- "-" --
---------
function "-" (L, R : Valid_Big_Real) return Valid_Big_Real is
Result : Big_Real;
begin
Result.Num := L.Num * R.Den - R.Num * L.Den;
Result.Den := L.Den * R.Den;
Normalize (Result);
return Result;
end "-";
---------
-- "*" --
---------
function "*" (L, R : Valid_Big_Real) return Valid_Big_Real is
Result : Big_Real;
begin
Result.Num := L.Num * R.Num;
Result.Den := L.Den * R.Den;
Normalize (Result);
return Result;
end "*";
---------
-- "/" --
---------
function "/" (L, R : Valid_Big_Real) return Valid_Big_Real is
Result : Big_Real;
begin
Result.Num := L.Num * R.Den;
Result.Den := L.Den * R.Num;
Normalize (Result);
return Result;
end "/";
----------
-- "**" --
----------
function "**" (L : Valid_Big_Real; R : Integer) return Valid_Big_Real is
Result : Big_Real;
begin
if R = 0 then
Result.Num := To_Big_Integer (1);
Result.Den := To_Big_Integer (1);
else
if R < 0 then
Result.Num := L.Den ** (-R);
Result.Den := L.Num ** (-R);
else
Result.Num := L.Num ** R;
Result.Den := L.Den ** R;
end if;
Normalize (Result);
end if;
return Result;
end "**";
---------
-- Min --
---------
function Min (L, R : Valid_Big_Real) return Valid_Big_Real is
(if L < R then L else R);
---------
-- Max --
---------
function Max (L, R : Valid_Big_Real) return Valid_Big_Real is
(if L > R then L else R);
---------------
-- Normalize --
---------------
procedure Normalize (Arg : in out Big_Real) is
Zero : constant Big_Integer := To_Big_Integer (0);
begin
if Arg.Den < Zero then
Arg.Num := -Arg.Num;
Arg.Den := -Arg.Den;
end if;
if Arg.Num = Zero then
Arg.Den := To_Big_Integer (1);
else
declare
GCD : constant Big_Integer :=
Greatest_Common_Divisor (Arg.Num, Arg.Den);
begin
Arg.Num := Arg.Num / GCD;
Arg.Den := Arg.Den / GCD;
end;
end if;
end Normalize;
end Ada.Numerics.Big_Numbers.Big_Reals;