%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1995-2007, 2011-2012 The University of Melbourne.
% Copyright (C) 2014, 2016-2023 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: math.m.
% Main author: bromage.
% Stability: high.
%
% Higher mathematical operations. (The basics are in float.m.)
%
% By default, domain errors are currently handled by throwing an exception.
% For better performance, each operation in this module that can throw a domain
% exception also has an unchecked version that omits the domain check.
%
% The unchecked operations are semantically safe, since the target math
% library and/or floating point hardware perform these checks for you.
% The benefit of having the Mercury library perform the checks instead is
% that Mercury will tell you in which function or predicate the error
% occurred, as well as giving you a stack trace if that is enabled; with
% the unchecked operations you only have the information that the
% floating-point exception signal handler gives you.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- module math.
:- interface.

%---------------------------------------------------------------------------%
%
% Mathematical constants
%

    % Pythagoras' number.
    %
:- func pi = float.

    % Base of natural logarithms.
    %
:- func e = float.

%---------------------------------------------------------------------------%
%
% "Next integer" operations
%

    % ceiling(X) = Ceil is true if Ceil is the smallest integer
    % not less than X.
    % If X is of infinite magnitude then Ceil = X.
    %
:- func ceiling(float) = float.

    % floor(X) = Floor is true if Floor is the largest integer
    % not greater than X.
    % If X is of infinite magnitude then Floor = X.
    %
:- func floor(float) = float.

    % round(X) = Round is true if Round is the integer closest to X.
    % If X has a fractional value of 0.5, it is rounded up.
    % If X is of infinite magnitude then Round = X.
    %
:- func round(float) = float.

    % truncate(X) = Trunc is true if Trunc is the integer closest to X
    % such that |Trunc| =< |X|.
    % If X is of infinite magnitude then Trunc = X.
    %
:- func truncate(float) = float.

%---------------------------------------------------------------------------%
%
% Polynomial roots
%

    % sqrt(X) = Sqrt is true if Sqrt is the positive square root of X.
    %
    % Domain restriction: X >= 0
    %
:- func sqrt(float) = float.
:- func unchecked_sqrt(float) = float.

:- type quadratic_roots
    --->    no_roots
    ;       one_root(float)
    ;       two_roots(float, float).

    % solve_quadratic(A, B, C) = Roots is true if Roots are
    % the solutions to the equation Ax^2 + Bx + C.
    %
    % Domain restriction: A \= 0
    %
:- func solve_quadratic(float, float, float) = quadratic_roots.

%---------------------------------------------------------------------------%
%
% Power/logarithm operations
%

    % pow(X, Y) = Res is true if Res is X raised to the power of Y.
    %
    % Domain restriction: X >= 0 and (X = 0 implies Y > 0)
    %
:- func pow(float, float) = float.
:- func unchecked_pow(float, float) = float.

    % exp(X) = Exp is true if Exp is e raised to the power of X.
    %
:- func exp(float) = float.

    % ln(X) = Log is true if Log is the natural logarithm of X.
    %
    % Domain restriction: X > 0
    %
:- func ln(float) = float.
:- func unchecked_ln(float) = float.

    % log10(X) = Log is true if Log is the logarithm to base 10 of X.
    %
    % Domain restriction: X > 0
    %
:- func log10(float) = float.
:- func unchecked_log10(float) = float.

    % log2(X) = Log is true if Log is the logarithm to base 2 of X.
    %
    % Domain restriction: X > 0
    %
:- func log2(float) = float.
:- func unchecked_log2(float) = float.

    % log(B, X) = Log is true if Log is the logarithm to base B of X.
    %
    % Domain restriction: X > 0 and B > 0 and B \= 1
    %
:- func log(float, float) = float.
:- func unchecked_log(float, float) = float.

%---------------------------------------------------------------------------%
%
% Trigonometric operations
%

    % sin(X) = Sin is true if Sin is the sine of X.
    %
:- func sin(float) = float.

    % cos(X) = Cos is true if Cos is the cosine of X.
    %
:- func cos(float) = float.

    % tan(X) = Tan is true if Tan is the tangent of X.
    %
:- func tan(float) = float.

    % asin(X) = ASin is true if ASin is the inverse sine of X,
    % where ASin is in the range [-pi/2,pi/2].
    %
    % Domain restriction: X must be in the range [-1,1]
    %
:- func asin(float) = float.
:- func unchecked_asin(float) = float.

    % acos(X) = ACos is true if ACos is the inverse cosine of X,
    % where ACos is in the range [0, pi].
    %
    % Domain restriction: X must be in the range [-1,1]
    %
:- func acos(float) = float.
:- func unchecked_acos(float) = float.

    % atan(X) = ATan is true if ATan is the inverse tangent of X,
    % where ATan is in the range [-pi/2,pi/2].
    %
:- func atan(float) = float.

    % atan2(Y, X) = ATan is true if ATan is the inverse tangent of Y/X,
    % where ATan is in the range [-pi,pi].
    %
:- func atan2(float, float) = float.

%---------------------------------------------------------------------------%
%
% Hyperbolic functions
%

    % sinh(X) = Sinh is true if Sinh is the hyperbolic sine of X.
    %
:- func sinh(float) = float.

    % cosh(X) = Cosh is true if Cosh is the hyperbolic cosine of X.
    %
:- func cosh(float) = float.

    % tanh(X) = Tanh is true if Tanh is the hyperbolic tangent of X.
    %
:- func tanh(float) = float.

%---------------------------------------------------------------------------%
%
% Fused multiply-add operation.
%

    % Succeeds if this grade and platform provide the fused multiply-add
    % operation.
    %
:- pred have_fma is semidet.

    % fma(X, Y, Z) = FMA is true if FMA = (X * Y) + Z, rounded as one
    % floating-point operation.
    %
    % This function is (currently) only available on the C backends and only if
    % the target math library supports it.
    % Use have_fma/0 to check whether it is supported.
    %
:- func fma(float, float, float) = float.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

:- import_module exception.
:- import_module float.

:- pragma foreign_import_module("C", float).    % For ML_FLOAT_{CEIL,FLOOR}.

% These operations are mostly implemented using the C interface.

:- pragma foreign_decl("C", "

    #include <math.h>

    // Mathematical constants.
    //
    // The maximum number of significant decimal digits which can be packed
    // into an IEEE-754 extended precision floating point number is 18.
    // Therefore 20 significant decimal digits for these constants
    // should be plenty.

    #define ML_FLOAT_E      2.7182818284590452354
    #define ML_FLOAT_PI     3.1415926535897932384
    #define ML_FLOAT_LN2    0.69314718055994530941

    // Choose between float or double cmath functions depending on the type of
    // MR_Float
    #if defined(MR_USE_SINGLE_PREC_FLOAT)
        #define ML_EXP(X)       expf(X)
        #define ML_LOG(X)       logf(X)
        #define ML_LOG10(X)     log10f(X)
        #define ML_POW(X, Y)    powf(X, Y)
        #define ML_SQRT(X)      sqrtf(X)
        #define ML_SIN(X)       sinf(X)
        #define ML_COS(X)       cosf(X)
        #define ML_TAN(X)       tanf(X)
        #define ML_ASIN(X)      asinf(X)
        #define ML_ACOS(X)      acosf(X)
        #define ML_ATAN(X)      atanf(X)
        #define ML_ATAN2(X, Y)  atan2f(X, Y)
        #define ML_SINH(X)      sinhf(X)
        #define ML_COSH(X)      coshf(X)
        #define ML_TANH(X)      tanhf(X)
        #if defined(MR_HAVE_FMA)
            #define ML_FMA(X, Y, Z) fmaf(X, Y, Z)
        #endif
    #else
        #define ML_EXP(X)       exp(X)
        #define ML_LOG(X)       log(X)
        #define ML_LOG10(X)     log10(X)
        #define ML_POW(X, Y)    pow(X, Y)
        #define ML_SQRT(X)      sqrt(X)
        #define ML_SIN(X)       sin(X)
        #define ML_COS(X)       cos(X)
        #define ML_TAN(X)       tan(X)
        #define ML_ASIN(X)      asin(X)
        #define ML_ACOS(X)      acos(X)
        #define ML_ATAN(X)      atan(X)
        #define ML_ATAN2(X, Y)  atan2(X, Y)
        #define ML_SINH(X)      sinh(X)
        #define ML_COSH(X)      cosh(X)
        #define ML_TANH(X)      tanh(X)
        #if defined(MR_HAVE_FMA)
            #define ML_FMA(X, Y, Z) fma(X, Y, Z)
        #endif
    #endif

"). % end pragma foreign_decl

:- pragma foreign_code("C#", "

    // This is not defined in the .NET Frameworks.
    // For pi and e we use the constants defined in System.Math.

    public static double ML_FLOAT_LN2 = 0.69314718055994530941;

").

:- pragma foreign_code("Java", "

    // As for .NET, java does not have a built-in ln2

    private static final double ML_FLOAT_LN2 = 0.69314718055994530941;

").

:- pred math_domain_checks is semidet.

:- pragma foreign_proc("C",
    math_domain_checks,
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
#ifdef ML_OMIT_MATH_DOMAIN_CHECKS
    SUCCESS_INDICATOR = MR_FALSE;
#else
    SUCCESS_INDICATOR = MR_TRUE;
#endif
").

:- pragma foreign_proc("C#",
    math_domain_checks,
    [thread_safe, promise_pure],
"
#if ML_OMIT_MATH_DOMAIN_CHECKS
    SUCCESS_INDICATOR = false;
#else
    SUCCESS_INDICATOR = true;
#endif
").

:- pragma foreign_proc("Java",
    math_domain_checks,
    [thread_safe, promise_pure],
"
    SUCCESS_INDICATOR = true;
").

%
% Mathematical constants from math.m
%
    % Pythagoras' number
:- pragma foreign_proc("C",
    pi = (Pi::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Pi = ML_FLOAT_PI;
").
:- pragma foreign_proc("C#",
    pi = (Pi::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Pi = System.Math.PI;
").
:- pragma foreign_proc("Java",
    pi = (Pi::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Pi = java.lang.Math.PI;
").
    % This version is only used for back-ends for which there is no
    % matching foreign_proc version. We define this with sufficient
    % digits that if the underlying implementation's
    % floating point parsing routines are good, it should
    % to be accurate enough for 128-bit IEEE float.
pi = 3.1415926535897932384626433832795029.

    % Base of natural logarithms
:- pragma foreign_proc("C",
    e = (E::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    E = ML_FLOAT_E;
").
:- pragma foreign_proc("C#",
    e = (E::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    E = System.Math.E;
").
:- pragma foreign_proc("Java",
    e = (E::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    E = java.lang.Math.E;
").
    % This version is only used for back-ends for which there is no
    % matching foreign_proc version. We define this with sufficient
    % digits that if the underlying implementation's
    % floating point parsing routines are good, it should
    % to be accurate enough for 128-bit IEEE float.
e = 2.7182818284590452353602874713526625.

:- pragma foreign_proc("C",
    ceiling(Num::in) = (Ceil::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Ceil = ML_FLOAT_CEIL(Num);
").
:- pragma foreign_proc("C#",
    ceiling(Num::in) = (Ceil::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Ceil = System.Math.Ceiling(Num);
").
:- pragma foreign_proc("Java",
    ceiling(Num::in) = (Ceil::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Ceil = java.lang.Math.ceil(Num);
").

:- pragma foreign_proc("C",
    floor(Num::in) = (Floor::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Floor = ML_FLOAT_FLOOR(Num);
").
:- pragma foreign_proc("C#",
    floor(Num::in) = (Floor::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Floor = System.Math.Floor(Num);
").
:- pragma foreign_proc("Java",
    floor(Num::in) = (Floor::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Floor = java.lang.Math.floor(Num);
").

:- pragma foreign_proc("C",
    round(Num::in) = (Rounded::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Rounded = ML_FLOAT_FLOOR(Num + (MR_Float) 0.5);
").
:- pragma foreign_proc("C#",
    round(Num::in) = (Rounded::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    // XXX the semantics of System.Math.Round() are not the same as ours.
    // Unfortunately they are better (round to nearest even number).
    Rounded = System.Math.Floor(Num+0.5);
").
:- pragma foreign_proc("Java",
    round(Num::in) = (Rounded::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    // For +/- infinity Java's round method will return Long.{MAX,MIN}_VALUE.
    // This does not match the documented Mercury behaviour for round with
    // infinities.
    if (java.lang.Double.isInfinite(Num)) {
        Rounded = Num;
    } else {
        Rounded = java.lang.Math.round(Num);
    }
").
round(Num) = math.floor(Num + 0.5).

truncate(X) = ( if X < 0.0 then math.ceiling(X) else math.floor(X)).

sqrt(X) = SquareRoot :-
    ( if math_domain_checks, X < 0.0 then
        throw(domain_error("math.sqrt"))
    else
        SquareRoot = unchecked_sqrt(X)
    ).

:- pragma foreign_proc("C",
    unchecked_sqrt(X::in) = (SquareRoot::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    SquareRoot = ML_SQRT(X);
").
:- pragma foreign_proc("C#",
    unchecked_sqrt(X::in) = (SquareRoot::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    SquareRoot = System.Math.Sqrt(X);
").
:- pragma foreign_proc("Java",
    unchecked_sqrt(X::in) = (SquareRoot::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    SquareRoot = java.lang.Math.sqrt(X);
").
    % This version is only used for back-ends for which there is no
    % matching foreign_proc version.
unchecked_sqrt(X) = math.exp(math.ln(X) / 2.0).

solve_quadratic(A, B, C) = Roots :-
    % This implementation is designed to minimise numerical errors;
    % it is adapted from "Numerical recipes in C".
    DSquared = B * B - 4.0 * A * C,
    compare(CmpD, DSquared, 0.0),
    (
        CmpD = (<),
        Roots = no_roots
    ;
        CmpD = (=),
        Root = -0.5 * B / A,
        Roots = one_root(Root)
    ;
        CmpD = (>),
        D = sqrt(DSquared),
        compare(CmpB, B, 0.0),
        (
            CmpB = (<),
            Q = -0.5 * (B - D),
            Root1 = Q / A,
            Root2 = C / Q
        ;
            CmpB = (=),
            Root1 = -0.5 * D / A,
            Root2 = -Root1
        ;
            CmpB = (>),
            Q = -0.5 * (B + D),
            Root1 = Q / A,
            Root2 = C / Q
        ),
        Roots = two_roots(Root1, Root2)
    ).

pow(X, Y) = Res :-
    ( if math_domain_checks, X < 0.0 then
        throw(domain_error("math.pow"))
    else if X = 0.0 then
        ( if Y =< 0.0 then
            throw(domain_error("math.pow"))
        else
            Res = 0.0
        )
    else
        Res = math.unchecked_pow(X, Y)
    ).

:- pragma foreign_proc("C",
    unchecked_pow(X::in, Y::in) = (Res::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Res = ML_POW(X, Y);
").

:- pragma foreign_proc("C#",
    unchecked_pow(X::in, Y::in) = (Res::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Res = System.Math.Pow(X, Y);
").

:- pragma foreign_proc("Java",
    unchecked_pow(X::in, Y::in) = (Res::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Res = java.lang.Math.pow(X, Y);
").

:- pragma foreign_proc("C",
    exp(X::in) = (Exp::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Exp = ML_EXP(X);
").
:- pragma foreign_proc("C#",
    exp(X::in) = (Exp::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Exp = System.Math.Exp(X);
").
:- pragma foreign_proc("Java",
    exp(X::in) = (Exp::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Exp = java.lang.Math.exp(X);
").

ln(X) = Log :-
    ( if math_domain_checks, X =< 0.0 then
        throw(domain_error("math.ln"))
    else
        Log = math.unchecked_ln(X)
    ).

:- pragma foreign_proc("C",
    unchecked_ln(X::in) = (Log::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Log = ML_LOG(X);
").
:- pragma foreign_proc("C#",
    unchecked_ln(X::in) = (Log::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log = System.Math.Log(X);
").
:- pragma foreign_proc("Java",
    unchecked_ln(X::in) = (Log::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log = java.lang.Math.log(X);
").

log10(X) = Log :-
    ( if math_domain_checks, X =< 0.0 then
        throw(domain_error("math.log10"))
    else
        Log = math.unchecked_log10(X)
    ).

:- pragma foreign_proc("C",
    unchecked_log10(X::in) = (Log10::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Log10 = ML_LOG10(X);
").
:- pragma foreign_proc("C#",
    unchecked_log10(X::in) = (Log10::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log10 = System.Math.Log10(X);
").
:- pragma foreign_proc("Java",
    unchecked_log10(X::in) = (Log10::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log10 = java.lang.Math.log10(X);
").

log2(X) = Log :-
    ( if math_domain_checks, X =< 0.0 then
        throw(domain_error("math.log2"))
    else
        Log = math.unchecked_log2(X)
    ).

:- pragma foreign_proc("C",
    unchecked_log2(X::in) = (Log2::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Log2 = ML_LOG(X) / ML_FLOAT_LN2;
").
:- pragma foreign_proc("C#",
    unchecked_log2(X::in) = (Log2::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log2 = System.Math.Log(X) / ML_FLOAT_LN2;
").
:- pragma foreign_proc("Java",
    unchecked_log2(X::in) = (Log2::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log2 = java.lang.Math.log(X) / ML_FLOAT_LN2;
").
unchecked_log2(X) = math.unchecked_ln(X) / math.unchecked_ln(2.0).

log(B, X) = Log :-
    ( if
        math_domain_checks,
        ( X =< 0.0
        ; B =< 0.0
        ; B = 1.0
        )
    then
        throw(domain_error("math.log"))
    else
        Log = math.unchecked_log(B, X)
    ).

:- pragma foreign_proc("C",
    unchecked_log(B::in, X::in) = (Log::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Log = ML_LOG(X) / ML_LOG(B);
").
:- pragma foreign_proc("C#",
    unchecked_log(B::in, X::in) = (Log::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    Log = System.Math.Log(X, B);
").
% Java implementation will default to mercury here.
unchecked_log(B, X) = math.unchecked_ln(X) / math.unchecked_ln(B).

:- pragma foreign_proc("C",
    sin(X::in) = (Sin::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Sin = ML_SIN(X);
").
:- pragma foreign_proc("C#",
    sin(X::in) = (Sin::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Sin = System.Math.Sin(X);
").
:- pragma foreign_proc("Java",
    sin(X::in) = (Sin::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Sin = java.lang.Math.sin(X);
").

:- pragma foreign_proc("C",
    cos(X::in) = (Cos::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Cos = ML_COS(X);
").
:- pragma foreign_proc("C#",
    cos(X::in) = (Cos::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Cos = System.Math.Cos(X);
").
:- pragma foreign_proc("Java",
    cos(X::in) = (Cos::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Cos = java.lang.Math.cos(X);
").

:- pragma foreign_proc("C",
    tan(X::in) = (Tan::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Tan = ML_TAN(X);
").
:- pragma foreign_proc("C#",
    tan(X::in) = (Tan::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Tan = System.Math.Tan(X);
").
:- pragma foreign_proc("Java",
    tan(X::in) = (Tan::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Tan = java.lang.Math.tan(X);
").

asin(X) = ASin :-
    ( if
        math_domain_checks,
        ( X < -1.0
        ; X > 1.0
        )
    then
        throw(domain_error("math.asin"))
    else
        ASin = math.unchecked_asin(X)
    ).

:- pragma foreign_proc("C",
    unchecked_asin(X::in) = (ASin::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    ASin = ML_ASIN(X);
").
:- pragma foreign_proc("C#",
    unchecked_asin(X::in) = (ASin::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    ASin = System.Math.Asin(X);
").
:- pragma foreign_proc("Java",
    unchecked_asin(X::in) = (ASin::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    ASin = java.lang.Math.asin(X);
").

acos(X) = ACos :-
    ( if
        math_domain_checks,
        ( X < -1.0
        ; X > 1.0
        )
    then
        throw(domain_error("math.acos"))
    else
        ACos = math.unchecked_acos(X)
    ).

:- pragma foreign_proc("C",
    unchecked_acos(X::in) = (ACos::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    ACos = ML_ACOS(X);
").
:- pragma foreign_proc("C#",
    unchecked_acos(X::in) = (ACos::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    ACos = System.Math.Acos(X);
").
:- pragma foreign_proc("Java",
    unchecked_acos(X::in) = (ACos::out),
    [will_not_call_mercury, thread_safe, promise_pure],
"
    ACos = java.lang.Math.acos(X);
").

:- pragma foreign_proc("C",
    atan(X::in) = (ATan::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    ATan = ML_ATAN(X);
").
:- pragma foreign_proc("C#",
    atan(X::in) = (ATan::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    ATan = System.Math.Atan(X);
").
:- pragma foreign_proc("Java",
    atan(X::in) = (ATan::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    ATan = java.lang.Math.atan(X);
").

:- pragma foreign_proc("C",
    atan2(Y::in, X::in) = (ATan2::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    ATan2 = ML_ATAN2(Y, X);
").
:- pragma foreign_proc("C#",
    atan2(Y::in, X::in) = (ATan2::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    ATan2 = System.Math.Atan2(Y, X);
").
:- pragma foreign_proc("Java",
    atan2(Y::in, X::in) = (ATan2::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    ATan2 = java.lang.Math.atan2(Y, X);
").

:- pragma foreign_proc("C",
    sinh(X::in) = (Sinh::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Sinh = ML_SINH(X);
").
:- pragma foreign_proc("Java",
    sinh(X::in) = (Sinh::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Sinh = java.lang.Math.sinh(X);
").
:- pragma foreign_proc("C#",
    sinh(X::in) = (Sinh::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Sinh = System.Math.Sinh(X);
").
% Version for back-ends that do not have a foreign_proc version.
sinh(X) = Sinh :-
    Sinh = (exp(X)-exp(-X)) / 2.0.

:- pragma foreign_proc("C",
    cosh(X::in) = (Cosh::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Cosh = ML_COSH(X);
").
:- pragma foreign_proc("Java",
    cosh(X::in) = (Cosh::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Cosh = java.lang.Math.cosh(X);
").
:- pragma foreign_proc("C#",
    cosh(X::in) = (Cosh::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Cosh = System.Math.Cosh(X);
").
% Version for back-ends that do not have a foreign_proc version.
cosh(X) = Cosh :-
    Cosh = (exp(X)+exp(-X)) / 2.0.

:- pragma foreign_proc("C",
    tanh(X::in) = (Tanh::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
    Tanh = ML_TANH(X);
").
:- pragma foreign_proc("Java",
    tanh(X::in) = (Tanh::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Tanh = java.lang.Math.tanh(X);
").
:- pragma foreign_proc("C#",
    tanh(X::in) = (Tanh::out),
    [will_not_call_mercury, promise_pure, thread_safe],
"
    Tanh = System.Math.Tanh(X);
").
% Version for back-ends that do not have a foreign_proc version.
tanh(X) = Tanh :-
    Tanh = (exp(X)-exp(-X)) / (exp(X)+exp(-X)).

%---------------------------------------------------------------------------%

:- pragma foreign_proc("C",
    have_fma,
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
#if defined(MR_HAVE_FMA)
    SUCCESS_INDICATOR = MR_TRUE;
#else
    SUCCESS_INDICATOR = MR_FALSE;
#endif
").

have_fma :-
    semidet_false.

:- pragma no_determinism_warning(func(fma/3)).

:- pragma foreign_proc("C",
    fma(X::in, Y::in, Z::in) = (FMA::out),
    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
        does_not_affect_liveness],
"
#if defined(MR_HAVE_FMA)
    FMA = ML_FMA(X, Y, Z);
#else
    MR_fatal_error(""math.fma not supported"");
#endif
").

fma(_, _, _) = _ :-
    private_builtin.sorry("math.fma").

% NOTE: Java 9 provides Math.fma.
% NOTE: .NET core 3.0 provides System.Math.FusedMultiplyAdd.

%---------------------------------------------------------------------------%
:- end_module math.
%---------------------------------------------------------------------------%
