/* * 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_context *ctx, duk_double_t initial, duk__two_arg_func min_max) { duk_idx_t n = duk_get_top(ctx); 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(ctx, 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(ctx, 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 (x == 0 && y == 0) { /* XXX: what's the safest way of creating a negative zero? */ if (DUK_SIGNBIT(x) != 0 || DUK_SIGNBIT(y) != 0) { return -0.0; } else { return +0.0; } } #ifdef DUK_USE_MATH_FMIN return DUK_FMIN(x, y); #else return (x < y ? x : y); #endif } 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 (x == 0 && y == 0) { if (DUK_SIGNBIT(x) == 0 || DUK_SIGNBIT(y) == 0) { return +0.0; } else { return -0.0; } } #ifdef DUK_USE_MATH_FMAX return DUK_FMAX(x, y); #else return (x > y ? x : y); #endif } 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); } DUK_LOCAL double duk__pow_fixed(double x, double y) { /* The ANSI C pow() semantics differ from Ecmascript. * * E.g. when x==1 and y is +/- infinite, the Ecmascript required * result is NaN, while at least Linux pow() returns 1. */ duk_small_int_t cx, cy, sx; DUK_UNREF(cx); DUK_UNREF(sx); cy = (duk_small_int_t) DUK_FPCLASSIFY(y); if (cy == DUK_FP_NAN) { goto ret_nan; } if (DUK_FABS(x) == 1.0 && cy == DUK_FP_INFINITE) { goto ret_nan; } #if defined(DUK_USE_POW_NETBSD_WORKAROUND) /* See test-bug-netbsd-math-pow.js: NetBSD 6.0 on x86 (at least) does not * correctly handle some cases where x=+/-0. Specific fixes to these * here. */ cx = (duk_small_int_t) DUK_FPCLASSIFY(x); if (cx == DUK_FP_ZERO && y < 0.0) { sx = (duk_small_int_t) DUK_SIGNBIT(x); if (sx == 0) { /* Math.pow(+0,y) should be Infinity when y<0. NetBSD pow() * returns -Infinity instead when y is <0 and finite. The * if-clause also catches y == -Infinity (which works even * without the fix). */ return DUK_DOUBLE_INFINITY; } else { /* Math.pow(-0,y) where y<0 should be: * - -Infinity if y<0 and an odd integer * - Infinity otherwise * NetBSD pow() returns -Infinity for all finite y<0. The * if-clause also catches y == -Infinity (which works even * without the fix). */ /* fmod() return value has same sign as input (negative) so * the result here will be in the range ]-2,0], 1 indicates * odd. If x is -Infinity, NaN is returned and the odd check * always concludes "not odd" which results in desired outcome. */ double tmp = DUK_FMOD(y, 2); if (tmp == -1.0) { return -DUK_DOUBLE_INFINITY; } else { /* Not odd, or y == -Infinity */ return DUK_DOUBLE_INFINITY; } } } #endif return DUK_POW(x, y); ret_nan: return DUK_DOUBLE_NAN; } /* 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(double x, double y) { 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 #else 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 #endif }; /* 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, duk__pow_fixed #else DUK_ATAN2, duk__pow_fixed #endif }; DUK_INTERNAL duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx) { duk_small_int_t fun_idx = duk_get_current_magic(ctx); duk__one_arg_func fun; DUK_ASSERT(fun_idx >= 0); DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__one_arg_funcs) / sizeof(duk__one_arg_func))); fun = duk__one_arg_funcs[fun_idx]; duk_push_number(ctx, (duk_double_t) fun((double) duk_to_number(ctx, 0))); return 1; } DUK_INTERNAL duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx) { duk_small_int_t fun_idx = duk_get_current_magic(ctx); duk__two_arg_func fun; DUK_ASSERT(fun_idx >= 0); DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__two_arg_funcs) / sizeof(duk__two_arg_func))); fun = duk__two_arg_funcs[fun_idx]; duk_push_number(ctx, (duk_double_t) fun((double) duk_to_number(ctx, 0), (double) duk_to_number(ctx, 1))); return 1; } DUK_INTERNAL duk_ret_t duk_bi_math_object_max(duk_context *ctx) { return duk__math_minmax(ctx, -DUK_DOUBLE_INFINITY, duk__fmax_fixed); } DUK_INTERNAL duk_ret_t duk_bi_math_object_min(duk_context *ctx) { return duk__math_minmax(ctx, DUK_DOUBLE_INFINITY, duk__fmin_fixed); } DUK_INTERNAL duk_ret_t duk_bi_math_object_random(duk_context *ctx) { duk_push_number(ctx, (duk_double_t) duk_util_tinyrandom_get_double((duk_hthread *) ctx)); return 1; } #else /* DUK_USE_MATH_BUILTIN */ /* A stubbed built-in is useful for e.g. compilation torture testing with BCC. */ DUK_INTERNAL duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx) { DUK_UNREF(ctx); return DUK_RET_UNIMPLEMENTED_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx) { DUK_UNREF(ctx); return DUK_RET_UNIMPLEMENTED_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_math_object_max(duk_context *ctx) { DUK_UNREF(ctx); return DUK_RET_UNIMPLEMENTED_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_math_object_min(duk_context *ctx) { DUK_UNREF(ctx); return DUK_RET_UNIMPLEMENTED_ERROR; } DUK_INTERNAL duk_ret_t duk_bi_math_object_random(duk_context *ctx) { DUK_UNREF(ctx); return DUK_RET_UNIMPLEMENTED_ERROR; } #endif /* DUK_USE_MATH_BUILTIN */