blob: 62c93414b04cbc0b432edb62d84881c8add5925b [file] [log] [blame]
/*
* 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 */