1 /*
2 * Math built-ins
3 */
4
5 #include "duk_internal.h"
6
7 #if defined(DUK_USE_MATH_BUILTIN)
8
9 /*
10 * Use static helpers which can work with math.h functions matching
11 * the following signatures. This is not portable if any of these math
12 * functions is actually a macro.
13 *
14 * Typing here is intentionally 'double' wherever values interact with
15 * the standard library APIs.
16 */
17
18 typedef double (*duk__one_arg_func)(double);
19 typedef double (*duk__two_arg_func)(double, double);
20
duk__math_minmax(duk_context * ctx,duk_double_t initial,duk__two_arg_func min_max)21 DUK_LOCAL duk_ret_t duk__math_minmax(duk_context *ctx, duk_double_t initial, duk__two_arg_func min_max) {
22 duk_idx_t n = duk_get_top(ctx);
23 duk_idx_t i;
24 duk_double_t res = initial;
25 duk_double_t t;
26
27 /*
28 * Note: fmax() does not match the E5 semantics. E5 requires
29 * that if -any- input to Math.max() is a NaN, the result is a
30 * NaN. fmax() will return a NaN only if -both- inputs are NaN.
31 * Same applies to fmin().
32 *
33 * Note: every input value must be coerced with ToNumber(), even
34 * if we know the result will be a NaN anyway: ToNumber() may have
35 * side effects for which even order of evaluation matters.
36 */
37
38 for (i = 0; i < n; i++) {
39 t = duk_to_number(ctx, i);
40 if (DUK_FPCLASSIFY(t) == DUK_FP_NAN || DUK_FPCLASSIFY(res) == DUK_FP_NAN) {
41 /* Note: not normalized, but duk_push_number() will normalize */
42 res = (duk_double_t) DUK_DOUBLE_NAN;
43 } else {
44 res = (duk_double_t) min_max(res, (double) t);
45 }
46 }
47
48 duk_push_number(ctx, res);
49 return 1;
50 }
51
duk__fmin_fixed(double x,double y)52 DUK_LOCAL double duk__fmin_fixed(double x, double y) {
53 /* fmin() with args -0 and +0 is not guaranteed to return
54 * -0 as Ecmascript requires.
55 */
56 if (x == 0 && y == 0) {
57 /* XXX: what's the safest way of creating a negative zero? */
58 if (DUK_SIGNBIT(x) != 0 || DUK_SIGNBIT(y) != 0) {
59 return -0.0;
60 } else {
61 return +0.0;
62 }
63 }
64 #ifdef DUK_USE_MATH_FMIN
65 return DUK_FMIN(x, y);
66 #else
67 return (x < y ? x : y);
68 #endif
69 }
70
duk__fmax_fixed(double x,double y)71 DUK_LOCAL double duk__fmax_fixed(double x, double y) {
72 /* fmax() with args -0 and +0 is not guaranteed to return
73 * +0 as Ecmascript requires.
74 */
75 if (x == 0 && y == 0) {
76 if (DUK_SIGNBIT(x) == 0 || DUK_SIGNBIT(y) == 0) {
77 return +0.0;
78 } else {
79 return -0.0;
80 }
81 }
82 #ifdef DUK_USE_MATH_FMAX
83 return DUK_FMAX(x, y);
84 #else
85 return (x > y ? x : y);
86 #endif
87 }
88
duk__round_fixed(double x)89 DUK_LOCAL double duk__round_fixed(double x) {
90 /* Numbers half-way between integers must be rounded towards +Infinity,
91 * e.g. -3.5 must be rounded to -3 (not -4). When rounded to zero, zero
92 * sign must be set appropriately. E5.1 Section 15.8.2.15.
93 *
94 * Note that ANSI C round() is "round to nearest integer, away from zero",
95 * which is incorrect for negative values. Here we make do with floor().
96 */
97
98 duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x);
99 if (c == DUK_FP_NAN || c == DUK_FP_INFINITE || c == DUK_FP_ZERO) {
100 return x;
101 }
102
103 /*
104 * x is finite and non-zero
105 *
106 * -1.6 -> floor(-1.1) -> -2
107 * -1.5 -> floor(-1.0) -> -1 (towards +Inf)
108 * -1.4 -> floor(-0.9) -> -1
109 * -0.5 -> -0.0 (special case)
110 * -0.1 -> -0.0 (special case)
111 * +0.1 -> +0.0 (special case)
112 * +0.5 -> floor(+1.0) -> 1 (towards +Inf)
113 * +1.4 -> floor(+1.9) -> 1
114 * +1.5 -> floor(+2.0) -> 2 (towards +Inf)
115 * +1.6 -> floor(+2.1) -> 2
116 */
117
118 if (x >= -0.5 && x < 0.5) {
119 /* +0.5 is handled by floor, this is on purpose */
120 if (x < 0.0) {
121 return -0.0;
122 } else {
123 return +0.0;
124 }
125 }
126
127 return DUK_FLOOR(x + 0.5);
128 }
129
duk__pow_fixed(double x,double y)130 DUK_LOCAL double duk__pow_fixed(double x, double y) {
131 /* The ANSI C pow() semantics differ from Ecmascript.
132 *
133 * E.g. when x==1 and y is +/- infinite, the Ecmascript required
134 * result is NaN, while at least Linux pow() returns 1.
135 */
136
137 duk_small_int_t cx, cy, sx;
138
139 DUK_UNREF(cx);
140 DUK_UNREF(sx);
141 cy = (duk_small_int_t) DUK_FPCLASSIFY(y);
142
143 if (cy == DUK_FP_NAN) {
144 goto ret_nan;
145 }
146 if (DUK_FABS(x) == 1.0 && cy == DUK_FP_INFINITE) {
147 goto ret_nan;
148 }
149 #if defined(DUK_USE_POW_NETBSD_WORKAROUND)
150 /* See test-bug-netbsd-math-pow.js: NetBSD 6.0 on x86 (at least) does not
151 * correctly handle some cases where x=+/-0. Specific fixes to these
152 * here.
153 */
154 cx = (duk_small_int_t) DUK_FPCLASSIFY(x);
155 if (cx == DUK_FP_ZERO && y < 0.0) {
156 sx = (duk_small_int_t) DUK_SIGNBIT(x);
157 if (sx == 0) {
158 /* Math.pow(+0,y) should be Infinity when y<0. NetBSD pow()
159 * returns -Infinity instead when y is <0 and finite. The
160 * if-clause also catches y == -Infinity (which works even
161 * without the fix).
162 */
163 return DUK_DOUBLE_INFINITY;
164 } else {
165 /* Math.pow(-0,y) where y<0 should be:
166 * - -Infinity if y<0 and an odd integer
167 * - Infinity otherwise
168 * NetBSD pow() returns -Infinity for all finite y<0. The
169 * if-clause also catches y == -Infinity (which works even
170 * without the fix).
171 */
172
173 /* fmod() return value has same sign as input (negative) so
174 * the result here will be in the range ]-2,0], 1 indicates
175 * odd. If x is -Infinity, NaN is returned and the odd check
176 * always concludes "not odd" which results in desired outcome.
177 */
178 double tmp = DUK_FMOD(y, 2);
179 if (tmp == -1.0) {
180 return -DUK_DOUBLE_INFINITY;
181 } else {
182 /* Not odd, or y == -Infinity */
183 return DUK_DOUBLE_INFINITY;
184 }
185 }
186 }
187 #endif
188 return DUK_POW(x, y);
189
190 ret_nan:
191 return DUK_DOUBLE_NAN;
192 }
193
194 /* Wrappers for calling standard math library methods. These may be required
195 * on platforms where one or more of the math built-ins are defined as macros
196 * or inline functions and are thus not suitable to be used as function pointers.
197 */
198 #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS)
duk__fabs(double x)199 DUK_LOCAL double duk__fabs(double x) {
200 return DUK_FABS(x);
201 }
duk__acos(double x)202 DUK_LOCAL double duk__acos(double x) {
203 return DUK_ACOS(x);
204 }
duk__asin(double x)205 DUK_LOCAL double duk__asin(double x) {
206 return DUK_ASIN(x);
207 }
duk__atan(double x)208 DUK_LOCAL double duk__atan(double x) {
209 return DUK_ATAN(x);
210 }
duk__ceil(double x)211 DUK_LOCAL double duk__ceil(double x) {
212 return DUK_CEIL(x);
213 }
duk__cos(double x)214 DUK_LOCAL double duk__cos(double x) {
215 return DUK_COS(x);
216 }
duk__exp(double x)217 DUK_LOCAL double duk__exp(double x) {
218 return DUK_EXP(x);
219 }
duk__floor(double x)220 DUK_LOCAL double duk__floor(double x) {
221 return DUK_FLOOR(x);
222 }
duk__log(double x)223 DUK_LOCAL double duk__log(double x) {
224 return DUK_LOG(x);
225 }
duk__sin(double x)226 DUK_LOCAL double duk__sin(double x) {
227 return DUK_SIN(x);
228 }
duk__sqrt(double x)229 DUK_LOCAL double duk__sqrt(double x) {
230 return DUK_SQRT(x);
231 }
duk__tan(double x)232 DUK_LOCAL double duk__tan(double x) {
233 return DUK_TAN(x);
234 }
duk__atan2(double x,double y)235 DUK_LOCAL double duk__atan2(double x, double y) {
236 return DUK_ATAN2(x, y);
237 }
238 #endif /* DUK_USE_AVOID_PLATFORM_FUNCPTRS */
239
240 /* order must match constants in genbuiltins.py */
241 DUK_LOCAL const duk__one_arg_func duk__one_arg_funcs[] = {
242 #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS)
243 duk__fabs,
244 duk__acos,
245 duk__asin,
246 duk__atan,
247 duk__ceil,
248 duk__cos,
249 duk__exp,
250 duk__floor,
251 duk__log,
252 duk__round_fixed,
253 duk__sin,
254 duk__sqrt,
255 duk__tan
256 #else
257 DUK_FABS,
258 DUK_ACOS,
259 DUK_ASIN,
260 DUK_ATAN,
261 DUK_CEIL,
262 DUK_COS,
263 DUK_EXP,
264 DUK_FLOOR,
265 DUK_LOG,
266 duk__round_fixed,
267 DUK_SIN,
268 DUK_SQRT,
269 DUK_TAN
270 #endif
271 };
272
273 /* order must match constants in genbuiltins.py */
274 DUK_LOCAL const duk__two_arg_func duk__two_arg_funcs[] = {
275 #if defined(DUK_USE_AVOID_PLATFORM_FUNCPTRS)
276 duk__atan2,
277 duk__pow_fixed
278 #else
279 DUK_ATAN2,
280 duk__pow_fixed
281 #endif
282 };
283
duk_bi_math_object_onearg_shared(duk_context * ctx)284 DUK_INTERNAL duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx) {
285 duk_small_int_t fun_idx = duk_get_current_magic(ctx);
286 duk__one_arg_func fun;
287
288 DUK_ASSERT(fun_idx >= 0);
289 DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__one_arg_funcs) / sizeof(duk__one_arg_func)));
290 fun = duk__one_arg_funcs[fun_idx];
291 duk_push_number(ctx, (duk_double_t) fun((double) duk_to_number(ctx, 0)));
292 return 1;
293 }
294
duk_bi_math_object_twoarg_shared(duk_context * ctx)295 DUK_INTERNAL duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx) {
296 duk_small_int_t fun_idx = duk_get_current_magic(ctx);
297 duk__two_arg_func fun;
298
299 DUK_ASSERT(fun_idx >= 0);
300 DUK_ASSERT(fun_idx < (duk_small_int_t) (sizeof(duk__two_arg_funcs) / sizeof(duk__two_arg_func)));
301 fun = duk__two_arg_funcs[fun_idx];
302 duk_push_number(ctx, (duk_double_t) fun((double) duk_to_number(ctx, 0), (double) duk_to_number(ctx, 1)));
303 return 1;
304 }
305
duk_bi_math_object_max(duk_context * ctx)306 DUK_INTERNAL duk_ret_t duk_bi_math_object_max(duk_context *ctx) {
307 return duk__math_minmax(ctx, -DUK_DOUBLE_INFINITY, duk__fmax_fixed);
308 }
309
duk_bi_math_object_min(duk_context * ctx)310 DUK_INTERNAL duk_ret_t duk_bi_math_object_min(duk_context *ctx) {
311 return duk__math_minmax(ctx, DUK_DOUBLE_INFINITY, duk__fmin_fixed);
312 }
313
duk_bi_math_object_random(duk_context * ctx)314 DUK_INTERNAL duk_ret_t duk_bi_math_object_random(duk_context *ctx) {
315 duk_push_number(ctx, (duk_double_t) duk_util_tinyrandom_get_double((duk_hthread *) ctx));
316 return 1;
317 }
318
319 #else /* DUK_USE_MATH_BUILTIN */
320
321 /* A stubbed built-in is useful for e.g. compilation torture testing with BCC. */
322
duk_bi_math_object_onearg_shared(duk_context * ctx)323 DUK_INTERNAL duk_ret_t duk_bi_math_object_onearg_shared(duk_context *ctx) {
324 DUK_UNREF(ctx);
325 return DUK_RET_UNIMPLEMENTED_ERROR;
326 }
327
duk_bi_math_object_twoarg_shared(duk_context * ctx)328 DUK_INTERNAL duk_ret_t duk_bi_math_object_twoarg_shared(duk_context *ctx) {
329 DUK_UNREF(ctx);
330 return DUK_RET_UNIMPLEMENTED_ERROR;
331 }
332
duk_bi_math_object_max(duk_context * ctx)333 DUK_INTERNAL duk_ret_t duk_bi_math_object_max(duk_context *ctx) {
334 DUK_UNREF(ctx);
335 return DUK_RET_UNIMPLEMENTED_ERROR;
336 }
337
duk_bi_math_object_min(duk_context * ctx)338 DUK_INTERNAL duk_ret_t duk_bi_math_object_min(duk_context *ctx) {
339 DUK_UNREF(ctx);
340 return DUK_RET_UNIMPLEMENTED_ERROR;
341 }
342
duk_bi_math_object_random(duk_context * ctx)343 DUK_INTERNAL duk_ret_t duk_bi_math_object_random(duk_context *ctx) {
344 DUK_UNREF(ctx);
345 return DUK_RET_UNIMPLEMENTED_ERROR;
346 }
347
348 #endif /* DUK_USE_MATH_BUILTIN */
349