/* * Math built-ins */ #include "duk_internal.h" #if defined(DUK_USE_MATH_BUILTIN) /* * Use static helpers which can work with math.h functions matching * the following signatures. This is not portable if any of these math * functions is actually a macro. * * Typing here is intentionally 'double' wherever values interact with * the standard library APIs. */ typedef double (*duk__one_arg_func)(double); typedef double (*duk__two_arg_func)(double, double); DUK_LOCAL duk_ret_t duk__math_minmax(duk_hthread *thr, duk_double_t initial, duk__two_arg_func min_max) { duk_idx_t n = duk_get_top(thr); duk_idx_t i; duk_double_t res = initial; duk_double_t t; /* * Note: fmax() does not match the E5 semantics. E5 requires * that if -any- input to Math.max() is a NaN, the result is a * NaN. fmax() will return a NaN only if -both- inputs are NaN. * Same applies to fmin(). * * Note: every input value must be coerced with ToNumber(), even * if we know the result will be a NaN anyway: ToNumber() may have * side effects for which even order of evaluation matters. */ for (i = 0; i < n; i++) { t = duk_to_number(thr, i); if (DUK_FPCLASSIFY(t) == DUK_FP_NAN || DUK_FPCLASSIFY(res) == DUK_FP_NAN) { /* Note: not normalized, but duk_push_number() will normalize */ res = (duk_double_t) DUK_DOUBLE_NAN; } else { res = (duk_double_t) min_max(res, (double) t); } } duk_push_number(thr, res); return 1; } DUK_LOCAL double duk__fmin_fixed(double x, double y) { /* fmin() with args -0 and +0 is not guaranteed to return * -0 as ECMAScript requires. */ if (duk_double_equals(x, 0.0) && duk_double_equals(y, 0.0)) { duk_double_union du1, du2; du1.d = x; du2.d = y; /* Already checked to be zero so these must hold, and allow us * to check for "x is -0 or y is -0" by ORing the high parts * for comparison. */ DUK_ASSERT(du1.ui[DUK_DBL_IDX_UI0] == 0 || du1.ui[DUK_DBL_IDX_UI0] == 0x80000000UL); DUK_ASSERT(du2.ui[DUK_DBL_IDX_UI0] == 0 || du2.ui[DUK_DBL_IDX_UI0] == 0x80000000UL); /* XXX: what's the safest way of creating a negative zero? */ if ((du1.ui[DUK_DBL_IDX_UI0] | du2.ui[DUK_DBL_IDX_UI0]) != 0) { /* Enter here if either x or y (or both) is -0. */ return -0.0; } else { return +0.0; } } return duk_double_fmin(x, y); } DUK_LOCAL double duk__fmax_fixed(double x, double y) { /* fmax() with args -0 and +0 is not guaranteed to return * +0 as ECMAScript requires. */ if (duk_double_equals(x, 0.0) && duk_double_equals(y, 0.0)) { if (DUK_SIGNBIT(x) == 0 || DUK_SIGNBIT(y) == 0) { return +0.0; } else { return -0.0; } } return duk_double_fmax(x, y); } #if defined(DUK_USE_ES6) DUK_LOCAL double duk__cbrt(double x) { /* cbrt() is C99. To avoid hassling embedders with the need to provide a * cube root function, we can get by with pow(). The result is not * identical, but that's OK: ES2015 says it's implementation-dependent. */ #if defined(DUK_CBRT) /* cbrt() matches ES2015 requirements. */ return DUK_CBRT(x); #else duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x); /* pow() does not, however. */ if (c == DUK_FP_NAN || c == DUK_FP_INFINITE || c == DUK_FP_ZERO) { return x; } if (DUK_SIGNBIT(x)) { return -DUK_POW(-x, 1.0 / 3.0); } else { return DUK_POW(x, 1.0 / 3.0); } #endif } DUK_LOCAL double duk__log2(double x) { #if defined(DUK_LOG2) return DUK_LOG2(x); #else return DUK_LOG(x) * DUK_DOUBLE_LOG2E; #endif } DUK_LOCAL double duk__log10(double x) { #if defined(DUK_LOG10) return DUK_LOG10(x); #else return DUK_LOG(x) * DUK_DOUBLE_LOG10E; #endif } DUK_LOCAL double duk__trunc(double x) { #if defined(DUK_TRUNC) return DUK_TRUNC(x); #else /* Handles -0 correctly: -0.0 matches 'x >= 0.0' but floor() * is required to return -0 when the argument is -0. */ return x >= 0.0 ? DUK_FLOOR(x) : DUK_CEIL(x); #endif } #endif /* DUK_USE_ES6 */ DUK_LOCAL double duk__round_fixed(double x) { /* Numbers half-way between integers must be rounded towards +Infinity, * e.g. -3.5 must be rounded to -3 (not -4). When rounded to zero, zero * sign must be set appropriately. E5.1 Section 15.8.2.15. * * Note that ANSI C round() is "round to nearest integer, away from zero", * which is incorrect for negative values. Here we make do with floor(). */ duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x); if (c == DUK_FP_NAN || c == DUK_FP_INFINITE || c == DUK_FP_ZERO) { return x; } /* * x is finite and non-zero * * -1.6 -> floor(-1.1) -> -2 * -1.5 -> floor(-1.0) -> -1 (towards +Inf) * -1.4 -> floor(-0.9) -> -1 * -0.5 -> -0.0 (special case) * -0.1 -> -0.0 (special case) * +0.1 -> +0.0 (special case) * +0.5 -> floor(+1.0) -> 1 (towards +Inf) * +1.4 -> floor(+1.9) -> 1 * +1.5 -> floor(+2.0) -> 2 (towards +Inf) * +1.6 -> floor(+2.1) -> 2 */ if (x >= -0.5 && x < 0.5) { /* +0.5 is handled by floor, this is on purpose */ if (x < 0.0) { return -0.0; } else { return +0.0; } } return DUK_FLOOR(x + 0.5); } /* Wrappers for calling standard math library methods. These may be required * on platforms where one or more of the math built-ins are defined as macros * or inline functions and are thus not suitable to be used as function pointers. */ #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS) DUK_LOCAL double duk__fabs(double x) { return DUK_FABS(x); } DUK_LOCAL double duk__acos(double x) { return DUK_ACOS(x); } DUK_LOCAL double duk__asin(double x) { return DUK_ASIN(x); } DUK_LOCAL double duk__atan(double x) { return DUK_ATAN(x); } DUK_LOCAL double duk__ceil(double x) { return DUK_CEIL(x); } DUK_LOCAL double duk__cos(double x) { return DUK_COS(x); } DUK_LOCAL double duk__exp(double x) { return DUK_EXP(x); } DUK_LOCAL double duk__floor(double x) { return DUK_FLOOR(x); } DUK_LOCAL double duk__log(double x) { return DUK_LOG(x); } DUK_LOCAL double duk__sin(double x) { return DUK_SIN(x); } DUK_LOCAL double duk__sqrt(double x) { return DUK_SQRT(x); } DUK_LOCAL double duk__tan(double x) { return DUK_TAN(x); } DUK_LOCAL double duk__atan2_fixed(double x, double y) { #if defined(DUK_USE_ATAN2_WORKAROUNDS) /* Specific fixes to common atan2() implementation issues: * - test-bug-mingw-math-issues.js */ if (DUK_ISINF(x) && DUK_ISINF(y)) { if (DUK_SIGNBIT(x)) { if (DUK_SIGNBIT(y)) { return -2.356194490192345; } else { return -0.7853981633974483; } } else { if (DUK_SIGNBIT(y)) { return 2.356194490192345; } else { return 0.7853981633974483; } } } #else /* Some ISO C assumptions. */ DUK_ASSERT(duk_double_equals(DUK_ATAN2(DUK_DOUBLE_INFINITY, DUK_DOUBLE_INFINITY), 0.7853981633974483)); DUK_ASSERT(duk_double_equals(DUK_ATAN2(-DUK_DOUBLE_INFINITY, DUK_DOUBLE_INFINITY), -0.7853981633974483)); DUK_ASSERT(duk_double_equals(DUK_ATAN2(DUK_DOUBLE_INFINITY, -DUK_DOUBLE_INFINITY), 2.356194490192345)); DUK_ASSERT(duk_double_equals(DUK_ATAN2(-DUK_DOUBLE_INFINITY, -DUK_DOUBLE_INFINITY), -2.356194490192345)); #endif return DUK_ATAN2(x, y); } #endif /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */ /* order must match constants in genbuiltins.py */ DUK_LOCAL const duk__one_arg_func duk__one_arg_funcs[] = { #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS) duk__fabs, duk__acos, duk__asin, duk__atan, duk__ceil, duk__cos, duk__exp, duk__floor, duk__log, duk__round_fixed, duk__sin, duk__sqrt, duk__tan, #if defined(DUK_USE_ES6) duk__cbrt, duk__log2, duk__log10, duk__trunc #endif #else /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */ DUK_FABS, DUK_ACOS, DUK_ASIN, DUK_ATAN, DUK_CEIL, DUK_COS, DUK_EXP, DUK_FLOOR, DUK_LOG, duk__round_fixed, DUK_SIN, DUK_SQRT, DUK_TAN, #if defined(DUK_USE_ES6) duk__cbrt, duk__log2, duk__log10, duk__trunc #endif #endif /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */ }; /* order must match constants in genbuiltins.py */ DUK_LOCAL const duk__two_arg_func duk__two_arg_funcs[] = { #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS) duk__atan2_fixed, duk_js_arith_pow #else duk__atan2_fixed, duk_js_arith_pow #endif }; DUK_INTERNAL duk_ret_t duk_bi_math_object_onearg_shared(duk_hthread *thr) { duk_small_int_t fun_idx = duk_get_current_magic(thr); duk__one_arg_func fun; duk_double_t arg1; DUK_ASSERT(fun_idx >= 0); DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__one_arg_funcs) / sizeof(duk__one_arg_func))); arg1 = duk_to_number(thr, 0); fun = duk__one_arg_funcs[fun_idx]; duk_push_number(thr, (duk_double_t) fun((double) arg1)); return 1; } DUK_INTERNAL duk_ret_t duk_bi_math_object_twoarg_shared(duk_hthread *thr) { duk_small_int_t fun_idx = duk_get_current_magic(thr); duk__two_arg_func fun; duk_double_t arg1; duk_double_t arg2; DUK_ASSERT(fun_idx >= 0); DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__two_arg_funcs) / sizeof(duk__two_arg_func))); arg1 = duk_to_number(thr, 0); /* explicit ordered evaluation to match coercion semantics */ arg2 = duk_to_number(thr, 1); fun = duk__two_arg_funcs[fun_idx]; duk_push_number(thr, (duk_double_t) fun((double) arg1, (double) arg2)); return 1; } DUK_INTERNAL duk_ret_t duk_bi_math_object_max(duk_hthread *thr) { return duk__math_minmax(thr, -DUK_DOUBLE_INFINITY, duk__fmax_fixed); } DUK_INTERNAL duk_ret_t duk_bi_math_object_min(duk_hthread *thr) { return duk__math_minmax(thr, DUK_DOUBLE_INFINITY, duk__fmin_fixed); } DUK_INTERNAL duk_ret_t duk_bi_math_object_random(duk_hthread *thr) { duk_push_number(thr, (duk_double_t) DUK_UTIL_GET_RANDOM_DOUBLE(thr)); return 1; } #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_hypot(duk_hthread *thr) { /* * E6 Section 20.2.2.18: Math.hypot * * - If no arguments are passed, the result is +0. * - If any argument is +inf, the result is +inf. * - If any argument is -inf, the result is +inf. * - If no argument is +inf or -inf, and any argument is NaN, the result is * NaN. * - If all arguments are either +0 or -0, the result is +0. */ duk_idx_t nargs; duk_idx_t i; duk_bool_t found_nan; duk_double_t max; duk_double_t sum, summand; duk_double_t comp, prelim; duk_double_t t; nargs = duk_get_top(thr); /* Find the highest value. Also ToNumber() coerces. */ max = 0.0; found_nan = 0; for (i = 0; i < nargs; i++) { t = DUK_FABS(duk_to_number(thr, i)); if (DUK_FPCLASSIFY(t) == DUK_FP_NAN) { found_nan = 1; } else { max = duk_double_fmax(max, t); } } /* Early return cases. */ if (duk_double_equals(max, DUK_DOUBLE_INFINITY)) { duk_push_number(thr, DUK_DOUBLE_INFINITY); return 1; } else if (found_nan) { duk_push_number(thr, DUK_DOUBLE_NAN); return 1; } else if (duk_double_equals(max, 0.0)) { duk_push_number(thr, 0.0); /* Otherwise we'd divide by zero. */ return 1; } /* Use Kahan summation and normalize to the highest value to minimize * floating point rounding error and avoid overflow. * * https://en.wikipedia.org/wiki/Kahan_summation_algorithm */ sum = 0.0; comp = 0.0; for (i = 0; i < nargs; i++) { t = DUK_FABS(duk_get_number(thr, i)) / max; summand = (t * t) - comp; prelim = sum + summand; comp = (prelim - sum) - summand; sum = prelim; } duk_push_number(thr, (duk_double_t) DUK_SQRT(sum) * max); return 1; } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_sign(duk_hthread *thr) { duk_double_t d; d = duk_to_number(thr, 0); if (duk_double_is_nan(d)) { DUK_ASSERT(duk_is_nan(thr, -1)); return 1; /* NaN input -> return NaN */ } if (duk_double_equals(d, 0.0)) { /* Zero sign kept, i.e. -0 -> -0, +0 -> +0. */ return 1; } duk_push_int(thr, (d > 0.0 ? 1 : -1)); return 1; } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_clz32(duk_hthread *thr) { duk_uint32_t x; duk_small_uint_t i; #if defined(DUK_USE_PREFER_SIZE) duk_uint32_t mask; x = duk_to_uint32(thr, 0); for (i = 0, mask = 0x80000000UL; mask != 0; mask >>= 1) { if (x & mask) { break; } i++; } DUK_ASSERT(i <= 32); duk_push_uint(thr, i); return 1; #else /* DUK_USE_PREFER_SIZE */ i = 0; x = duk_to_uint32(thr, 0); if (x & 0xffff0000UL) { x >>= 16; } else { i += 16; } if (x & 0x0000ff00UL) { x >>= 8; } else { i += 8; } if (x & 0x000000f0UL) { x >>= 4; } else { i += 4; } if (x & 0x0000000cUL) { x >>= 2; } else { i += 2; } if (x & 0x00000002UL) { x >>= 1; } else { i += 1; } if (x & 0x00000001UL) { ; } else { i += 1; } DUK_ASSERT(i <= 32); duk_push_uint(thr, i); return 1; #endif /* DUK_USE_PREFER_SIZE */ } #endif /* DUK_USE_ES6 */ #if defined(DUK_USE_ES6) DUK_INTERNAL duk_ret_t duk_bi_math_object_imul(duk_hthread *thr) { duk_uint32_t x, y, z; x = duk_to_uint32(thr, 0); y = duk_to_uint32(thr, 1); z = x * y; /* While arguments are ToUint32() coerced and the multiplication * is unsigned as such, the final result is curiously interpreted * as a signed 32-bit value. */ duk_push_i32(thr, (duk_int32_t) z); return 1; } #endif /* DUK_USE_ES6 */ #endif /* DUK_USE_MATH_BUILTIN */