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