/* * Copyright (C) 2020-2021 Jo-Philipp Wich * * 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. * * @module math */ #include #include #include #include "ucode/module.h" /** * 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 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 cosine of `x`, where `x` is given in radians. * * Returns the resulting consine 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 nfinity, 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 calulate 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)); } /** * 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 nonnegative 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 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 * noninteger, 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)); } /** * Produces a pseudo-random positive integer. * * 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`. * * 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 * * @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)); } return ucv_int64_new(rand()); } /** * 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))); } static const uc_function_list_t math_fns[] = { { "abs", uc_abs }, { "atan2", uc_atan2 }, { "cos", uc_cos }, { "exp", uc_exp }, { "log", uc_log }, { "sin", uc_sin }, { "sqrt", uc_sqrt }, { "pow", uc_pow }, { "rand", uc_rand }, { "srand", uc_srand }, { "isnan", uc_isnan }, }; 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)); }