# Copyright (C) 2022-2023 Exaloop Inc. <https://exaloop.io>

@pure
@llvm
def _inf() -> float:
    ret double 0x7FF0000000000000

@pure
@llvm
def _nan() -> float:
    ret double 0x7FF8000000000000

e = 2.7182818284590452354
pi = 3.14159265358979323846
tau = 6.28318530717958647693
inf = _inf()
nan = _nan()

def factorial(x: int) -> int:
    _F = (
        1,
        1,
        2,
        6,
        24,
        120,
        720,
        5040,
        40320,
        362880,
        3628800,
        39916800,
        479001600,
        6227020800,
        87178291200,
        1307674368000,
        20922789888000,
        355687428096000,
        6402373705728000,
        121645100408832000,
        2432902008176640000,
    )
    if not (0 <= x <= 20):
        raise ValueError("factorial is only supported for 0 <= x <= 20")
    return _F[x]

def isnan(x: float) -> bool:
    """
    isnan(float) -> bool

    Return True if float arg is a NaN, else False.
    """
    @pure
    @llvm
    def f(x: float) -> bool:
        %y = fcmp uno double %x, 0.000000e+00
        %z = zext i1 %y to i8
        ret i8 %z

    return f(x)

def isinf(x: float) -> bool:
    """
    isinf(float) -> bool:

    Return True if float arg is an INF, else False.
    """
    @pure
    @llvm
    def f(x: float) -> bool:
        declare double @llvm.fabs.f64(double)
        %a = call double @llvm.fabs.f64(double %x)
        %b = fcmp oeq double %a, 0x7FF0000000000000
        %c = zext i1 %b to i8
        ret i8 %c

    return f(x)

def isfinite(x: float) -> bool:
    """
    isfinite(float) -> bool

    Return True if x is neither an infinity nor a NaN,
    and False otherwise.
    """
    return not (isnan(x) or isinf(x))

def _check1(arg: float, r: float, can_overflow: bool = False):
    if __py_numerics__:
        if isnan(r) and not isnan(arg):
            raise ValueError("math domain error")

        if isinf(r) and isfinite(arg):
            if can_overflow:
                raise OverflowError("math range error")
            else:
                raise ValueError("math domain error")

    return r

def _check2(x: float, y: float, r: float, can_overflow: bool = False):
    if __py_numerics__:
        if isnan(r) and not isnan(x) and not isnan(y):
            raise ValueError("math domain error")

        if isinf(r) and isfinite(x) and isfinite(y):
            if can_overflow:
                raise OverflowError("math range error")
            else:
                raise ValueError("math domain error")

    return r

def ceil(x: float) -> float:
    """
    ceil(float) -> float

    Return the ceiling of x as an Integral.
    This is the smallest integer >= x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.ceil.f64(double)
        %y = call double @llvm.ceil.f64(double %x)
        ret double %y

    return f(x)

def floor(x: float) -> float:
    """
    floor(float) -> float

    Return the floor of x as an Integral.
    This is the largest integer <= x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.floor.f64(double)
        %y = call double @llvm.floor.f64(double %x)
        ret double %y

    return f(x)

def fabs(x: float) -> float:
    """
    fabs(float) -> float

    Returns the absolute value of a floating point number.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.fabs.f64(double)
        %y = call double @llvm.fabs.f64(double %x)
        ret double %y

    return f(x)

def fmod(x: float, y: float) -> float:
    """
    fmod(float, float) -> float

    Returns the remainder of x divided by y.
    """
    @pure
    @llvm
    def f(x: float, y: float) -> float:
        %z = frem double %x, %y
        ret double %z

    return f(x, y)

def exp(x: float) -> float:
    """
    exp(float) -> float

    Returns the value of e raised to the xth power.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.exp.f64(double)
        %y = call double @llvm.exp.f64(double %x)
        ret double %y

    return _check1(x, f(x), True)

def expm1(x: float) -> float:
    """
    expm1(float) -> float

    Return e raised to the power x, minus 1. expm1 provides
    a way to compute this quantity to full precision.
    """
    return _check1(x, _C.expm1(x), True)

def ldexp(x: float, i: int) -> float:
    """
    ldexp(float, int) -> float

    Returns x multiplied by 2 raised to the power of exponent.
    """
    return _check1(x, _C.ldexp(x, i32(i)), True)

def log(x: float, base: float = e) -> float:
    """
    log(float) -> float

    Returns the natural logarithm (base-e logarithm) of x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.log.f64(double)
        %y = call double @llvm.log.f64(double %x)
        ret double %y

    if base == e:
        return _check1(x, f(x))
    else:
        return _check1(x, f(x)) / _check1(base, f(base))

def log2(x: float) -> float:
    """
    log2(float) -> float

    Return the base-2 logarithm of x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.log2.f64(double)
        %y = call double @llvm.log2.f64(double %x)
        ret double %y

    return _check1(x, f(x))

def log10(x: float) -> float:
    """
    log10(float) -> float

    Returns the common logarithm (base-10 logarithm) of x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.log10.f64(double)
        %y = call double @llvm.log10.f64(double %x)
        ret double %y

    return _check1(x, f(x))

def degrees(x: float) -> float:
    """
    degrees(float) -> float

    Convert angle x from radians to degrees.
    """
    radToDeg = 180.0 / pi
    return x * radToDeg

def radians(x: float) -> float:
    """
    radians(float) -> float

    Convert angle x from degrees to radians.
    """
    degToRad = pi / 180.0
    return x * degToRad

def sqrt(x: float) -> float:
    """
    sqrt(float) -> float

    Returns the square root of x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.sqrt.f64(double)
        %y = call double @llvm.sqrt.f64(double %x)
        ret double %y

    return _check1(x, f(x))

def pow(x: float, y: float) -> float:
    """
    pow(float, float) -> float

    Returns x raised to the power of y.
    """
    @pure
    @llvm
    def f(x: float, y: float) -> float:
        declare double @llvm.pow.f64(double, double)
        %z = call double @llvm.pow.f64(double %x, double %y)
        ret double %z

    return _check2(x, y, f(x, y), True)

def acos(x: float) -> float:
    """
    acos(float) -> float

    Returns the arc cosine of x in radians.
    """
    return _check1(x, _C.acos(x))

def asin(x: float) -> float:
    """
    asin(float) -> float

    Returns the arc sine of x in radians.
    """
    return _check1(x, _C.asin(x))

def atan(x: float) -> float:
    """
    atan(float) -> float

    Returns the arc tangent of x in radians.
    """
    return _check1(x, _C.atan(x))

def atan2(y: float, x: float) -> float:
    """
    atan2(float, float) -> float

    Returns the arc tangent in radians of y/x based
    on the signs of both values to determine the
    correct quadrant.
    """
    return _check2(x, y, _C.atan2(y, x))

def cos(x: float) -> float:
    """
    cos(float) -> float

    Returns the cosine of a radian angle x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.cos.f64(double)
        %y = call double @llvm.cos.f64(double %x)
        ret double %y

    return _check1(x, f(x))

def sin(x: float) -> float:
    """
    sin(float) -> float

    Returns the sine of a radian angle x.
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.sin.f64(double)
        %y = call double @llvm.sin.f64(double %x)
        ret double %y

    return _check1(x, f(x))

def hypot(x: float, y: float) -> float:
    """
    hypot(float, float) -> float

    Return the Euclidean norm.
    This is the length of the vector from the
    origin to point (x, y).
    """
    return _check2(x, y, _C.hypot(x, y), True)

def tan(x: float) -> float:
    """
    tan(float) -> float

    Return the tangent of a radian angle x.
    """
    return _check1(x, _C.tan(x))

def cosh(x: float) -> float:
    """
    cosh(float) -> float

    Returns the hyperbolic cosine of x.
    """
    return _check1(x, _C.cosh(x), True)

def sinh(x: float) -> float:
    """
    sinh(float) -> float

    Returns the hyperbolic sine of x.
    """
    return _check1(x, _C.sinh(x), True)

def tanh(x: float) -> float:
    """
    tanh(float) -> float

    Returns the hyperbolic tangent of x.
    """
    return _check1(x, _C.tanh(x))

def acosh(x: float) -> float:
    """
    acosh(float) -> float

    Return the inverse hyperbolic cosine of x.
    """
    return _check1(x, _C.acosh(x))

def asinh(x: float) -> float:
    """
    asinh(float) -> float

    Return the inverse hyperbolic sine of x.
    """
    return _check1(x, _C.asinh(x))

def atanh(x: float) -> float:
    """
    atanh(float) -> float

    Return the inverse hyperbolic tangent of x.
    """
    return _check1(x, _C.atanh(x))

def copysign(x: float, y: float) -> float:
    """
    copysign(float, float) -> float

    Return a float with the magnitude (absolute value) of
    x but the sign of y.
    """
    @pure
    @llvm
    def f(x: float, y: float) -> float:
        declare double @llvm.copysign.f64(double, double)
        %z = call double @llvm.copysign.f64(double %x, double %y)
        ret double %z

    return _check2(x, y, f(x, y))

def log1p(x: float) -> float:
    """
    log1p(float) -> float

    Return the natural logarithm of 1+x (base e).
    """
    return _check1(x, _C.log1p(x))

def trunc(x: float) -> float:
    """
    trunc(float) -> float

    Return the Real value x truncated to an Integral
    (usually an integer).
    """
    @pure
    @llvm
    def f(x: float) -> float:
        declare double @llvm.trunc.f64(double)
        %y = call double @llvm.trunc.f64(double %x)
        ret double %y

    return _check1(x, f(x))

def erf(x: float) -> float:
    """
    erf(float) -> float

    Return the error function at x.
    """
    return _check1(x, _C.erf(x))

def erfc(x: float) -> float:
    """
    erfc(float) -> float

    Return the complementary error function at x.
    """
    return _check1(x, _C.erfc(x))

def gamma(x: float) -> float:
    """
    gamma(float) -> float

    Return the Gamma function at x.
    """
    return _check1(x, _C.tgamma(x), True)

def lgamma(x: float) -> float:
    """
    lgamma(float) -> float

    Return the natural logarithm of
    the absolute value of the Gamma function at x.
    """
    return _check1(x, _C.lgamma(x), True)

def remainder(x: float, y: float) -> float:
    """
    remainder(float, float) -> float

    Return the IEEE 754-style remainder of x with respect to y.
    For finite x and finite nonzero y, this is the difference
    x - n*y, where n is the closest integer to the exact value
    of the quotient x / y. If x / y is exactly halfway between
    two consecutive integers, the nearest even integer is used
    for n.
    """
    return _check2(x, y, _C.remainder(x, y))

def gcd(a: float, b: float) -> float:
    """
    gcd(float, float) -> float

    returns greatest common divisor of x and y.
    """
    a = abs(a)
    b = abs(b)
    while a:
        a, b = b % a, a
    return b

@pure
def frexp(x: float) -> Tuple[float, int]:
    """
    frexp(float) -> Tuple[float, int]

    The returned value is the mantissa and the integer pointed
    to by exponent is the exponent. The resultant value is
    x = mantissa * 2 ^ exponent.
    """
    tmp = i32(0)
    res = _C.frexp(float(x), __ptr__(tmp))
    return (res, int(tmp))

@pure
def modf(x: float) -> Tuple[float, float]:
    """
    modf(float) -> Tuple[float, float]

    The returned value is the fraction component (part after
    the decimal), and sets integer to the integer component.
    """
    tmp = 0.0
    res = _C.modf(float(x), __ptr__(tmp))
    return (res, tmp)

def isclose(a: float, b: float, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> bool:
    """
    isclose(float, float) -> bool

    Return True if a is close in value to b, and False otherwise.
    For the values to be considered close, the difference between them
    must be smaller than at least one of the tolerances.
    """

    # short circuit exact equality -- needed to catch two
    # infinities of the same sign. And perhaps speeds things
    # up a bit sometimes.
    if a == b:
        return True

    # This catches the case of two infinities of opposite sign, or
    # one infinity and one finite number. Two infinities of opposite
    # sign would otherwise have an infinite relative tolerance.
    # Two infinities of the same sign are caught by the equality check
    # above.
    if a == inf or b == inf:
        return False

    # NAN is not close to anything, not even itself
    if a == nan or b == nan:
        return False

    # regular computation
    diff = fabs(b - a)

    return ((diff <= fabs(rel_tol * b)) or (diff <= fabs(rel_tol * a))) or (
        diff <= abs_tol
    )

def fsum(seq):
    def _fsum_realloc(p: Ptr[float], ps: Ptr[float], n: int, m: int):
        from internal.gc import realloc, sizeof
        v = Ptr[float]()
        m += m
        if n < m:
            if p == ps:
                v = Ptr[float](m)
                str.memcpy(v.as_byte(), ps.as_byte(), n * sizeof(float))
            else:
                v = Ptr[float](realloc(p.as_byte(), m * sizeof(float), n * sizeof(float)))
        return v, m

    _NUM_PARTIALS: Static[int] = 32
    ps_arr = __array__[float](_NUM_PARTIALS)
    ps = ps_arr.ptr
    p = ps
    n, m = 0, _NUM_PARTIALS
    xsave, special_sum, inf_sum = 0.0, 0.0, 0.0
    hi, yr, lo = 0.0, 0.0, 0.0

    for item in seq:
        x = float(item)
        xsave = x
        i = 0

        for j in range(n):  # for y in partials
            y = p[j]
            if fabs(x) < fabs(y):
                x, y = y, x
            hi = x + y
            yr = hi - x
            lo = y - yr
            if lo != 0.0:
                p[i] = lo
                i += 1
            x = hi

        n = i
        if x != 0.0:
            if not isfinite(x):
                # a nonfinite x could arise either as
                # a result of intermediate overflow, or
                if isfinite(xsave):
                    raise OverflowError("intermediate overflow in fsum")
                if isinf(xsave):
                    inf_sum += xsave
                special_sum += xsave
                # reset partials
                n = 0
            else:
                if n >= m:
                    p, m = _fsum_realloc(p, ps, n, m)
                p[n] = x
                n += 1

    if special_sum != 0.0:
        if isnan(inf_sum):
            raise ValueError("-inf + inf in fsum")
        else:
            return special_sum

    hi = 0.0
    if n > 0:
        hi = p[n - 1]
        n -= 1
        # sum_exact(ps, hi) from the top, stop when the sum becomes inexact
        while n > 0:
            x = hi
            y = p[n - 1]
            n -= 1
            # assert fabs(y) < fabs(x)
            hi = x + y
            yr = hi - x
            lo = y - yr
            if lo != 0.0:
                break

        # Make half-even rounding work across multiple partials.
        # Needed so that sum([1e-16, 1, 1e16]) will round-up the last
        # digit to two instead of down to zero (the 1e-16 makes the 1
        # slightly closer to two).  With a potential 1 ULP rounding
        # error fixed-up, math.fsum() can guarantee commutativity.
        if n > 0 and ((lo < 0.0 and p[n-1] < 0.0) or (lo > 0.0 and p[n-1] > 0.0)):
            y = lo * 2.0
            x = hi + y
            yr = x - hi
            if y == yr:
                hi = x

    return hi

# 32-bit float ops

e32 = float32(e)
pi32 = float32(pi)
tau32 = float32(tau)

inf32 = float32(inf)
nan32 = float32(nan)

@overload
def isnan(x: float32) -> bool:
    """
    isnan(float32) -> bool

    Return True if float arg is a NaN, else False.
    """
    @pure
    @llvm
    def f(x: float32) -> bool:
        %y = fcmp uno float %x, 0.000000e+00
        %z = zext i1 %y to i8
        ret i8 %z

    return f(x)

@overload
def isinf(x: float32) -> bool:
    """
    isinf(float32) -> bool:

    Return True if float arg is an INF, else False.
    """
    @pure
    @llvm
    def f(x: float32) -> bool:
        declare float @llvm.fabs.f32(float)
        %a = call float @llvm.fabs.f32(float %x)
        %b = fcmp oeq float %a, 0x7FF0000000000000
        %c = zext i1 %b to i8
        ret i8 %c

    return f(x)

@overload
def isfinite(x: float32) -> bool:
    """
    isfinite(float32) -> bool

    Return True if x is neither an infinity nor a NaN,
    and False otherwise.
    """
    return not (isnan(x) or isinf(x))

@overload
def ceil(x: float32) -> float32:
    """
    ceil(float32) -> float32

    Return the ceiling of x as an Integral.
    This is the smallest integer >= x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.ceil.f32(float)
        %y = call float @llvm.ceil.f32(float %x)
        ret float %y

    return f(x)

@overload
def floor(x: float32) -> float32:
    """
    floor(float32) -> float32

    Return the floor of x as an Integral.
    This is the largest integer <= x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.floor.f32(float)
        %y = call float @llvm.floor.f32(float %x)
        ret float %y

    return f(x)

@overload
def fabs(x: float32) -> float32:
    """
    fabs(float32) -> float32

    Returns the absolute value of a float32ing point number.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.fabs.f32(float)
        %y = call float @llvm.fabs.f32(float %x)
        ret float %y

    return f(x)

@overload
def fmod(x: float32, y: float32) -> float32:
    """
    fmod(float32, float32) -> float32

    Returns the remainder of x divided by y.
    """
    @pure
    @llvm
    def f(x: float32, y: float32) -> float32:
        %z = frem float %x, %y
        ret float %z

    return f(x, y)

@overload
def exp(x: float32) -> float32:
    """
    exp(float32) -> float32

    Returns the value of e raised to the xth power.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.exp.f32(float)
        %y = call float @llvm.exp.f32(float %x)
        ret float %y

    return f(x)

@overload
def expm1(x: float32) -> float32:
    """
    expm1(float32) -> float32

    Return e raised to the power x, minus 1. expm1 provides
    a way to compute this quantity to full precision.
    """
    return _C.expm1f(x)

@overload
def ldexp(x: float32, i: int) -> float32:
    """
    ldexp(float32, int) -> float32

    Returns x multiplied by 2 raised to the power of exponent.
    """
    return _C.ldexpf(x, i32(i))

@overload
def log(x: float32, base: float32 = e32) -> float32:
    """
    log(float32) -> float32

    Returns the natural logarithm (base-e logarithm) of x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.log.f32(float)
        %y = call float @llvm.log.f32(float %x)
        ret float %y

    if base == e32:
        return f(x)
    else:
        return f(x) / f(base)

@overload
def log2(x: float32) -> float32:
    """
    log2(float32) -> float32

    Return the base-2 logarithm of x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.log2.f32(float)
        %y = call float @llvm.log2.f32(float %x)
        ret float %y

    return f(x)

@overload
def log10(x: float32) -> float32:
    """
    log10(float32) -> float32

    Returns the common logarithm (base-10 logarithm) of x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.log10.f32(float)
        %y = call float @llvm.log10.f32(float %x)
        ret float %y

    return f(x)

@overload
def degrees(x: float32) -> float32:
    """
    degrees(float32) -> float32

    Convert angle x from radians to degrees.
    """
    radToDeg = float32(180.0) / pi32
    return x * radToDeg

@overload
def radians(x: float32) -> float32:
    """
    radians(float32) -> float32

    Convert angle x from degrees to radians.
    """
    degToRad = pi32 / float32(180.0)
    return x * degToRad

@overload
def sqrt(x: float32) -> float32:
    """
    sqrt(float32) -> float32

    Returns the square root of x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.sqrt.f32(float)
        %y = call float @llvm.sqrt.f32(float %x)
        ret float %y

    return f(x)

@overload
def pow(x: float32, y: float32) -> float32:
    """
    pow(float32, float32) -> float32

    Returns x raised to the power of y.
    """
    @pure
    @llvm
    def f(x: float32, y: float32) -> float32:
        declare float @llvm.pow.f32(float, float)
        %z = call float @llvm.pow.f32(float %x, float %y)
        ret float %z

    return f(x, y)

@overload
def acos(x: float32) -> float32:
    """
    acos(float32) -> float32

    Returns the arc cosine of x in radians.
    """
    return _C.acosf(x)

@overload
def asin(x: float32) -> float32:
    """
    asin(float32) -> float32

    Returns the arc sine of x in radians.
    """
    return _C.asinf(x)

@overload
def atan(x: float32) -> float32:
    """
    atan(float32) -> float32

    Returns the arc tangent of x in radians.
    """
    return _C.atanf(x)

@overload
def atan2(y: float32, x: float32) -> float32:
    """
    atan2(float32, float32) -> float32

    Returns the arc tangent in radians of y/x based
    on the signs of both values to determine the
    correct quadrant.
    """
    return _C.atan2f(y, x)

@overload
def cos(x: float32) -> float32:
    """
    cos(float32) -> float32

    Returns the cosine of a radian angle x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.cos.f32(float)
        %y = call float @llvm.cos.f32(float %x)
        ret float %y

    return f(x)

@overload
def sin(x: float32) -> float32:
    """
    sin(float32) -> float32

    Returns the sine of a radian angle x.
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.sin.f32(float)
        %y = call float @llvm.sin.f32(float %x)
        ret float %y

    return f(x)

@overload
def hypot(x: float32, y: float32) -> float32:
    """
    hypot(float32, float32) -> float32

    Return the Euclidean norm.
    This is the length of the vector from the
    origin to point (x, y).
    """
    return _C.hypotf(x, y)

@overload
def tan(x: float32) -> float32:
    """
    tan(float32) -> float32

    Return the tangent of a radian angle x.
    """
    return _C.tanf(x)

@overload
def cosh(x: float32) -> float32:
    """
    cosh(float32) -> float32

    Returns the hyperbolic cosine of x.
    """
    return _C.coshf(x)

@overload
def sinh(x: float32) -> float32:
    """
    sinh(float32) -> float32

    Returns the hyperbolic sine of x.
    """
    return _C.sinhf(x)

@overload
def tanh(x: float32) -> float32:
    """
    tanh(float32) -> float32

    Returns the hyperbolic tangent of x.
    """
    return _C.tanhf(x)

@overload
def acosh(x: float32) -> float32:
    """
    acosh(float32) -> float32

    Return the inverse hyperbolic cosine of x.
    """
    return _C.acoshf(x)

@overload
def asinh(x: float32) -> float32:
    """
    asinh(float32) -> float32

    Return the inverse hyperbolic sine of x.
    """
    return _C.asinhf(x)

@overload
def atanh(x: float32) -> float32:
    """
    atanh(float32) -> float32

    Return the inverse hyperbolic tangent of x.
    """
    return _C.atanhf(x)

@overload
def copysign(x: float32, y: float32) -> float32:
    """
    copysign(float32, float32) -> float32

    Return a float32 with the magnitude (absolute value) of
    x but the sign of y.
    """
    @pure
    @llvm
    def f(x: float32, y: float32) -> float32:
        declare float @llvm.copysign.f32(float, float)
        %z = call float @llvm.copysign.f32(float %x, float %y)
        ret float %z

    return f(x, y)

@overload
def log1p(x: float32) -> float32:
    """
    log1p(float32) -> float32

    Return the natural logarithm of 1+x (base e).
    """
    return _C.log1pf(x)

@overload
def trunc(x: float32) -> float32:
    """
    trunc(float32) -> float32

    Return the Real value x truncated to an Integral
    (usually an integer).
    """
    @pure
    @llvm
    def f(x: float32) -> float32:
        declare float @llvm.trunc.f32(float)
        %y = call float @llvm.trunc.f32(float %x)
        ret float %y

    return f(x)

@overload
def erf(x: float32) -> float32:
    """
    erf(float32) -> float32

    Return the error function at x.
    """
    return _C.erff(x)

@overload
def erfc(x: float32) -> float32:
    """
    erfc(float32) -> float32

    Return the complementary error function at x.
    """
    return _C.erfcf(x)

@overload
def gamma(x: float32) -> float32:
    """
    gamma(float32) -> float32

    Return the Gamma function at x.
    """
    return _C.tgammaf(x)

@overload
def lgamma(x: float32) -> float32:
    """
    lgamma(float32) -> float32

    Return the natural logarithm of
    the absolute value of the Gamma function at x.
    """
    return _C.lgammaf(x)

@overload
def remainder(x: float32, y: float32) -> float32:
    """
    remainder(float32, float32) -> float32

    Return the IEEE 754-style remainder of x with respect to y.
    For finite x and finite nonzero y, this is the difference
    x - n*y, where n is the closest integer to the exact value
    of the quotient x / y. If x / y is exactly halfway between
    two consecutive integers, the nearest even integer is used
    for n.
    """
    return _C.remainderf(x, y)

@overload
def gcd(a: float32, b: float32) -> float32:
    """
    gcd(float32, float32) -> float32

    returns greatest common divisor of x and y.
    """
    a = abs(a)
    b = abs(b)
    while a:
        a, b = b % a, a
    return b

@overload
@pure
def frexp(x: float32) -> Tuple[float32, int]:
    """
    frexp(float32) -> Tuple[float32, int]

    The returned value is the mantissa and the integer pointed
    to by exponent is the exponent. The resultant value is
    x = mantissa * 2 ^ exponent.
    """
    tmp = i32(0)
    res = _C.frexpf(float32(x), __ptr__(tmp))
    return (res, int(tmp))

@overload
@pure
def modf(x: float32) -> Tuple[float32, float32]:
    """
    modf(float32) -> Tuple[float32, float32]

    The returned value is the fraction component (part after
    the decimal), and sets integer to the integer component.
    """
    tmp = float32(0.0)
    res = _C.modff(float32(x), __ptr__(tmp))
    return (res, tmp)

@overload
def isclose(a: float32, b: float32, rel_tol: float32 = float32(1e-09), abs_tol: float32 = float32(0.0)) -> bool:
    """
    isclose(float32, float32) -> bool

    Return True if a is close in value to b, and False otherwise.
    For the values to be considered close, the difference between them
    must be smaller than at least one of the tolerances.
    """

    # short circuit exact equality -- needed to catch two
    # infinities of the same sign. And perhaps speeds things
    # up a bit sometimes.
    if a == b:
        return True

    # This catches the case of two infinities of opposite sign, or
    # one infinity and one finite number. Two infinities of opposite
    # sign would otherwise have an infinite relative tolerance.
    # Two infinities of the same sign are caught by the equality check
    # above.
    if a == inf32 or b == inf32:
        return False

    # NAN is not close to anything, not even itself
    if a == nan32 or b == nan32:
        return False

    # regular computation
    diff = fabs(b - a)

    return ((diff <= fabs(rel_tol * b)) or (diff <= fabs(rel_tol * a))) or (
        diff <= abs_tol
    )