(********************************************************************)
(*                                                                  *)
(*  float.s7i     Floating point support library                    *)
(*  Copyright (C) 1993, 1994, 2005, 2008  Thomas Mertes             *)
(*                2011 - 2016, 2018, 2019  Thomas Mertes            *)
(*                2021 - 2024  Thomas Mertes                        *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "enable_io.s7i";


(**
 *  Double precision floating point numbers.
 *)
const type: float is          subtype object;


$ system "float" is float;

const proc: destroy (ref float: aValue)                        is action "GEN_DESTR";
const proc: (ref float: dest) ::= (ref float: source)          is action "FLT_CREATE";
IN_PARAM_IS_VALUE(float);


(**
 *  Default value of ''float'' (0.0).
 *)
const float: (attr float) . value is 0.0;


const proc: (inout float: dest) := (in float: source)          is action "FLT_CPY";


(**
 *  Plus sign for ''float'' numbers.
 *  @return its operand unchanged.
 *)
const func float: + (in float: number)                         is action "FLT_PLUS";


(**
 *  Minus sign, negate a ''float'' number.
 *  @return the negated value of the number.
 *)
const func float: - (in float: number)                         is action "FLT_NEGATE";


(**
 *  Add two ''float'' numbers.
 *  @return the sum of the two numbers.
 *)
const func float: (in float: summand1) + (in float: summand2)  is action "FLT_ADD";


(**
 *  Compute the subtraction of two ''float'' numbers.
 *  @return the difference of the two numbers.
 *)
const func float: (in float: minuend) - (in float: subtrahend) is action "FLT_SBTR";


(**
 *  Multiply two ''float'' numbers.
 *  @return the product of the two numbers.
 *)
const func float: (in float: factor1) * (in float: factor2)    is action "FLT_MULT";


(**
 *  Compute the division of two ''float'' numbers.
 *   A / 0.0    returns  Infinity  for A > 0.0
 *   A / 0.0    returns -Infinity  for A < 0.0
 *   0.0 / 0.0  returns NaN
 *  @return the quotient of the division.
 *)
const func float: (in float: dividend) / (in float: divisor)   is action "FLT_DIV";


(**
 *  Compute the floating-point remainder of a division.
 *  The remainder has the same sign as the dividend.
 *  The remainder is dividend - flt(trunc(dividend / divisor)) * divisor
 *  The remainder is computed without a conversion to integer.
 *    A        rem NaN       returns NaN
 *    NaN      rem B         returns NaN
 *    A        rem 0.0       returns NaN
 *    Infinity rem B         returns NaN
 *   -Infinity rem B         returns NaN
 *    0.0      rem B         returns 0.0  for B <> 0.0
 *    A        rem Infinity  returns A
 *  @return the floating-point remainder of the division.
 *)
const func float: (in float: dividend) rem (in float: divisor) is action "FLT_REM";


(**
 *  Compute the floating-point modulo of a division.
 *  The modulo has the same sign as the divisor.
 *  The modulo is dividend - floor(dividend / divisor) * divisor
 *    A        mod  NaN       returns  NaN
 *    NaN      mod  B         returns  NaN
 *    A        mod  0.0       returns  NaN
 *    Infinity mod  B         returns  NaN
 *   -Infinity mod  B         returns  NaN
 *    0.0      mod  B         returns  0.0         for B <> 0.0
 *    A        mod  Infinity  returns  A           for A > 0
 *    A        mod  Infinity  returns  Infinity    for A < 0
 *    A        mod -Infinity  returns  A           for A < 0
 *    A        mod -Infinity  returns -Infinity    for A > 0
 *  @return the floating-point modulo of the division.
 *)
const func float: (in float: dividend) mod (in float: divisor) is action "FLT_MOD";


(**
 *  Compute the exponentiation of a float ''base'' with an [[integer]] ''exponent''.
 *     A    ** 0  returns 1.0
 *     NaN  ** 0  returns 1.0
 *     NaN  ** B  returns NaN              for B <> 0
 *     0.0  ** B  returns 0.0              for B > 0
 *     0.0  ** 0  returns 1.0
 *     0.0  ** B  returns Infinity         for B < 0
 *   (-0.0) ** B  returns -Infinity        for B < 0 and odd(B)
 *     A    ** B  returns 1.0 / A ** (-B)  for B < 0
 *  @return the result of the exponentiation.
 *)
const func float: (in float: base) ** (in integer: exponent)   is action "FLT_IPOW";


(**
 *  Compute the exponentiation of a float ''base'' with a float ''exponent''.
 *     A    ** B    returns NaN        for A < 0.0 and B is not integer
 *     A    ** 0.0  returns 1.0
 *     NaN  ** 0.0  returns 1.0
 *     NaN  ** B    returns NaN        for B <> 0.0
 *     0.0  ** B    returns 0.0        for B > 0.0
 *     0.0  ** 0.0  returns 1.0
 *     0.0  ** B    returns Infinity   for B < 0.0
 *   (-0.0) ** B    returns -Infinity  for B < 0.0 and odd(B)
 *     1.0  ** B    returns 1.0
 *     1.0  ** NaN  returns 1.0
 *     A    ** NaN  returns NaN        for A <> 1.0
 *  @return the result of the exponentiation.
 *)
const func float: (in float: base) ** (in float: exponent)     is action "FLT_POW";


(**
 *  Multiply ''number'' by 2 raised to the power of ''exponent''.
 *  In other words: A << B is equivalent to A * 2.0 ** B
 *  If the result underflows zero is returned.
 *  If the result overflows Infinity or -Infinity is returned,
 *  depending on the sign of ''number''.
 *  If the argument ''number'' is a [[#NaN|NaN]], Infinity or -Infinity the
 *  unchanged argument is returned.
 *  @return number * 2.0 ** exponent
 *)
const func float: (in float: number) << (in integer: exponent) is action "FLT_LSHIFT";


(**
 *  Divide ''number'' by 2 raised to the power of ''exponent''.
 *  In other words: A >> B is equivalent to A / 2.0 ** B
 *  If the result underflows zero is returned.
 *  If the result overflows Infinity or -Infinity is returned,
 *  depending on the sign of ''number''.
 *  If the argument ''number'' is a [[#NaN|NaN]], Infinity or -Infinity the
 *  unchanged argument is returned.
 *  @return number / 2.0 ** exponent
 *)
const func float: (in float: number) >> (in integer: exponent) is action "FLT_RSHIFT";


(**
 *  Increment a float ''number'' by a ''delta''.
 *)
const proc: (inout float: number) +:= (in float: delta)        is action "FLT_ADD_ASSIGN";


(**
 *  Decrement a float ''number'' by a ''delta''.
 *)
const proc: (inout float: number) -:= (in float: delta)        is action "FLT_SBTR_ASSIGN";


(**
 *  Multiply a float ''number'' by a ''factor'' and assign the result back to ''number''.
 *)
const proc: (inout float: number) *:= (in float: factor)       is action "FLT_MULT_ASSIGN";


(**
 *  Divide a float ''number'' by a ''divisor'' and assign the result back to ''number''.
 *)
const proc: (inout float: number) /:= (in float: divisor)      is action "FLT_DIV_ASSIGN";


(**
 *  Check if two float numbers are equal.
 *  According to IEEE 754 a [[#NaN|NaN]] is not equal to any float value.
 *  Therefore ''NaN = any_value'' and ''any_value = NaN''
 *  always return FALSE. Even ''NaN = NaN'' returns FALSE.
 *  @return TRUE if both numbers are equal, FALSE otherwise.
 *)
const func boolean: (in float: number1) = (in float: number2)  is action "FLT_EQ";


(**
 *  Check if two float numbers are not equal.
 *  According to IEEE 754 a [[#NaN|NaN]] is not equal to any float value.
 *  Therefore ''NaN <> any_value'' and ''any_value <> NaN''
 *  always return TRUE. Even ''NaN <> NaN'' returns TRUE.
 *  @return FALSE if both numbers are equal, TRUE otherwise.
 *)
const func boolean: (in float: number1) <> (in float: number2) is action "FLT_NE";


(**
 *  Check if ''number1'' is less than ''number2''.
 *  According to IEEE 754 a [[#NaN|NaN]] is neither less than,
 *  equal to, nor greater than any value, including itself.
 *  If ''number1'' or ''number2'' is NaN, the result
 *  is FALSE;
 *  @return TRUE if ''number1'' is less than ''number2'',
 *          FALSE otherwise.
 *)
const func boolean: (in float: number1) < (in float: number2)  is action "FLT_LT";


(**
 *  Check if ''number1'' is greater than ''number2''.
 *  According to IEEE 754 a [[#NaN|NaN]] is neither less than,
 *  equal to, nor greater than any value, including itself.
 *  If ''number1'' or ''number2'' is NaN, the result
 *  is FALSE;
 *  @return TRUE if ''number1'' is greater than ''number2'',
 *          FALSE otherwise.
 *)
const func boolean: (in float: number1) > (in float: number2)  is action "FLT_GT";


(**
 *  Check if ''number1'' is less than or equal to ''number2''.
 *  According to IEEE 754 a [[#NaN|NaN]] is neither less than,
 *  equal to, nor greater than any value, including itself.
 *  If ''number1'' or ''number2'' is NaN, the result
 *  is FALSE;
 *  @return TRUE if ''number1'' is less than or equal to ''number2'',
 *          FALSE otherwise.
 *)
const func boolean: (in float: number1) <= (in float: number2) is action "FLT_LE";


(**
 *  Check if ''number1'' is greater than or equal to ''number2''.
 *  According to IEEE 754 a [[#NaN|NaN]] is neither less than,
 *  equal to, nor greater than any value, including itself.
 *  If ''number1'' or ''number2'' is NaN, the result
 *  is FALSE;
 *  @return TRUE if ''number1'' is greater than or equal to ''number2'',
 *          FALSE otherwise.
 *)
const func boolean: (in float: number1) >= (in float: number2) is action "FLT_GE";


(**
 *  Positive infinity.
 *  Infinity is the result of 1.0 / 0.0 .
 *)
const float: Infinity is 1.0 / 0.0;


(**
 *  Not-a-Number (NaN) value.
 *  Represents an undefined or unrepresentable value.
 *  NaN is the result of 0.0 / 0.0 .
 *  Checking for NaN is done with the function [[#isNaN(in_float)|isNaN]].
 *)
const float: NaN is 0.0 / 0.0;


(**
 *  Compare two float numbers.
 *  Because ''compare'' is used to sort float values, a unique
 *  sort sequence of the values is needed. Therefore ''compare''
 *  considers all [[#NaN|NaN]] values as greater than Infinity.
 *  The NaN values are also considered to be equal to each other.
 *  Negative zero (-0.0) is considered by ''compare'' to be
 *  equal to positive zero (+0.0). This conforms to the behavior
 *  of all other float comparisons with zero.
 *  @return -1, 0 or 1 if the first argument is considered to be
 *          respectively less than, equal to, or greater than the
 *          second.
 *)
const func integer: compare (in float: number1, in float: number2) is action "FLT_CMP";


(**
 *  Compute the hash value of a float number.
 *  @return the hash value.
 *)
const func integer: hashCode (in float: number)                is action "FLT_HASHCODE";


(**
 *  Convert a float number to a [[string]].
 *  The number is converted to a string with decimal representation.
 *  The result string has the style [-]ddd.ddd where there is at least
 *  one digit before and after the decimal point. The number of digits
 *  after the decimal point is determined automatically. Except for the
 *  case with only one zero digit after the decimal point the last digit
 *  is never zero. Negative zero (-0.0) and positive zero (+0.0)
 *  are both converted to "0.0".
 *   str(16.125)    returns "16.125"
 *   str(-0.0)      returns "0.0"
 *   str(Infinity)  returns "Infinity"
 *   str(-Infinity) returns "-Infinity"
 *   str(NaN)       returns "NaN"
 *  @return the string result of the conversion.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: str (in float: number)                      is action "FLT_STR";


(**
 *  Convert a float number to a [[string]].
 *  The number is converted to a string with decimal representation.
 *  The result string has the style [-]ddd.ddd where there is at least
 *  one digit before and after the decimal point. The number of digits
 *  after the decimal point is determined automatically. Except for the
 *  case with only one zero digit after the decimal point the last digit
 *  is never zero. Negative zero (-0.0) and positive zero (+0.0)
 *  are both converted to "0.0".
 *   string(16.125)    returns "16.125"
 *   string(-0.0)      returns "0.0"
 *   string(Infinity)  returns "Infinity"
 *   string(-Infinity) returns "-Infinity"
 *   string(NaN)       returns "NaN"
 *  @return the string result of the conversion.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: string (in float: number)                   is action "FLT_STR";


(**
 *  Convert a [[string]] to a float number.
 *   float("1.2345")     returns  1.2345
 *   float("1.2345e6")   returns  1234500.0
 *   float("-1.0e-308")  returns -1.0e-308
 *   float("1")          returns  1.0
 *   float("2.")         returns  2.0
 *   float(".5")         returns  0.5
 *   float("-.25")       returns  -0.25
 *   float("Infinity")   returns  Infinity
 *   float("-Infinity")  returns -Infinity
 *   float("NaN")        returns  NaN
 *   float("3.14PI"      raises RANGE_ERROR
 *  @return the float result of the conversion.
 *  @exception RANGE_ERROR If the string contains not a float literal.
 *)
const func float: float (in string: stri)                      is action "FLT_PARSE1";


(**
 *  Convert a [[string]] to a float number.
 *   float parse "1.2345"     returns  1.2345
 *   float parse "1.2345e6"   returns  1234500.0
 *   float parse "-1.0e-308"  returns -1.0e-308
 *   float parse "1"          returns  1.0
 *   float parse "2."         returns  2.0
 *   float parse ".5"         returns  0.5
 *   float parse "-.25"       returns  -0.25
 *   float parse "Infinity"   returns  Infinity
 *   float parse "-Infinity"  returns -Infinity
 *   float parse "NaN"        returns  NaN
 *   float parse "2.14PI"     raises RANGE_ERROR
 *  @return the float result of the conversion.
 *  @exception RANGE_ERROR If the string contains not a float literal.
 *)
const func float: (attr float) parse (in string: stri) is
    return float(stri);


(**
 *  Convert a ''float'' to a [[string]] in decimal fixed point notation.
 *  The number is rounded to the specified number of digits (''precision'').
 *  Halfway cases are rounded away from zero. Except for a ''precision'' of
 *  zero the representation has a decimal point and at least one digit
 *  before and after the decimal point. Negative numbers are preceded by
 *  a minus sign (e.g.: "-1.25"). If all digits in the result are 0 a
 *  possible negative sign is omitted.
 *   0.012345 digits 4   returns "0.0123"
 *   1.2468 digits 2     returns "1.25"
 *   3.1415 digits 0     returns "3"
 *   0.125 digits 2      returns "0.12"
 *   0.375 digits 2      returns "0.38"
 *   Infinity digits 5   returns "Infinity"
 *   -Infinity digits 6  returns "-Infinity"
 *   NaN digits 7        returns "NaN"
 *   -0.004 digits 2     returns "0.00"
 *  @param precision Number of digits after the decimal point.
 *         If the ''precision'' is zero the decimal point is omitted.
 *  @return the string result of the conversion.
 *  @exception RANGE_ERROR If the ''precision'' is negative.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: (in float: number) digits (in integer: precision) is action "FLT_DGTS";


(**
 *  Convert a ''float'' to a [[string]] in scientific notation.
 *  Scientific notation uses a decimal significand and a decimal exponent.
 *  The significand has an optional sign and exactly one digit before the
 *  decimal point. The fractional part of the significand is rounded
 *  to the specified number of digits (''precision''). Halfway cases are
 *  rounded away from zero. The fractional part is followed by the
 *  letter e and an exponent, which is always signed. The value zero is
 *  never written with a negative sign.
 *   0.012345 sci 4   returns "1.2345e-2"
 *   1.2468 sci 2     returns "1.25e+0"
 *   3.1415 sci 0     returns "3e+0"
 *   0.125 sci 1      returns "1.2e-1"
 *   0.375 sci 1      returns "3.8e-1"
 *   Infinity sci 5   returns "Infinity"
 *   -Infinity sci 6  returns "-Infinity"
 *   NaN sci 7        returns "NaN"
 *   -0.004 sci 2     returns "-4.00e-3"
 *   -0.0 sci 2       returns "0.00e+0"
 *  @param precision Number of digits after the decimal point.
 *         If the ''precision'' is zero the decimal point is omitted.
 *  @return the string result of the conversion.
 *  @exception RANGE_ERROR If the ''precision'' is negative.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: (in float: number) sci (in integer: precision)    is action "FLT_SCI";


(**
 *  Set the number of exponent digits in a scientific float notation.
 *  If ''sciNumber'' contains a [[string]] in scientific float notation
 *  the exponent is changed to contain at least ''expDigits'' digits.
 *  If ''sciNumber'' contains not a string in scientific float
 *  notation it is returned unchanged. The ''exp'' operator is
 *  intended to be used together with the ''sci'' operator
 *   0.012345 sci 4 exp 2   returns "1.2345e-02"
 *   1.2468e15 sci 2 exp 1  returns "1.25e+15"
 *   3.1415 sci 0 exp 3     returns "3e+000"
 *   0.125 sci 1 exp 2      returns "1.2e-01"
 *   0.375 sci 1 exp 2      returns "3.8e-01"
 *   Infinity sci 5 exp 2   returns "Infinity"
 *   -Infinity sci 6 exp 2  returns "-Infinity"
 *   NaN sci 7 exp 2        returns "NaN"
 *   -0.004 sci 2 exp 2     returns "-4.00e-03"
 *  @return the string result of the conversion.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: (in string: sciNumber) exp (in integer: expDigits) is func
  result
    var string: paddedStri is "";
  local
    var integer: pos is 0;
  begin
    pos := length(sciNumber);
    while pos >= 1 and sciNumber[pos] >= '0' and sciNumber[pos] <= '9' do
      decr(pos);
    end while;
    if pos >= 2 and sciNumber[pred(pos)] = 'e' and
        (sciNumber[pos] = '+' or sciNumber[pos] = '-') and
        expDigits > length(sciNumber) - pos then
      paddedStri := sciNumber[.. pos] & "0" mult expDigits - length(sciNumber) + pos &
          sciNumber[succ(pos) ..];
    else
      paddedStri := sciNumber;
    end if;
  end func;


(**
 *  Convert an [[integer]] to a float.
 *  @return the float result of the conversion.
 *)
const func float: flt (in integer: number)                     is action "FLT_ICONV1";


(**
 *  Convert an [[integer]] to a float.
 *  @return the float result of the conversion.
 *)
const func float: float (in integer: number)                   is action "FLT_ICONV1";


(**
 *  Convert an [[integer]] to a float.
 *  @return the float result of the conversion.
 *)
const func float: (attr float) conv (in integer: number)       is action "FLT_ICONV3";


(**
 *  Compute the absolute value of a ''float'' number.
 *  @return the absolute value.
 *)
const func float: abs (in float: number)                       is action "FLT_ABS";


(**
 *  Round a ''float'' to the nearest [[integer]].
 *  Halfway cases are rounded away from zero.
 *   round( 1.4999)  returns  1
 *   round( 0.5)     returns  1
 *   round( 0.4999)  returns  0
 *   round(-0.4999)  returns  0
 *   round(-0.5)     returns -1
 *   round(-1.4999)  returns -1
 *   round(1.0e+19)  raises  RANGE_ERROR
 *  @return the rounded value.
 *  @exception RANGE_ERROR If the number is [[#NaN|NaN]], -Infinity, Infinity,
 *             or does not fit into an integer.
 *)
const func integer: round (in float: number)                   is action "FLT_ROUND";


(**
 *  Truncate towards zero.
 *  The fractional part of a number is discarded.
 *   trunc( 1.9999)  returns  1
 *   trunc( 1.0)     returns  1
 *   trunc( 0.9999)  returns  0
 *   trunc(-0.9999)  returns  0
 *   trunc(-1.0)     returns -1
 *   trunc(-1.9999)  returns -1
 *   trunc(1.0e+19)  raises  RANGE_ERROR
 *  @return the nearest [[integer]] not larger in absolute value
 *          than the argument.
 *  @exception RANGE_ERROR If the number is [[#NaN|NaN]], -Infinity, Infinity,
 *             or does not fit into an integer.
 *)
const func integer: trunc (in float: number)                   is action "FLT_TRUNC";


(**
 *  Determine if a number has a Not-a-Number ([[#NaN|NaN]]) value.
 *  NaN represents an undefined or unrepresentable value.
 *  @return TRUE if the number has a Not-a-Number (NaN) value,
 *          FALSE otherwise.
 *)
const func boolean: isNaN (in float: number)                   is action "FLT_ISNAN";


(**
 *  Determine if a number is negative zero (-0.0).
 *  This function is the only possibility to determine if a number
 *  is -0.0. The comparison operators (=, <>, <, >, <=, >=) and
 *  the function ''compare'' treat 0.0 and -0.0 as equal. The
 *  operators ''digits'' and ''sci'' and the function ''str''
 *  return the same [[string]] for -0.0 and +0.0.
 *  @return TRUE if the number is -0.0,
 *          FALSE otherwise.
 *)
const func boolean: isNegativeZero (in float: number)          is action "FLT_ISNEGATIVEZERO";


(**
 *  Determine if a number is +0.0.
 *  @return TRUE if the number is +0.0,
 *          FALSE otherwise.
 *)
const func boolean: isPositiveZero (in float: number) is
  return number = 0.0 and not isNegativeZero(number);


(**
 *  Convert a float number to a [[string]].
 *  The number is converted to a string with decimal representation.
 *  @return the string result of the conversion.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: literal (in float: number)                  is action "FLT_STR";


(**
 *  Compute pseudo-random number in the range [low, high).
 *  The random values are uniform distributed.
 *  @return the computed pseudo-random number.
 *  @exception RANGE_ERROR The range is empty (low >= high holds).
 *)
const func float: rand (in float: low, in float: high)         is action "FLT_RAND";


const proc: decompose (in float: number, inout float: fraction,
                       inout integer: exponent)                is action "FLT_DECOMPOSE";


const type: floatElements is new struct
    var float: fraction is 0.0;
    var integer: exponent is 0;
  end struct;


(**
 *  Decompose float into normalized fraction and integral exponent for 2.
 *  If the argument (number) is 0.0, -0.0, Infinity, -Infinity or [[#NaN|NaN]]
 *  the fraction is set to the argument and the exponent is set to 0.
 *  For all other arguments the fraction is set to an absolute value
 *  between 0.5(included) and 1.0(excluded). For all values of number holds:
 *   number = fraction * 2.0 ** exponent
 *  @param number Number to be decomposed into fraction and exponent.
 *  @return floatElements with fraction and exponent set.
 *)
const func floatElements: decompose (in float: number) is func
  result
    var floatElements: elements is floatElements.value;
  begin
    decompose(number, elements.fraction, elements.exponent);
  end func;


# Allows 'array float' everywhere without extra type definition.
const type: _floatArray is array float;


enable_io(float);
DECLARE_TERNARY(float);
DECLARE_MIN_MAX(float);