/*
* Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* # Mathematical Functions
*
* The `math` module bundles various mathematical and trigonometrical functions.
*
* Functions can be individually imported and directly accessed using the
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
* syntax:
*
* ```
* import { pow, rand } from 'math';
*
* let x = pow(2, 5);
* let y = rand();
* ```
*
* Alternatively, the module namespace can be imported
* using a wildcard import statement:
*
* ```
* import * as math from 'math';
*
* let x = math.pow(2, 5);
* let y = math.rand();
* ```
*
* Additionally, the math module namespace may also be imported by invoking the
* `ucode` interpreter with the `-lmath` switch.
*
* It should be noted that when the ucode interpreter is run as `-p "..."`,
* values involving Infinity are returned as the max double precision value
* +/-1e309 (JSON), whereas when run as `-e "print(...)"` Infinity is
* represented by the string `Infinity`. The boolean check `isinf()` is
* available to determine Infinity values.
*
* @module math
*/
#include <math.h>
#include <errno.h>
#include <sys/time.h>
#include "ucode/module.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif
#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)
/**
* Returns the absolute value of the given numeric value.
*
* @function module:math#abs
*
* @param {*} number
* The number to return the absolute value for.
*
* @returns {number}
* Returns the absolute value or `NaN` if the given argument could
* not be converted to a number.
*/
static uc_value_t *
uc_abs(uc_vm_t *vm, size_t nargs)
{
uc_value_t *v = uc_fn_arg(0), *nv, *res;
int64_t n;
double d;
nv = v ? ucv_to_number(v) : NULL;
switch (ucv_type(nv)) {
case UC_INTEGER:
n = ucv_int64_get(nv);
if (n >= 0 || errno == ERANGE)
res = ucv_get(nv);
else if (n == INT64_MIN)
res = ucv_uint64_new((uint64_t)INT64_MAX + 1);
else
res = ucv_uint64_new(-n);
break;
case UC_DOUBLE:
d = ucv_double_get(nv);
if (isnan(d) || d >= 0)
res = ucv_get(nv);
else
res = ucv_double_new(-d);
break;
default:
res = ucv_double_new(NAN);
break;
}
ucv_put(nv);
return res;
}
/**
* Calculates the arc cosine of `x`.
*
* On success, this function returns the principal value of the arc
* cosine of `x` in radians; the return value is in the range [pi, 0].
*
* - If `x` is -1, pi is returned.
* - If `x` is 0, pi/2 is returned.
* - If `x` is +1, 0 is returned.
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#acos
*
* @param {double} x
* The `x` value.
*
* @returns {double}
* @example
* acos(-1); // 3.1415926535898 i.e. pi
* acos(0); // 1.5707963267949 i.e. pi/2
* acos(1); // 0.0 i.e. 0 pi
*/
static uc_value_t *
uc_arccos(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(acos(x));
}
/**
* Calculates the arc sine of `x`.
*
* On success, this function returns the principal value of the arc
* sine of `x` in radians; the return value is in the range [-pi/2, pi/2].
*
* - If `x` is +0 (-0), 0 is returned.
* - If `x` is +1 (-1), pi/2 (-pi/2) is returned.
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#asin
*
* @param {double} x
* The `x` value.
*
* @returns {double}
* @example
* asin(-1); // -1.5707963267949 i.e. -pi/2
* asin(0); // 0.0 i.e. 0 pi
* asin(1); // 1.5707963267949 i.e. pi/2
*/
static uc_value_t *
uc_arcsin(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(asin(x));
}
/**
* Calculates the arc tangent of `x`.
*
* On success, this function returns the principal value of the arc
* tangent of `x` in radians; the return value is in the range [-pi/2, pi/2].
*
* - If `x` is +0 (-0), 0 is returned.
* - As `x` tends toward +Infinity (-Infinity), the return value asymptotically
* converges toward pi/2 (-pi/2).
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#atan
*
* @param {double} x
* The `x` value.
*
* @returns {double}
* @example
* atan(-100000); // -1.5707863267949 i.e. ~ -pi/2
* atan(0); // 0.0 i.e. 0 pi
* atan(100000); // 1.5707863267949 i.e. ~ pi/2
*/
static uc_value_t *
uc_arctan(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(atan(x));
}
/**
* Calculates the hyperbolic cosine of `x`.
*
* On success, this function returns the principal value of the hyperbolic
* cosine of `x`; the return value is in the range [Infinity, 1].
*
* The relationship is: cosh = `((e^x) + (e^-x)) / 2`.
*
* - As `x` decreases below -1, the return value exponentiates toward Infinity.
* - If `x` is 0, 1 is returned.
* - As `x` increases above +1, the return value exponentiates toward Infinity.
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#cosh
*
* @param {double} x
* The `x` value.
*
* @returns {double}
* @example
* cosh(-10); // 11013.232920103
* cosh(-1); // 1.5430806348152
* cosh(0); // 1.0
* cosh(1); // 1.5430806348152
* cosh(10); // 11013.232920103
*/
static uc_value_t *
uc_cosh(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(cosh(x));
}
/**
* Calculates the hyperbolic sine of `x`.
*
* On success, this function returns the principal value of the hyperbolic
* sine of `x`; the return value is in the range [-Infinity, Infinity].
*
* The relationship is: sinh = `((e^x) - (e^-x)) / 2`.
*
* - As `x` decreases below -1, the return value exponentiates toward -Infinity.
* - If `x` is 0, 0 is returned.
* - As `x` increases above +1, the return value exponentiates toward Infinity.
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#sinh
*
* @param {double} x
* The `x` value.
*
* @returns {double}
* @example
* sinh(-10); // -11013.232920103
* sinh(-1); // -1.1752011936438
* sinh(0); // 0.0
* sinh(1); // 1.1752011936438
* sinh(10); // 11013.232920103
*/
static uc_value_t *
uc_sinh(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(sinh(x));
}
/**
* Calculates the hyperbolic tangent of `x`.
*
* On success, this function returns the principal value of the hyperbolic
* tangent of `x`; the return value is in the range [-1, 1].
*
* The relationship is: tanh = `((e^x) - (e^-x)) / ((e^x) + (e^-x))`, or
* tanh = `sinh(x) / cosh(x)`.
*
* - As `x` decreases below -1, the return value asymptotically expands
* toward -1.
* - If `x` is 0, 0 is returned.
* - As `x` increases above +1, the return value asymptotically expands
* toward 1.
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#tanh
*
* @param {double} x
* The `x` value.
*
* @returns {double}
* @example
* atan(-100); // -1.0
* atan(-10); // -0.99999999587769
* atan(0); // 0.0
* atan(10); // 0.99999999587769
* atan(100); // 1.0
*/
static uc_value_t *
uc_tanh(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(tanh(x));
}
/**
* Calculates the principal value of the arc tangent of `y`/`x`,
* using the signs of the two arguments to determine the quadrant
* of the result.
*
* On success, this function returns the principal value of the arc
* tangent of `y`/`x` in radians; the return value is in the range [-pi, pi].
*
* - If `y` is +0 (-0) and `x` is less than 0, +pi (-pi) is returned.
* - If `y` is +0 (-0) and `x` is greater than 0, +0 (-0) is returned.
* - If `y` is less than 0 and `x` is +0 or -0, -pi/2 is returned.
* - If `y` is greater than 0 and `x` is +0 or -0, pi/2 is returned.
* - If either `x` or `y` is NaN, a NaN is returned.
* - If `y` is +0 (-0) and `x` is -0, +pi (-pi) is returned.
* - If `y` is +0 (-0) and `x` is +0, +0 (-0) is returned.
* - If `y` is a finite value greater (less) than 0, and `x` is negative
* infinity, +pi (-pi) is returned.
* - If `y` is a finite value greater (less) than 0, and `x` is positive
* infinity, +0 (-0) is returned.
* - If `y` is positive infinity (negative infinity), and `x` is finite,
* pi/2 (-pi/2) is returned.
* - If `y` is positive infinity (negative infinity) and `x` is negative
* infinity, +3 * pi/4 (-3 * pi/4) is returned.
* - If `y` is positive infinity (negative infinity) and `x` is positive
* infinity, +pi/4 (-pi/4) is returned.
*
* When either `x` or `y` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#atan2
*
* @param {*} y
* The `y` value.
*
* @param {*} x
* The `x` value.
*
* @returns {number}
*/
static uc_value_t *
uc_atan2(uc_vm_t *vm, size_t nargs)
{
double d1 = ucv_to_double(uc_fn_arg(0));
double d2 = ucv_to_double(uc_fn_arg(1));
if (isnan(d1) || isnan(d2))
return ucv_double_new(NAN);
return ucv_double_new(atan2(d1, d2));
}
/**
* Calculates the tangent of `x`, the floating-point value representing the
* angle in radians.
*
* On success, this function returns the tangent of `x`.
*
* The relationship is `tan(x) = sin(x) / cos (x)`. A graph of the tangent has
* periodic patterns directly related to ratios of pi, where radian values of
* whole multiples of (1, 2, 3, ...) pi are 0, and radian values of half
* multiples of pi (1/2, 3/2, 5/2, ...) are +/-Infinity.
*
*
* When `x` can't be converted to a numeric value, `NaN` is
* returned.
*
* @function module:math#tan
*
* @param {double} x
* The `x` value.
*
* @returns {double}
*/
static uc_value_t *
uc_tan(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(tan(x));
}
/**
* Calculates the cosine of `x`, where `x` is given in radians.
*
* Returns the resulting cosine value.
*
* Returns `NaN` if the `x` value can't be converted to a number.
*
* @function module:math#cos
*
* @param {number} x
* Radians value to calculate cosine for.
*
* @returns {number}
*/
static uc_value_t *
uc_cos(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
if (isnan(d))
return ucv_double_new(NAN);
return ucv_double_new(cos(d));
}
/**
* Calculates the value of `e` (the base of natural logarithms)
* raised to the power of `x`.
*
* On success, returns the exponential value of `x`.
*
* - If `x` is positive infinity, positive infinity is returned.
* - If `x` is negative infinity, `+0` is returned.
* - If the result underflows, a range error occurs, and zero is returned.
* - If the result overflows, a range error occurs, and `Infinity` is returned.
*
* Returns `NaN` if the `x` value can't be converted to a number.
*
* @function module:math#exp
*
* @param {number} x
* Power to raise `e` to.
*
* @returns {number}
*/
static uc_value_t *
uc_exp(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
if (isnan(d))
return ucv_double_new(NAN);
return ucv_double_new(exp(d));
}
/**
* Calculates the natural logarithm of `x`.
*
* On success, returns the natural logarithm of `x`.
*
* - If `x` is `1`, the result is `+0`.
* - If `x` is positive infinity, positive infinity is returned.
* - If `x` is zero, then a pole error occurs, and the function
* returns negative infinity.
* - If `x` is negative (including negative infinity), then a domain
* error occurs, and `NaN` is returned.
*
* Returns `NaN` if the `x` value can't be converted to a number.
*
* @function module:math#log
*
* @param {number} x
* Value to calculate natural logarithm of.
*
* @returns {number}
*/
static uc_value_t *
uc_log(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
if (isnan(d))
return ucv_double_new(NAN);
return ucv_double_new(log(d));
}
/**
* Calculate base-10 log of x.
*
* @function module:math#log10
*
* @param {double} x number
*
* @returns {double}
* The common (base-10) logarithm of x, or
* `NaN` if the given argument could not be converted to a number.
*
* @example
* log10(100); // 2.0
* log10(10); // 1.0
* log10(5); // 0.69897000433602
* log10(1); // 0.0
* log10(0); // -1e309
*/
static uc_value_t *
uc_log10(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(log10(x));
}
/**
* Calculate base-2 log of x.
*
* @function module:math#log2
*
* @param {double} x number
*
* @returns {double}
* The common (base-2) logarithm of x, or
* `NaN` if the given argument could not be converted to a number.
*
* @example
* log2(1024); // 10.0
* log2(512); // 9.0
* log2(16); // 4.0
* log2(4); // 2.0
* log2(2); // 1.0
* log2(1); // 0.0
* log2(0); // -1e309
*/
static uc_value_t *
uc_log2(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(log2(x));
}
/**
* Computes the natural (base e) logarithm of 1 + x. This function is more
* precise than the expression {@link module:math#log `log`}(1 + x) if x is
* close to zero.
*
* @function module:math#log1p
*
* @param {double} x number
*
* @returns {double}
* The natural (base e) logarithm of 1 + x, or
* `NaN` if the given argument could not be converted to a number.
*
* @example
* log1p(10); // 2.3978952727984
* log1p(1); // 0.69314718055995
* log1p(0.1); // 0.095310179804325
* log1p(0.001); // 0.00099950033308353
* log1p(0); // 0.0
*/
static uc_value_t *
uc_log1p(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(log1p(x));
}
/**
* Computes the e (Euler's number, 2.7182818) raised to the given power x,
* minus 1.0. This function is more accurate than the expression
* {@link module:math#exp `exp(x)`}-1.0
* if x is close to zero.
*
* @function module:math#expm1
*
* @param {double} x number
*
* @returns {double}
* The e (Euler's number, 2.7182818) raised to the given power x, minus 1.0, or
* `NaN` if the given argument could not be converted to a number.
*
* @example
* expm1(10); // 22025.465794807
* expm1(1); // 1.718281828459
* expm1(0.1); // 0.10517091807565
* expm1(0.001); // 0.0010005001667083
* exp(0.001)-1; // 0.0010005001667084
* expm1(0.0001); // 0.00010000500016667
* exp(0.0001)-1; // 0.00010000500016671
* expm1(0.000001); // 1.0000005000002e-06
* exp(0.000001)-1; // 1.0000004999622e-06
* expm1(0); // 0.0
*/
static uc_value_t *
uc_expm1(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(expm1(x));
}
/**
* Calculates the sine of `x`, where `x` is given in radians.
*
* Returns the resulting sine value.
*
* - When `x` is positive or negative infinity, a domain error occurs
* and `NaN` is returned.
*
* Returns `NaN` if the `x` value can't be converted to a number.
*
* @function module:math#sin
*
* @param {number} x
* Radians value to calculate sine for.
*
* @returns {number}
*/
static uc_value_t *
uc_sin(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
if (isnan(d))
return ucv_double_new(NAN);
return ucv_double_new(sin(d));
}
/**
* Calculates the non-negative square root of `x`.
*
* Returns the resulting square root value.
*
* - If `x` is `+0` (`-0`) then `+0` (`-0`) is returned.
* - If `x` is positive infinity, positive infinity is returned.
* - If `x` is less than `-0`, a domain error occurs, and `NaN` is returned.
*
* Returns `NaN` if the `x` value can't be converted to a number.
*
* @function module:math#sqrt
*
* @param {number} x
* Value to calculate square root for.
*
* @returns {number}
*/
static uc_value_t *
uc_sqrt(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
if (isnan(d))
return ucv_double_new(NAN);
return ucv_double_new(sqrt(d));
}
/**
* Calculates the cube root of `x`.
*
* Returns the resulting cube root value.
*
* - If `x` is `+0` (`-0`) then `+0` (`-0`) is returned.
* - If `x` is (+/-) infinity, (+/-) infinity is returned.
*
* Returns `NaN` if the `x` value can't be converted to a number.
*
* @function module:math#cbrt
*
* @param {double} x
* Value to calculate cube root for.
*
* @returns {double}
* @example
* cbrt(27); // 3.0
* cbrt(0); // 0.0
* cbrt(-27); // -3.0
*/
static uc_value_t *
uc_cbrt(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_double_new(cbrt(x));
}
/**
* Computes the square root of the sum of the squares of `x` and `y`, i.e.
* the hypotenuse without undue overflow or underflow at intermediate stages of
* the computation.
*
* Returns the result of `sqrt(x^2 + y^2)`.
*
* - If `x` and `y` are `+0` (`-0`) then `+0` (`-0`) is returned.
* - If `x` or `y` is `+0` (`-0`) then `+y` or `+x` is returned.
* - If `x` or `y` is (+/-) infinity, (+/-) infinity is returned.
*
* Returns `NaN` if the `x` or `y` value can't be converted to a number.
*
* @function module:math#hypot
*
* @param {double} x base
* @param {double} y height
*
* @returns {double}
* @example
* hypot(3, 3); // 4.2426406871193
* hypot(2, 2); // 2.8284271247462
* hypot(1, 1); // 1.4142135623731
* hypot(0, 0); // 0.0
* hypot(-1, -1); // -1.4142135623731
*/
static uc_value_t *
uc_hypot(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
double y = ucv_to_double(uc_fn_arg(1));
if (isnan(x) || isnan(y))
return ucv_double_new(NAN);
return ucv_double_new(hypot(x, y));
}
/**
* Calculates the value of `x` raised to the power of `y`.
*
* On success, returns the value of `x` raised to the power of `y`.
*
* - If the result overflows, a range error occurs, and the function
* returns `Infinity`.
* - If result underflows, and is not representable, a range error
* occurs, and `0.0` with the appropriate sign is returned.
* - If `x` is `+0` or `-0`, and `y` is an odd integer less than `0`,
* a pole error occurs `Infinity` is returned, with the same sign
* as `x`.
* - If `x` is `+0` or `-0`, and `y` is less than `0` and not an odd
* integer, a pole error occurs and `Infinity` is returned.
* - If `x` is `+0` (`-0`), and `y` is an odd integer greater than `0`,
* the result is `+0` (`-0`).
* - If `x` is `0`, and `y` greater than `0` and not an odd integer,
* the result is `+0`.
* - If `x` is `-1`, and `y` is positive infinity or negative infinity,
* the result is `1.0`.
* - If `x` is `+1`, the result is `1.0` (even if `y` is `NaN`).
* - If `y` is `0`, the result is `1.0` (even if `x` is `NaN`).
* - If `x` is a finite value less than `0`, and `y` is a finite
* non-integer, a domain error occurs, and `NaN` is returned.
* - If the absolute value of `x` is less than `1`, and `y` is negative
* infinity, the result is positive infinity.
* - If the absolute value of `x` is greater than `1`, and `y` is
* negative infinity, the result is `+0`.
* - If the absolute value of `x` is less than `1`, and `y` is positive
* infinity, the result is `+0`.
* - If the absolute value of `x` is greater than `1`, and `y` is positive
* infinity, the result is positive infinity.
* - If `x` is negative infinity, and `y` is an odd integer less than `0`,
* the result is `-0`.
* - If `x` is negative infinity, and `y` less than `0` and not an odd
* integer, the result is `+0`.
* - If `x` is negative infinity, and `y` is an odd integer greater than
* `0`, the result is negative infinity.
* - If `x` is negative infinity, and `y` greater than `0` and not an odd
* integer, the result is positive infinity.
* - If `x` is positive infinity, and `y` less than `0`, the result is `+0`.
* - If `x` is positive infinity, and `y` greater than `0`, the result is
* positive infinity.
*
* Returns `NaN` if either the `x` or `y` value can't be converted to a number.
*
* @function module:math#pow
*
* @param {number} x
* The base value.
*
* @param {number} y
* The power value.
*
* @returns {number}
*/
static uc_value_t *
uc_pow(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
double y = ucv_to_double(uc_fn_arg(1));
if (isnan(x) || isnan(y))
return ucv_double_new(NAN);
return ucv_double_new(pow(x, y));
}
/**
* Depending on the arguments, it produces a pseudo-random positive integer,
* or a pseudo-random number in a supplied range.
*
* Without arguments it returns the calculated pseuo-random value. The value
* is within the range `0` to `RAND_MAX` inclusive where `RAND_MAX` is a platform
* specific value guaranteed to be at least `32767`.
*
* With 2 arguments `a, b` it returns a number in the range `a` to `b` inclusive.
* With a single argument `a` it returns a number in the range `0` to `a` inclusive.
*
* The {@link module:math#srand `srand()`} function sets its argument as the
* seed for a new sequence of pseudo-random integers to be returned by `rand()`.
* These sequences are repeatable by calling {@link module:math#srand `srand()`}
* with the same seed value.
*
* If no seed value is explicitly set by calling
* {@link module:math#srand `srand()`} prior to the first call to `rand()`,
* the math module will automatically seed the PRNG once, using the current
* time of day in milliseconds as seed value.
*
* @function module:math#rand
*
* @param {number} [a]
* End of the desired range.
*
* @param {number} [b]
* The other end of the desired range.
*
* @returns {number}
*/
static uc_value_t *
uc_rand(uc_vm_t *vm, size_t nargs)
{
struct timeval tv;
if (!ucv_boolean_get(uc_vm_registry_get(vm, "math.srand_called"))) {
gettimeofday(&tv, NULL);
srand((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
uc_vm_registry_set(vm, "math.srand_called", ucv_boolean_new(true));
}
if (nargs == 0)
return ucv_int64_new(rand());
double a = ucv_to_double(uc_fn_arg(0)), b = 0;
if (nargs > 1)
b = ucv_to_double(uc_fn_arg(1));
return ucv_double_new(a + ((b - a) * rand()) / RAND_MAX);
}
/**
* Seeds the pseudo-random number generator.
*
* This functions seeds the PRNG with the given value and thus affects the
* pseudo-random integer sequence produced by subsequent calls to
* {@link module:math#rand `rand()`}.
*
* Setting the same seed value will result in the same pseudo-random numbers
* produced by {@link module:math#rand `rand()`}.
*
* @function module:math#srand
*
* @param {number} seed
* The seed value.
*/
static uc_value_t *
uc_srand(uc_vm_t *vm, size_t nargs)
{
int64_t n = ucv_to_integer(uc_fn_arg(0));
srand((unsigned int)n);
uc_vm_registry_set(vm, "math.srand_called", ucv_boolean_new(true));
return NULL;
}
/**
* Tests whether `x` is a `NaN` double.
*
* This functions checks whether the given argument is of type `double` with
* a `NaN` (not a number) value.
*
* Returns `true` if the value is `NaN`, otherwise false.
*
* Note that a value can also be checked for `NaN` with the expression
* `x !== x` which only evaluates to `true` if `x` is `NaN`.
*
* @function module:math#isnan
*
* @param {number} x
* The value to test.
*
* @returns {boolean}
*/
static uc_value_t *
uc_isnan(uc_vm_t *vm, size_t nargs)
{
uc_value_t *v = uc_fn_arg(0);
return ucv_boolean_new(ucv_type(v) == UC_DOUBLE && isnan(ucv_double_get(v)));
}
/**
* Tests whether `x` is double precision `Infinity`.
*
* This functions checks whether the given argument is of type `double` of
* `Infinity` value. Double precision values >= 1.8e308 are considered `Infinity`.
*
* Returns `true` if the value is `Infinity`, otherwise false.
*
* @function module:math#isinf
*
* @param {number} x
* The value to test.
*
* @returns {boolean}
*/
static uc_value_t *
uc_isinf(uc_vm_t *vm, size_t nargs)
{
uc_value_t *v = uc_fn_arg(0);
return ucv_boolean_new(ucv_type(v) == UC_DOUBLE && isinf(ucv_double_get(v)));
}
/**
* Returns the radian value of the given degree value.
*
* @function module:math#deg2rad
*
* @param {double} number
* The number to return the radian value for.
*
* @returns {number}
* Returns the absolute value or `NaN` if the given argument could
* not be converted to a number.
* @example
* deg2rad(180); // 3.1415926535898
* deg2rad("180"); // 3.1415926535898
*/
static uc_value_t *
uc_deg2rad(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
return ucv_double_new(degToRad(d));
}
/**
* Returns the degree value of the given radian value.
*
* @function module:math#rad2deg
*
* @param {double} number
* The number to return the degree value for.
*
* @returns {number}
* Returns the absolute value or `NaN` if the given argument could
* not be converted to a number.
* @example
* rad2deg(3.1415926535898); // 180.0
* rad2deg("3.1415926535898"); // 180.0
*/
static uc_value_t *
uc_rad2deg(uc_vm_t *vm, size_t nargs)
{
double d = ucv_to_double(uc_fn_arg(0));
return ucv_double_new(radToDeg(d));
}
/**
* Returns the lesser of two values x and y.
*
* @function module:math#fmin
*
* @param {double} x first parameter
* @param {double} y second parameter
*
* @returns {double}
* Returns the lesser of the two values x or y or `NaN` if a given argument
* could not be converted to a number. Use `(-)Infinity` or `NAN` for
* comparisons involving said values.
* @example
* fmin("180", "-180"); // -180.0
* fmin(180, -180); // -180.0
* fmin(-Infinity, 0); // -1e309 i.e. -infinity in double type representation.
*/
static uc_value_t *
uc_fmin(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
double y = ucv_to_double(uc_fn_arg(1));
if (isnan(x) || isnan(y))
return ucv_double_new(NAN);
return ucv_double_new(fmin(x, y));
}
/**
* Returns the greater of two values x and y.
*
* @function module:math#fmax
*
* @param {double} x first parameter
* @param {double} y second parameter
*
* @returns {double}
* Returns the greater of the two values x or y or `NaN` if a given argument
* could not be converted to a number. Use `(-)Infinity` or `NAN` for
* comparisons involving said values.
* @example
* fmax("180", "-180"); // 180.0
* fmax(180, -180); // 180.0
* fmax(Infinity, 0); // 1e309 i.e. infinity in double type representation.
*/
static uc_value_t *
uc_fmax(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
double y = ucv_to_double(uc_fn_arg(1));
if (isnan(x) || isnan(y))
return ucv_double_new(NAN);
return ucv_double_new(fmax(x, y));
}
/**
* Clamps `x` to within `upper` and `lower` bounds if `x` exceeds them.
*
* The operation is effectively: `min(upper, max(x, lower))`.
*
* @function module:math#clamp
*
* @param {double} x number to clamp
* @param {double} upper upper bound
* @param {double} lower lower bound
*
* @returns {double}
* Returns `x` if within `upper` and `lower`, otherwise one of `lower` or `upper`
* if `x` exceeds those bounds, or `NaN` if any given argument
* could not be converted to a number.
* @example
* clamp(1000, 200, 180); // 200.0
* clamp(-1000, 200, 180); // 180.0
* clamp(190, 200, 180); // 190.0
*/
static uc_value_t *
uc_clamp(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
double upper = ucv_to_double(uc_fn_arg(1));
double lower = ucv_to_double(uc_fn_arg(2));
if (isnan(x) || isnan(upper) || isnan(lower))
return ucv_double_new(NAN);
return ucv_double_new(fmin(upper, fmax(x, lower)));
}
/**
* Returns -1 or 1 depending on the sign of the given number, or 0 if the given
* number itself is zero.
*
* @function module:math#sign
*
* @param {double} x number
*
* @returns {integer}
* Returns -1 or 1 for negative and positive inputs respectively, 0 if the given
* number is zero, or `NaN` if the given argument could not be converted to a
* number.
* @example
* sign(2); // 1
* sign(-8); // -1
* sign(0); // 0
* sign(-0); // 0
*/
static uc_value_t *
uc_sign(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_int64_new((x > 0) - (x < 0));
}
/**
* Returns -1 or 1 depending on the sign of the given number, or 0 if the given
* number itself is zero. IEEE-754 behaviour.
*
* @function module:math#signbit
*
* @param {double} x number
*
* @returns {integer}
* Returns -1 or 1 for negative and positive inputs respectively, 0 if the given
* number is zero, -1 for -0.0, or `NaN` if the given argument could not be
* converted to a number.
* @example
* signbit(2); // 1
* signbit(-8); // -1
* signbit(0); // 0
* signbit(-0.0); // -1
* signbit(-0); // 0
*/
static uc_value_t *
uc_signbit(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_int64_new(signbit(x) ? -1 : (x > 0));
}
/**
* Returns -1 or 1 depending on the sign of the given number only (no zero).
* (-)Zero effectively becomes 1.
*
* @function module:math#signnz
*
* @param {double} x number
*
* @returns {integer}
* Returns -1 or +1 for negative and positive inputs respectively, and zero is
* converted to +1, or `NaN` if the given argument could not be converted to a
* number.
* @example
* signnz(2); // 1
* signnz(-8); // -1
* signnz(0); // 1
* signnz(-0); // 1
*/
static uc_value_t *
uc_signnz(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
if (isnan(x))
return ucv_double_new(NAN);
return ucv_int64_new((x >= 0) ? 1 : -1);
}
/**
* Returns a double whose magnitude is that of `x`, but whose sign is that of
* `y`.
*
* @function module:math#copysign
*
* @param {double} x number
* @param {double} y number
*
* @returns {double}
* Returns `NaN` if a given argument could not be converted to a number.
* @example
* copysign(-3, -5); // -3.0
* copysign(8, -5); // -8.0
* copysign(-0, 3); // 0.0
*/
static uc_value_t *
uc_copysign(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
double y = ucv_to_double(uc_fn_arg(1));
if (isnan(x) || isnan(y))
return ucv_double_new(NAN);
return ucv_double_new(copysign(x, y));
}
/**
* Floors `x` to the largest integer value not greater than `x`.
*
* @function module:math#floor
*
* @param {double} x number
* @param {boolean} output_type - false is `integer`, true is `double`.
*
* @returns {number}
* Returns the largest integer value not greater than `x`, or `NaN` if the given
* argument could not be converted to a number.
* @example
* floor(2.7); // 2
* floor(2.7, true); // 2.0
* floor(-2.7); // -3
* floor(-0.0); // 0
* floor(-0.0, true); // -0.0
* floor(-Infinity); // -1e309 i.e. -infinity in double type representation.
*/
static uc_value_t *
uc_floor(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
bool ot = ucv_boolean_get(uc_fn_arg(1));
if (isnan(x))
return ucv_double_new(NAN);
return ot ? ucv_double_new(floor(x)) : ucv_int64_new(floor(x));
}
/**
* Computes the smallest integer value not less than `x`.
*
* @function module:math#ceil
*
* @param {double} x number
* @param {boolean} output_type - false is `integer`, true is `double`.
*
* @returns {number}
* Returns the smallest integer value not less than `x`, or `NaN` if the given
* argument could not be converted to a number.
* @example
* ceil(2.7); // 3
* ceil(2.7, true); // 3.0
* ceil(-2.7); // -2
* ceil(-0.0); // 0
* ceil(-0.0, true); // -0.0
* ceil(-Infinity); // -1e309 i.e. -infinity in double type representation.
*/
static uc_value_t *
uc_ceil(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
bool ot = ucv_boolean_get(uc_fn_arg(1));
if (isnan(x))
return ucv_double_new(NAN);
return ot ? ucv_double_new(ceil(x)) : ucv_int64_new(ceil(x));
}
/**
* Returns the integral value nearest to x rounding half-way cases away
* from zero, regardless of the current rounding direction.
*
* @function module:math#round
*
* @param {double} x number
* @param {boolean} output_type - false is `integer`, true is `double`.
*
* @returns {number}
* Returns the rounded integer value of `x`, or `NaN` if the given
* argument could not be converted to a number.
*
* @example
* round(2.4); // 2
* round(2.5); // 3
* round(2.7); // 3
* round(2.7, true); // 3.0
* round(-2.4); // -2
* round(-2.5); // -3
* round(-2.7); // -3
* round(-0.0); // 0
* round(-0.0, true); // -0.0
* round(-Infinity); // -1e309 i.e. -infinity in double type representation.
*/
static uc_value_t *
uc_round(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
bool ot = ucv_boolean_get(uc_fn_arg(1));
if (isnan(x))
return ucv_double_new(NAN);
return ot ? ucv_double_new(round(x)) : ucv_int64_new(round(x));
}
/**
* Truncate away the decimal portion to produce the nearest integer not greater
* in magnitude than x.
*
* @function module:math#trunc
*
* @param {number} x number
* @param {boolean} output_type - false is `integer`, true is `double`.
*
* @returns {number}
* The integral portion remaining after the decimal portion is truncated, or
* `NaN` if the given argument could not be converted to a number.
*
* @example
* trunc(2.4); // 2
* trunc(2.5); // 2
* trunc(2.7); // 2
* trunc(2.7, true); // 2.0
* trunc(-2.4); // -2
* trunc(-2.5); // -2
* trunc(-2.7); // -2
* trunc(-0.0); // 0
* trunc(-0.0, true); // -0.0
*/
static uc_value_t *
uc_trunc(uc_vm_t *vm, size_t nargs)
{
double x = ucv_to_double(uc_fn_arg(0));
bool ot = ucv_boolean_get(uc_fn_arg(1));
if (isnan(x))
return ucv_double_new(NAN);
return ot ? ucv_double_new(trunc(x)) : ucv_int64_new(trunc(x));
}
static const uc_function_list_t math_fns[] = {
{ "abs", uc_abs },
{ "acos", uc_arccos },
{ "asin", uc_arcsin },
{ "atan", uc_arctan },
{ "atan2", uc_atan2 },
{ "cosh", uc_cosh },
{ "sinh", uc_sinh },
{ "tanh", uc_tanh },
{ "tan", uc_tan },
{ "cos", uc_cos },
{ "exp", uc_exp },
{ "expm1", uc_expm1 },
{ "log", uc_log },
{ "log1p", uc_log1p },
{ "log10", uc_log10 },
{ "log2", uc_log2 },
{ "sin", uc_sin },
{ "sqrt", uc_sqrt },
{ "hypot", uc_hypot },
{ "cbrt", uc_cbrt },
{ "pow", uc_pow },
{ "rand", uc_rand },
{ "srand", uc_srand },
{ "isnan", uc_isnan },
{ "isinf", uc_isinf },
{ "deg2rad", uc_deg2rad },
{ "rad2deg", uc_rad2deg },
{ "fmin", uc_fmin },
{ "fmax", uc_fmax },
{ "clamp", uc_clamp },
{ "sign", uc_sign },
{ "signbit", uc_signbit },
{ "signnz", uc_signnz },
{ "copysign", uc_copysign },
{ "floor", uc_floor },
{ "ceil", uc_ceil },
{ "round", uc_round },
{ "trunc", uc_trunc },
};
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, math_fns);
uc_vm_registry_set(vm, "math.srand_called", ucv_boolean_new(false));
}