xref: /PHP-8.0/ext/standard/math.c (revision c37a1cd6)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | http://www.php.net/license/3_01.txt                                  |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Jim Winstead <jimw@php.net>                                 |
14    |          Stig Sæther Bakken <ssb@php.net>                            |
15    |          Zeev Suraski <zeev@php.net>                                 |
16    | PHP 4.0 patches by Thies C. Arntzen <thies@thieso.net>               |
17    +----------------------------------------------------------------------+
18 */
19 
20 #include "php.h"
21 #include "php_math.h"
22 #include "zend_multiply.h"
23 #include "zend_exceptions.h"
24 #include "zend_portability.h"
25 #include "zend_bitset.h"
26 
27 #include <math.h>
28 #include <float.h>
29 #include <stdlib.h>
30 
31 #include "basic_functions.h"
32 
33 /* {{{ php_intlog10abs
34    Returns floor(log10(fabs(val))), uses fast binary search */
php_intlog10abs(double value)35 static inline int php_intlog10abs(double value) {
36 	int result;
37 	value = fabs(value);
38 
39 	if (value < 1e-8 || value > 1e22) {
40 		result = (int)floor(log10(value));
41 	} else {
42 		static const double values[] = {
43 			1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1,
44 			1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
45 			1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
46 			1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
47 		/* Do a binary search with 5 steps */
48 		result = 15;
49 		if (value < values[result]) {
50 			result -= 8;
51 		} else {
52 			result += 8;
53 		}
54 		if (value < values[result]) {
55 			result -= 4;
56 		} else {
57 			result += 4;
58 		}
59 		if (value < values[result]) {
60 			result -= 2;
61 		} else {
62 			result += 2;
63 		}
64 		if (value < values[result]) {
65 			result -= 1;
66 		} else {
67 			result += 1;
68 		}
69 		if (value < values[result]) {
70 			result -= 1;
71 		}
72 		result -= 8;
73 	}
74 	return result;
75 }
76 /* }}} */
77 
78 /* {{{ php_intpow10
79        Returns pow(10.0, (double)power), uses fast lookup table for exact powers */
php_intpow10(int power)80 static inline double php_intpow10(int power) {
81 	static const double powers[] = {
82 		1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
83 		1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
84 		1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
85 
86 	/* Not in lookup table */
87 	if (power < 0 || power > 22) {
88 		return pow(10.0, (double)power);
89 	}
90 	return powers[power];
91 }
92 /* }}} */
93 
94 /* {{{ php_round_helper
95        Actually performs the rounding of a value to integer in a certain mode */
php_round_helper(double value,int mode)96 static inline double php_round_helper(double value, int mode) {
97 	double tmp_value;
98 
99 	if (value >= 0.0) {
100 		tmp_value = floor(value + 0.5);
101 		if ((mode == PHP_ROUND_HALF_DOWN && value == (-0.5 + tmp_value)) ||
102 			(mode == PHP_ROUND_HALF_EVEN && value == (0.5 + 2 * floor(tmp_value/2.0))) ||
103 			(mode == PHP_ROUND_HALF_ODD  && value == (0.5 + 2 * floor(tmp_value/2.0) - 1.0)))
104 		{
105 			tmp_value = tmp_value - 1.0;
106 		}
107 	} else {
108 		tmp_value = ceil(value - 0.5);
109 		if ((mode == PHP_ROUND_HALF_DOWN && value == (0.5 + tmp_value)) ||
110 			(mode == PHP_ROUND_HALF_EVEN && value == (-0.5 + 2 * ceil(tmp_value/2.0))) ||
111 			(mode == PHP_ROUND_HALF_ODD  && value == (-0.5 + 2 * ceil(tmp_value/2.0) + 1.0)))
112 		{
113 			tmp_value = tmp_value + 1.0;
114 		}
115 	}
116 
117 	return tmp_value;
118 }
119 /* }}} */
120 
121 /* {{{ _php_math_round */
122 /*
123  * Rounds a number to a certain number of decimal places in a certain rounding
124  * mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding
125  */
_php_math_round(double value,int places,int mode)126 PHPAPI double _php_math_round(double value, int places, int mode) {
127 	double f1, f2;
128 	double tmp_value;
129 	int precision_places;
130 
131 	if (!zend_finite(value) || value == 0.0) {
132 		return value;
133 	}
134 
135 	places = places < INT_MIN+1 ? INT_MIN+1 : places;
136 	precision_places = 14 - php_intlog10abs(value);
137 
138 	f1 = php_intpow10(abs(places));
139 
140 	/* If the decimal precision guaranteed by FP arithmetic is higher than
141 	   the requested places BUT is small enough to make sure a non-zero value
142 	   is returned, pre-round the result to the precision */
143 	if (precision_places > places && precision_places - 15 < places) {
144 		int64_t use_precision = precision_places < INT_MIN+1 ? INT_MIN+1 : precision_places;
145 
146 		f2 = php_intpow10(abs((int)use_precision));
147 		if (use_precision >= 0) {
148 			tmp_value = value * f2;
149 		} else {
150 			tmp_value = value / f2;
151 		}
152 		/* preround the result (tmp_value will always be something * 1e14,
153 		   thus never larger than 1e15 here) */
154 		tmp_value = php_round_helper(tmp_value, mode);
155 
156 		use_precision = places - precision_places;
157 		use_precision = use_precision < INT_MIN+1 ? INT_MIN+1 : use_precision;
158 		/* now correctly move the decimal point */
159 		f2 = php_intpow10(abs((int)use_precision));
160 		/* because places < precision_places */
161 		tmp_value = tmp_value / f2;
162 	} else {
163 		/* adjust the value */
164 		if (places >= 0) {
165 			tmp_value = value * f1;
166 		} else {
167 			tmp_value = value / f1;
168 		}
169 		/* This value is beyond our precision, so rounding it is pointless */
170 		if (fabs(tmp_value) >= 1e15) {
171 			return value;
172 		}
173 	}
174 
175 	/* round the temp value */
176 	tmp_value = php_round_helper(tmp_value, mode);
177 
178 	/* see if it makes sense to use simple division to round the value */
179 	if (abs(places) < 23) {
180 		if (places > 0) {
181 			tmp_value = tmp_value / f1;
182 		} else {
183 			tmp_value = tmp_value * f1;
184 		}
185 	} else {
186 		/* Simple division can't be used since that will cause wrong results.
187 		   Instead, the number is converted to a string and back again using
188 		   strtod(). strtod() will return the nearest possible FP value for
189 		   that string. */
190 
191 		/* 40 Bytes should be more than enough for this format string. The
192 		   float won't be larger than 1e15 anyway. But just in case, use
193 		   snprintf() and make sure the buffer is zero-terminated */
194 		char buf[40];
195 		snprintf(buf, 39, "%15fe%d", tmp_value, -places);
196 		buf[39] = '\0';
197 		tmp_value = zend_strtod(buf, NULL);
198 		/* couldn't convert to string and back */
199 		if (!zend_finite(tmp_value) || zend_isnan(tmp_value)) {
200 			tmp_value = value;
201 		}
202 	}
203 
204 	return tmp_value;
205 }
206 /* }}} */
207 
208 /* {{{ Return the absolute value of the number */
PHP_FUNCTION(abs)209 PHP_FUNCTION(abs)
210 {
211 	zval *value;
212 
213 	ZEND_PARSE_PARAMETERS_START(1, 1)
214 		Z_PARAM_NUMBER(value)
215 	ZEND_PARSE_PARAMETERS_END();
216 
217 	if (Z_TYPE_P(value) == IS_DOUBLE) {
218 		RETURN_DOUBLE(fabs(Z_DVAL_P(value)));
219 	} else if (Z_TYPE_P(value) == IS_LONG) {
220 		if (Z_LVAL_P(value) == ZEND_LONG_MIN) {
221 			RETURN_DOUBLE(-(double)ZEND_LONG_MIN);
222 		} else {
223 			RETURN_LONG(Z_LVAL_P(value) < 0 ? -Z_LVAL_P(value) : Z_LVAL_P(value));
224 		}
225 	} else {
226 		ZEND_ASSERT(0 && "Unexpected type");
227 	}
228 }
229 /* }}} */
230 
231 /* {{{ Returns the next highest integer value of the number */
PHP_FUNCTION(ceil)232 PHP_FUNCTION(ceil)
233 {
234 	zval *value;
235 
236 	ZEND_PARSE_PARAMETERS_START(1, 1)
237 		Z_PARAM_NUMBER(value)
238 	ZEND_PARSE_PARAMETERS_END();
239 
240 	if (Z_TYPE_P(value) == IS_DOUBLE) {
241 		RETURN_DOUBLE(ceil(Z_DVAL_P(value)));
242 	} else if (Z_TYPE_P(value) == IS_LONG) {
243 		RETURN_DOUBLE(zval_get_double(value));
244 	} else {
245 		ZEND_ASSERT(0 && "Unexpected type");
246 	}
247 }
248 /* }}} */
249 
250 /* {{{ Returns the next lowest integer value from the number */
PHP_FUNCTION(floor)251 PHP_FUNCTION(floor)
252 {
253 	zval *value;
254 
255 	ZEND_PARSE_PARAMETERS_START(1, 1)
256 		Z_PARAM_NUMBER(value)
257 	ZEND_PARSE_PARAMETERS_END();
258 
259 	if (Z_TYPE_P(value) == IS_DOUBLE) {
260 		RETURN_DOUBLE(floor(Z_DVAL_P(value)));
261 	} else if (Z_TYPE_P(value) == IS_LONG) {
262 		RETURN_DOUBLE(zval_get_double(value));
263 	} else {
264 		ZEND_ASSERT(0 && "Unexpected type");
265 	}
266 }
267 /* }}} */
268 
269 /* {{{ Returns the number rounded to specified precision */
PHP_FUNCTION(round)270 PHP_FUNCTION(round)
271 {
272 	zval *value;
273 	int places = 0;
274 	zend_long precision = 0;
275 	zend_long mode = PHP_ROUND_HALF_UP;
276 	double return_val;
277 
278 	ZEND_PARSE_PARAMETERS_START(1, 3)
279 		Z_PARAM_NUMBER(value)
280 		Z_PARAM_OPTIONAL
281 		Z_PARAM_LONG(precision)
282 		Z_PARAM_LONG(mode)
283 	ZEND_PARSE_PARAMETERS_END();
284 
285 	if (ZEND_NUM_ARGS() >= 2) {
286 #if SIZEOF_ZEND_LONG > SIZEOF_INT
287 		if (precision >= 0) {
288 			places = precision > INT_MAX ? INT_MAX : (int)precision;
289 		} else {
290 			places = precision <= INT_MIN ? INT_MIN+1 : (int)precision;
291 		}
292 #else
293 		places = precision;
294 #endif
295 	}
296 
297 	switch (Z_TYPE_P(value)) {
298 		case IS_LONG:
299 			/* Simple case - long that doesn't need to be rounded. */
300 			if (places >= 0) {
301 				RETURN_DOUBLE((double) Z_LVAL_P(value));
302 			}
303 			/* break omitted intentionally */
304 
305 		case IS_DOUBLE:
306 			return_val = (Z_TYPE_P(value) == IS_LONG) ? (double)Z_LVAL_P(value) : Z_DVAL_P(value);
307 			return_val = _php_math_round(return_val, (int)places, (int)mode);
308 			RETURN_DOUBLE(return_val);
309 			break;
310 
311 		EMPTY_SWITCH_DEFAULT_CASE()
312 	}
313 }
314 /* }}} */
315 
316 /* {{{ Returns the sine of the number in radians */
PHP_FUNCTION(sin)317 PHP_FUNCTION(sin)
318 {
319 	double num;
320 
321 	ZEND_PARSE_PARAMETERS_START(1, 1)
322 		Z_PARAM_DOUBLE(num)
323 	ZEND_PARSE_PARAMETERS_END();
324 	RETURN_DOUBLE(sin(num));
325 }
326 /* }}} */
327 
328 /* {{{ Returns the cosine of the number in radians */
PHP_FUNCTION(cos)329 PHP_FUNCTION(cos)
330 {
331 	double num;
332 
333 	ZEND_PARSE_PARAMETERS_START(1, 1)
334 		Z_PARAM_DOUBLE(num)
335 	ZEND_PARSE_PARAMETERS_END();
336 	RETURN_DOUBLE(cos(num));
337 }
338 /* }}} */
339 
340 /* {{{ Returns the tangent of the number in radians */
PHP_FUNCTION(tan)341 PHP_FUNCTION(tan)
342 {
343 	double num;
344 
345 	ZEND_PARSE_PARAMETERS_START(1, 1)
346 		Z_PARAM_DOUBLE(num)
347 	ZEND_PARSE_PARAMETERS_END();
348 	RETURN_DOUBLE(tan(num));
349 }
350 /* }}} */
351 
352 /* {{{ Returns the arc sine of the number in radians */
PHP_FUNCTION(asin)353 PHP_FUNCTION(asin)
354 {
355 	double num;
356 
357 	ZEND_PARSE_PARAMETERS_START(1, 1)
358 		Z_PARAM_DOUBLE(num)
359 	ZEND_PARSE_PARAMETERS_END();
360 	RETURN_DOUBLE(asin(num));
361 }
362 /* }}} */
363 
364 /* {{{ Return the arc cosine of the number in radians */
PHP_FUNCTION(acos)365 PHP_FUNCTION(acos)
366 {
367 	double num;
368 
369 	ZEND_PARSE_PARAMETERS_START(1, 1)
370 		Z_PARAM_DOUBLE(num)
371 	ZEND_PARSE_PARAMETERS_END();
372 	RETURN_DOUBLE(acos(num));
373 }
374 /* }}} */
375 
376 /* {{{ Returns the arc tangent of the number in radians */
PHP_FUNCTION(atan)377 PHP_FUNCTION(atan)
378 {
379 	double num;
380 
381 	ZEND_PARSE_PARAMETERS_START(1, 1)
382 		Z_PARAM_DOUBLE(num)
383 	ZEND_PARSE_PARAMETERS_END();
384 	RETURN_DOUBLE(atan(num));
385 }
386 /* }}} */
387 
388 /* {{{ Returns the arc tangent of y/x, with the resulting quadrant determined by the signs of y and x */
PHP_FUNCTION(atan2)389 PHP_FUNCTION(atan2)
390 {
391 	double num1, num2;
392 
393 	ZEND_PARSE_PARAMETERS_START(2, 2)
394 		Z_PARAM_DOUBLE(num1)
395 		Z_PARAM_DOUBLE(num2)
396 	ZEND_PARSE_PARAMETERS_END();
397 	RETURN_DOUBLE(atan2(num1, num2));
398 }
399 /* }}} */
400 
401 /* {{{ Returns the hyperbolic sine of the number, defined as (exp(number) - exp(-number))/2 */
PHP_FUNCTION(sinh)402 PHP_FUNCTION(sinh)
403 {
404 	double num;
405 
406 	ZEND_PARSE_PARAMETERS_START(1, 1)
407 		Z_PARAM_DOUBLE(num)
408 	ZEND_PARSE_PARAMETERS_END();
409 	RETURN_DOUBLE(sinh(num));
410 }
411 /* }}} */
412 
413 /* {{{ Returns the hyperbolic cosine of the number, defined as (exp(number) + exp(-number))/2 */
PHP_FUNCTION(cosh)414 PHP_FUNCTION(cosh)
415 {
416 	double num;
417 
418 	ZEND_PARSE_PARAMETERS_START(1, 1)
419 		Z_PARAM_DOUBLE(num)
420 	ZEND_PARSE_PARAMETERS_END();
421 	RETURN_DOUBLE(cosh(num));
422 }
423 /* }}} */
424 
425 /* {{{ Returns the hyperbolic tangent of the number, defined as sinh(number)/cosh(number) */
PHP_FUNCTION(tanh)426 PHP_FUNCTION(tanh)
427 {
428 	double num;
429 
430 	ZEND_PARSE_PARAMETERS_START(1, 1)
431 		Z_PARAM_DOUBLE(num)
432 	ZEND_PARSE_PARAMETERS_END();
433 	RETURN_DOUBLE(tanh(num));
434 }
435 /* }}} */
436 
437 /* {{{ Returns the inverse hyperbolic sine of the number, i.e. the value whose hyperbolic sine is number */
PHP_FUNCTION(asinh)438 PHP_FUNCTION(asinh)
439 {
440 	double num;
441 
442 	ZEND_PARSE_PARAMETERS_START(1, 1)
443 		Z_PARAM_DOUBLE(num)
444 	ZEND_PARSE_PARAMETERS_END();
445 	RETURN_DOUBLE(asinh(num));
446 }
447 /* }}} */
448 
449 /* {{{ Returns the inverse hyperbolic cosine of the number, i.e. the value whose hyperbolic cosine is number */
PHP_FUNCTION(acosh)450 PHP_FUNCTION(acosh)
451 {
452 	double num;
453 
454 	ZEND_PARSE_PARAMETERS_START(1, 1)
455 		Z_PARAM_DOUBLE(num)
456 	ZEND_PARSE_PARAMETERS_END();
457 	RETURN_DOUBLE(acosh(num));
458 }
459 /* }}} */
460 
461 /* {{{ Returns the inverse hyperbolic tangent of the number, i.e. the value whose hyperbolic tangent is number */
PHP_FUNCTION(atanh)462 PHP_FUNCTION(atanh)
463 {
464 	double num;
465 
466 	ZEND_PARSE_PARAMETERS_START(1, 1)
467 		Z_PARAM_DOUBLE(num)
468 	ZEND_PARSE_PARAMETERS_END();
469 	RETURN_DOUBLE(atanh(num));
470 }
471 /* }}} */
472 
473 /* {{{ Returns an approximation of pi */
PHP_FUNCTION(pi)474 PHP_FUNCTION(pi)
475 {
476 	ZEND_PARSE_PARAMETERS_NONE();
477 
478 	RETURN_DOUBLE(M_PI);
479 }
480 /* }}} */
481 
482 /* {{{ Returns whether argument is finite */
PHP_FUNCTION(is_finite)483 PHP_FUNCTION(is_finite)
484 {
485 	double dval;
486 
487 	ZEND_PARSE_PARAMETERS_START(1, 1)
488 		Z_PARAM_DOUBLE(dval)
489 	ZEND_PARSE_PARAMETERS_END();
490 	RETURN_BOOL(zend_finite(dval));
491 }
492 /* }}} */
493 
494 /* {{{ Returns whether argument is infinite */
PHP_FUNCTION(is_infinite)495 PHP_FUNCTION(is_infinite)
496 {
497 	double dval;
498 
499 	ZEND_PARSE_PARAMETERS_START(1, 1)
500 		Z_PARAM_DOUBLE(dval)
501 	ZEND_PARSE_PARAMETERS_END();
502 	RETURN_BOOL(zend_isinf(dval));
503 }
504 /* }}} */
505 
506 /* {{{ Returns whether argument is not a number */
PHP_FUNCTION(is_nan)507 PHP_FUNCTION(is_nan)
508 {
509 	double dval;
510 
511 	ZEND_PARSE_PARAMETERS_START(1, 1)
512 		Z_PARAM_DOUBLE(dval)
513 	ZEND_PARSE_PARAMETERS_END();
514 	RETURN_BOOL(zend_isnan(dval));
515 }
516 /* }}} */
517 
518 /* {{{ Returns base raised to the power of exponent. Returns integer result when possible */
PHP_FUNCTION(pow)519 PHP_FUNCTION(pow)
520 {
521 	zval *zbase, *zexp;
522 
523 	ZEND_PARSE_PARAMETERS_START(2, 2)
524 		Z_PARAM_ZVAL(zbase)
525 		Z_PARAM_ZVAL(zexp)
526 	ZEND_PARSE_PARAMETERS_END();
527 
528 	pow_function(return_value, zbase, zexp);
529 }
530 /* }}} */
531 
532 /* {{{ Returns e raised to the power of the number */
PHP_FUNCTION(exp)533 PHP_FUNCTION(exp)
534 {
535 	double num;
536 
537 	ZEND_PARSE_PARAMETERS_START(1, 1)
538 		Z_PARAM_DOUBLE(num)
539 	ZEND_PARSE_PARAMETERS_END();
540 
541 	RETURN_DOUBLE(exp(num));
542 }
543 /* }}} */
544 
545 /* {{{ Returns exp(number) - 1, computed in a way that accurate even when the value of number is close to zero */
PHP_FUNCTION(expm1)546 PHP_FUNCTION(expm1)
547 {
548 	double num;
549 
550 	ZEND_PARSE_PARAMETERS_START(1, 1)
551 		Z_PARAM_DOUBLE(num)
552 	ZEND_PARSE_PARAMETERS_END();
553 
554 	RETURN_DOUBLE(expm1(num));
555 }
556 /* }}} */
557 
558 /* {{{ Returns log(1 + number), computed in a way that accurate even when the value of number is close to zero */
PHP_FUNCTION(log1p)559 PHP_FUNCTION(log1p)
560 {
561 	double num;
562 
563 	ZEND_PARSE_PARAMETERS_START(1, 1)
564 		Z_PARAM_DOUBLE(num)
565 	ZEND_PARSE_PARAMETERS_END();
566 
567 	RETURN_DOUBLE(log1p(num));
568 }
569 /* }}} */
570 
571 /* {{{ Returns the natural logarithm of the number, or the base log if base is specified */
PHP_FUNCTION(log)572 PHP_FUNCTION(log)
573 {
574 	double num, base = 0;
575 
576 	ZEND_PARSE_PARAMETERS_START(1, 2)
577 		Z_PARAM_DOUBLE(num)
578 		Z_PARAM_OPTIONAL
579 		Z_PARAM_DOUBLE(base)
580 	ZEND_PARSE_PARAMETERS_END();
581 
582 	if (ZEND_NUM_ARGS() == 1) {
583 		RETURN_DOUBLE(log(num));
584 	}
585 
586 	if (base == 2.0) {
587 		RETURN_DOUBLE(log2(num));
588 	}
589 
590 	if (base == 10.0) {
591 		RETURN_DOUBLE(log10(num));
592 	}
593 
594 	if (base == 1.0) {
595 		RETURN_DOUBLE(ZEND_NAN);
596 	}
597 
598 	if (base <= 0.0) {
599 		zend_argument_value_error(2, "must be greater than 0");
600 		RETURN_THROWS();
601 	}
602 
603 	RETURN_DOUBLE(log(num) / log(base));
604 }
605 /* }}} */
606 
607 /* {{{ Returns the base-10 logarithm of the number */
PHP_FUNCTION(log10)608 PHP_FUNCTION(log10)
609 {
610 	double num;
611 
612 	ZEND_PARSE_PARAMETERS_START(1, 1)
613 		Z_PARAM_DOUBLE(num)
614 	ZEND_PARSE_PARAMETERS_END();
615 
616 	RETURN_DOUBLE(log10(num));
617 }
618 /* }}} */
619 
620 /* {{{ Returns the square root of the number */
PHP_FUNCTION(sqrt)621 PHP_FUNCTION(sqrt)
622 {
623 	double num;
624 
625 	ZEND_PARSE_PARAMETERS_START(1, 1)
626 		Z_PARAM_DOUBLE(num)
627 	ZEND_PARSE_PARAMETERS_END();
628 
629 	RETURN_DOUBLE(sqrt(num));
630 }
631 /* }}} */
632 
633 /* {{{ Returns sqrt(num1*num1 + num2*num2) */
PHP_FUNCTION(hypot)634 PHP_FUNCTION(hypot)
635 {
636 	double num1, num2;
637 
638 	ZEND_PARSE_PARAMETERS_START(2, 2)
639 		Z_PARAM_DOUBLE(num1)
640 		Z_PARAM_DOUBLE(num2)
641 	ZEND_PARSE_PARAMETERS_END();
642 
643 	RETURN_DOUBLE(hypot(num1, num2));
644 }
645 /* }}} */
646 
647 /* {{{ Converts the number in degrees to the radian equivalent */
PHP_FUNCTION(deg2rad)648 PHP_FUNCTION(deg2rad)
649 {
650 	double deg;
651 
652 	ZEND_PARSE_PARAMETERS_START(1, 1)
653 		Z_PARAM_DOUBLE(deg)
654 	ZEND_PARSE_PARAMETERS_END();
655 	RETURN_DOUBLE((deg / 180.0) * M_PI);
656 }
657 /* }}} */
658 
659 /* {{{ Converts the radian number to the equivalent number in degrees */
PHP_FUNCTION(rad2deg)660 PHP_FUNCTION(rad2deg)
661 {
662 	double rad;
663 
664 	ZEND_PARSE_PARAMETERS_START(1, 1)
665 		Z_PARAM_DOUBLE(rad)
666 	ZEND_PARSE_PARAMETERS_END();
667 
668 	RETURN_DOUBLE((rad / M_PI) * 180);
669 }
670 /* }}} */
671 
672 /* {{{ _php_math_basetolong */
673 /*
674  * Convert a string representation of a base(2-36) number to a long.
675  */
_php_math_basetolong(zval * arg,int base)676 PHPAPI zend_long _php_math_basetolong(zval *arg, int base)
677 {
678 	zend_long num = 0, digit, onum;
679 	zend_long i;
680 	char c, *s;
681 
682 	if (Z_TYPE_P(arg) != IS_STRING || base < 2 || base > 36) {
683 		return 0;
684 	}
685 
686 	s = Z_STRVAL_P(arg);
687 
688 	for (i = Z_STRLEN_P(arg); i > 0; i--) {
689 		c = *s++;
690 
691 		digit = (c >= '0' && c <= '9') ? c - '0'
692 			: (c >= 'A' && c <= 'Z') ? c - 'A' + 10
693 			: (c >= 'a' && c <= 'z') ? c - 'a' + 10
694 			: base;
695 
696 		if (digit >= base) {
697 			continue;
698 		}
699 
700 		onum = num;
701 		num = num * base + digit;
702 		if (num > onum)
703 			continue;
704 
705 		{
706 
707 			php_error_docref(NULL, E_WARNING, "Number %s is too big to fit in long", s);
708 			return ZEND_LONG_MAX;
709 		}
710 	}
711 
712 	return num;
713 }
714 /* }}} */
715 
716 /* {{{ _php_math_basetozval */
717 /*
718  * Convert a string representation of a base(2-36) number to a zval.
719  */
_php_math_basetozval(zend_string * str,int base,zval * ret)720 PHPAPI void _php_math_basetozval(zend_string *str, int base, zval *ret)
721 {
722 	zend_long num = 0;
723 	double fnum = 0;
724 	int mode = 0;
725 	char c, *s, *e;
726 	zend_long cutoff;
727 	int cutlim;
728 	int invalidchars = 0;
729 
730 	s = ZSTR_VAL(str);
731 	e = s + ZSTR_LEN(str);
732 
733 	/* Skip leading whitespace */
734 	while (s < e && isspace(*s)) s++;
735 	/* Skip trailing whitespace */
736 	while (s < e && isspace(*(e-1))) e--;
737 
738 	if (e - s >= 2) {
739 		if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2;
740 		if (base == 8 && s[0] == '0' && (s[1] == 'o' || s[1] == 'O')) s += 2;
741 		if (base == 2 && s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) s += 2;
742 	}
743 
744 	cutoff = ZEND_LONG_MAX / base;
745 	cutlim = ZEND_LONG_MAX % base;
746 
747 	while (s < e) {
748 		c = *s++;
749 
750 		/* might not work for EBCDIC */
751 		if (c >= '0' && c <= '9')
752 			c -= '0';
753 		else if (c >= 'A' && c <= 'Z')
754 			c -= 'A' - 10;
755 		else if (c >= 'a' && c <= 'z')
756 			c -= 'a' - 10;
757 		else {
758 			invalidchars++;
759 			continue;
760 		}
761 
762 		if (c >= base) {
763 			invalidchars++;
764 			continue;
765 		}
766 
767 		switch (mode) {
768 		case 0: /* Integer */
769 			if (num < cutoff || (num == cutoff && c <= cutlim)) {
770 				num = num * base + c;
771 				break;
772 			} else {
773 				fnum = (double)num;
774 				mode = 1;
775 			}
776 			/* fall-through */
777 		case 1: /* Float */
778 			fnum = fnum * base + c;
779 		}
780 	}
781 
782 	if (invalidchars > 0) {
783 		zend_error(E_DEPRECATED, "Invalid characters passed for attempted conversion, these have been ignored");
784 	}
785 
786 	if (mode == 1) {
787 		ZVAL_DOUBLE(ret, fnum);
788 	} else {
789 		ZVAL_LONG(ret, num);
790 	}
791 }
792 /* }}} */
793 
794 /* {{{ _php_math_longtobase */
795 /*
796  * Convert a long to a string containing a base(2-36) representation of
797  * the number.
798  */
_php_math_longtobase(zend_long arg,int base)799 PHPAPI zend_string * _php_math_longtobase(zend_long arg, int base)
800 {
801 	static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
802 	char buf[(sizeof(zend_ulong) << 3) + 1];
803 	char *ptr, *end;
804 	zend_ulong value;
805 
806 	if (base < 2 || base > 36) {
807 		return ZSTR_EMPTY_ALLOC();
808 	}
809 
810 	value = arg;
811 
812 	end = ptr = buf + sizeof(buf) - 1;
813 	*ptr = '\0';
814 
815 	do {
816 		ZEND_ASSERT(ptr > buf);
817 		*--ptr = digits[value % base];
818 		value /= base;
819 	} while (value);
820 
821 	return zend_string_init(ptr, end - ptr, 0);
822 }
823 /* }}} */
824 
825 /* {{{ _php_math_longtobase_pwr2 */
826 /*
827  * Convert a long to a string containing a base(2,4,6,16,32) representation of
828  * the number.
829  */
_php_math_longtobase_pwr2(zend_long arg,int base_log2)830 static zend_always_inline zend_string * _php_math_longtobase_pwr2(zend_long arg, int base_log2)
831 {
832 	static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
833 	zend_ulong value;
834 	size_t len;
835 	zend_string *ret;
836 	char *ptr;
837 
838 	value = arg;
839 
840 	if (value == 0) {
841 		len = 1;
842 	} else {
843 		len = ((sizeof(value) * 8 - zend_ulong_nlz(value)) + (base_log2 - 1)) / base_log2;
844 	}
845 
846 	ret = zend_string_alloc(len, 0);
847 	ptr = ZSTR_VAL(ret) + len;
848 	*ptr = '\0';
849 
850 	do {
851 		ZEND_ASSERT(ptr > ZSTR_VAL(ret));
852 		*--ptr = digits[value & ((1 << base_log2) - 1)];
853 		value >>= base_log2;
854 	} while (value);
855 
856 	return ret;
857 }
858 /* }}} */
859 
860 /* {{{ _php_math_zvaltobase */
861 /*
862  * Convert a zval to a string containing a base(2-36) representation of
863  * the number.
864  */
_php_math_zvaltobase(zval * arg,int base)865 PHPAPI zend_string * _php_math_zvaltobase(zval *arg, int base)
866 {
867 	static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
868 
869 	if ((Z_TYPE_P(arg) != IS_LONG && Z_TYPE_P(arg) != IS_DOUBLE) || base < 2 || base > 36) {
870 		return ZSTR_EMPTY_ALLOC();
871 	}
872 
873 	if (Z_TYPE_P(arg) == IS_DOUBLE) {
874 		double fvalue = floor(Z_DVAL_P(arg)); /* floor it just in case */
875 		char *ptr, *end;
876 		char buf[(sizeof(double) << 3) + 1];
877 
878 		/* Don't try to convert +/- infinity */
879 		if (fvalue == ZEND_INFINITY || fvalue == -ZEND_INFINITY) {
880 			zend_value_error("An infinite value cannot be converted to base %d", base);
881 			return NULL;
882 		}
883 
884 		end = ptr = buf + sizeof(buf) - 1;
885 		*ptr = '\0';
886 
887 		do {
888 			*--ptr = digits[(int) fmod(fvalue, base)];
889 			fvalue /= base;
890 		} while (ptr > buf && fabs(fvalue) >= 1);
891 
892 		return zend_string_init(ptr, end - ptr, 0);
893 	}
894 
895 	return _php_math_longtobase(Z_LVAL_P(arg), base);
896 }
897 /* }}} */
898 
899 /* {{{ Returns the decimal equivalent of the binary number */
PHP_FUNCTION(bindec)900 PHP_FUNCTION(bindec)
901 {
902 	zend_string *arg;
903 
904 	ZEND_PARSE_PARAMETERS_START(1, 1)
905 		Z_PARAM_STR(arg)
906 	ZEND_PARSE_PARAMETERS_END();
907 
908 	_php_math_basetozval(arg, 2, return_value);
909 }
910 /* }}} */
911 
912 /* {{{ Returns the decimal equivalent of the hexadecimal number */
PHP_FUNCTION(hexdec)913 PHP_FUNCTION(hexdec)
914 {
915 	zend_string *arg;
916 
917 	ZEND_PARSE_PARAMETERS_START(1, 1)
918 		Z_PARAM_STR(arg)
919 	ZEND_PARSE_PARAMETERS_END();
920 
921 	_php_math_basetozval(arg, 16, return_value);
922 }
923 /* }}} */
924 
925 /* {{{ Returns the decimal equivalent of an octal string */
PHP_FUNCTION(octdec)926 PHP_FUNCTION(octdec)
927 {
928 	zend_string *arg;
929 
930 	ZEND_PARSE_PARAMETERS_START(1, 1)
931 		Z_PARAM_STR(arg)
932 	ZEND_PARSE_PARAMETERS_END();
933 
934 	_php_math_basetozval(arg, 8, return_value);
935 }
936 /* }}} */
937 
938 /* {{{ Returns a string containing a binary representation of the number */
PHP_FUNCTION(decbin)939 PHP_FUNCTION(decbin)
940 {
941 	zend_long arg;
942 
943 	ZEND_PARSE_PARAMETERS_START(1, 1)
944 		Z_PARAM_LONG(arg)
945 	ZEND_PARSE_PARAMETERS_END();
946 
947 	RETURN_STR(_php_math_longtobase_pwr2(arg, 1));
948 }
949 /* }}} */
950 
951 /* {{{ Returns a string containing an octal representation of the given number */
PHP_FUNCTION(decoct)952 PHP_FUNCTION(decoct)
953 {
954 	zend_long arg;
955 
956 	ZEND_PARSE_PARAMETERS_START(1, 1)
957 		Z_PARAM_LONG(arg)
958 	ZEND_PARSE_PARAMETERS_END();
959 
960 	RETURN_STR(_php_math_longtobase_pwr2(arg, 3));
961 }
962 /* }}} */
963 
964 /* {{{ Returns a string containing a hexadecimal representation of the given number */
PHP_FUNCTION(dechex)965 PHP_FUNCTION(dechex)
966 {
967 	zend_long arg;
968 
969 	ZEND_PARSE_PARAMETERS_START(1, 1)
970 		Z_PARAM_LONG(arg)
971 	ZEND_PARSE_PARAMETERS_END();
972 
973 	RETURN_STR(_php_math_longtobase_pwr2(arg, 4));
974 }
975 /* }}} */
976 
977 /* {{{ Converts a number in a string from any base <= 36 to any base <= 36 */
PHP_FUNCTION(base_convert)978 PHP_FUNCTION(base_convert)
979 {
980 	zval temp;
981 	zend_string *number;
982 	zend_long frombase, tobase;
983 	zend_string *result;
984 
985 	ZEND_PARSE_PARAMETERS_START(3, 3)
986 		Z_PARAM_STR(number)
987 		Z_PARAM_LONG(frombase)
988 		Z_PARAM_LONG(tobase)
989 	ZEND_PARSE_PARAMETERS_END();
990 
991 	if (frombase < 2 || frombase > 36) {
992 		zend_argument_value_error(2, "must be between 2 and 36 (inclusive)");
993 		RETURN_THROWS();
994 	}
995 	if (tobase < 2 || tobase > 36) {
996 		zend_argument_value_error(3, "must be between 2 and 36 (inclusive)");
997 		RETURN_THROWS();
998 	}
999 
1000 	_php_math_basetozval(number, (int)frombase, &temp);
1001 	result = _php_math_zvaltobase(&temp, (int)tobase);
1002 	if (!result) {
1003 		RETURN_THROWS();
1004 	}
1005 
1006 	RETVAL_STR(result);
1007 }
1008 /* }}} */
1009 
1010 /* {{{ _php_math_number_format */
_php_math_number_format(double d,int dec,char dec_point,char thousand_sep)1011 PHPAPI zend_string *_php_math_number_format(double d, int dec, char dec_point, char thousand_sep)
1012 {
1013 	return _php_math_number_format_ex(d, dec, &dec_point, 1, &thousand_sep, 1);
1014 }
1015 
_php_math_number_format_ex(double d,int dec,const char * dec_point,size_t dec_point_len,const char * thousand_sep,size_t thousand_sep_len)1016 PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, const char *dec_point,
1017 		size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len)
1018 {
1019 	zend_string *res;
1020 	zend_string *tmpbuf;
1021 	char *s, *t;  /* source, target */
1022 	char *dp;
1023 	size_t integral;
1024 	size_t reslen = 0;
1025 	int count = 0;
1026 	int is_negative=0;
1027 
1028 	if (d < 0) {
1029 		is_negative = 1;
1030 		d = -d;
1031 	}
1032 
1033 	dec = MAX(0, dec);
1034 	d = _php_math_round(d, dec, PHP_ROUND_HALF_UP);
1035 	tmpbuf = strpprintf(0, "%.*F", dec, d);
1036 	if (tmpbuf == NULL) {
1037 		return NULL;
1038 	} else if (!isdigit((int)ZSTR_VAL(tmpbuf)[0])) {
1039 		return tmpbuf;
1040 	}
1041 
1042 	/* Check if the number is no longer negative after rounding */
1043 	if (is_negative && d == 0) {
1044 		is_negative = 0;
1045 	}
1046 
1047 	/* find decimal point, if expected */
1048 	if (dec) {
1049 		dp = strpbrk(ZSTR_VAL(tmpbuf), ".,");
1050 	} else {
1051 		dp = NULL;
1052 	}
1053 
1054 	/* calculate the length of the return buffer */
1055 	if (dp) {
1056 		integral = (dp - ZSTR_VAL(tmpbuf));
1057 	} else {
1058 		/* no decimal point was found */
1059 		integral = ZSTR_LEN(tmpbuf);
1060 	}
1061 
1062 	/* allow for thousand separators */
1063 	if (thousand_sep) {
1064 		integral = zend_safe_addmult((integral-1)/3, thousand_sep_len, integral, "number formatting");
1065 	}
1066 
1067 	reslen = integral;
1068 
1069 	if (dec) {
1070 		reslen += dec;
1071 
1072 		if (dec_point) {
1073 			reslen = zend_safe_addmult(reslen, 1, dec_point_len, "number formatting");
1074 		}
1075 	}
1076 
1077 	/* add a byte for minus sign */
1078 	if (is_negative) {
1079 		reslen++;
1080 	}
1081 	res = zend_string_alloc(reslen, 0);
1082 
1083 	s = ZSTR_VAL(tmpbuf) + ZSTR_LEN(tmpbuf) - 1;
1084 	t = ZSTR_VAL(res) + reslen;
1085 	*t-- = '\0';
1086 
1087 	/* copy the decimal places.
1088 	 * Take care, as the sprintf implementation may return less places than
1089 	 * we requested due to internal buffer limitations */
1090 	if (dec) {
1091 		size_t declen = (dp ? s - dp : 0);
1092 		size_t topad = (size_t)dec > declen ? dec - declen : 0;
1093 
1094 		/* pad with '0's */
1095 		while (topad--) {
1096 			*t-- = '0';
1097 		}
1098 
1099 		if (dp) {
1100 			s -= declen + 1; /* +1 to skip the point */
1101 			t -= declen;
1102 
1103 			/* now copy the chars after the point */
1104 			memcpy(t + 1, dp + 1, declen);
1105 		}
1106 
1107 		/* add decimal point */
1108 		if (dec_point) {
1109 			t -= dec_point_len;
1110 			memcpy(t + 1, dec_point, dec_point_len);
1111 		}
1112 	}
1113 
1114 	/* copy the numbers before the decimal point, adding thousand
1115 	 * separator every three digits */
1116 	while (s >= ZSTR_VAL(tmpbuf)) {
1117 		*t-- = *s--;
1118 		if (thousand_sep && (++count%3)==0 && s >= ZSTR_VAL(tmpbuf)) {
1119 			t -= thousand_sep_len;
1120 			memcpy(t + 1, thousand_sep, thousand_sep_len);
1121 		}
1122 	}
1123 
1124 	/* and a minus sign, if needed */
1125 	if (is_negative) {
1126 		*t-- = '-';
1127 	}
1128 
1129 	ZSTR_LEN(res) = reslen;
1130 	zend_string_release_ex(tmpbuf, 0);
1131 	return res;
1132 }
1133 
1134 /* {{{ Formats a number with grouped thousands */
PHP_FUNCTION(number_format)1135 PHP_FUNCTION(number_format)
1136 {
1137 	double num;
1138 	zend_long dec = 0;
1139 	char *thousand_sep = NULL, *dec_point = NULL;
1140 	size_t thousand_sep_len = 0, dec_point_len = 0;
1141 
1142 	ZEND_PARSE_PARAMETERS_START(1, 4)
1143 		Z_PARAM_DOUBLE(num)
1144 		Z_PARAM_OPTIONAL
1145 		Z_PARAM_LONG(dec)
1146 		Z_PARAM_STRING_OR_NULL(dec_point, dec_point_len)
1147 		Z_PARAM_STRING_OR_NULL(thousand_sep, thousand_sep_len)
1148 	ZEND_PARSE_PARAMETERS_END();
1149 
1150 	if (dec_point == NULL) {
1151 		dec_point = ".";
1152 		dec_point_len = 1;
1153 	}
1154 	if (thousand_sep == NULL) {
1155 		thousand_sep = ",";
1156 		thousand_sep_len = 1;
1157 	}
1158 
1159 	RETURN_STR(_php_math_number_format_ex(num, (int)dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1160 }
1161 /* }}} */
1162 
1163 /* {{{ Returns the remainder of dividing x by y as a float */
PHP_FUNCTION(fmod)1164 PHP_FUNCTION(fmod)
1165 {
1166 	double num1, num2;
1167 
1168 	ZEND_PARSE_PARAMETERS_START(2, 2)
1169 		Z_PARAM_DOUBLE(num1)
1170 		Z_PARAM_DOUBLE(num2)
1171 	ZEND_PARSE_PARAMETERS_END();
1172 
1173 	RETURN_DOUBLE(fmod(num1, num2));
1174 }
1175 /* }}} */
1176 
1177 /* {{{ Perform floating-point division of dividend / divisor
1178    with IEEE-754 semantics for division by zero. */
1179 #ifdef __clang__
1180 __attribute__((no_sanitize("float-divide-by-zero")))
1181 #endif
PHP_FUNCTION(fdiv)1182 PHP_FUNCTION(fdiv)
1183 {
1184 	double dividend, divisor;
1185 
1186 	ZEND_PARSE_PARAMETERS_START(2, 2)
1187 		Z_PARAM_DOUBLE(dividend)
1188 		Z_PARAM_DOUBLE(divisor)
1189 	ZEND_PARSE_PARAMETERS_END();
1190 
1191 	RETURN_DOUBLE(dividend / divisor);
1192 }
1193 /* }}} */
1194 
1195 /* {{{ Returns the integer quotient of the division of dividend by divisor */
PHP_FUNCTION(intdiv)1196 PHP_FUNCTION(intdiv)
1197 {
1198 	zend_long dividend, divisor;
1199 
1200 	ZEND_PARSE_PARAMETERS_START(2, 2)
1201 		Z_PARAM_LONG(dividend)
1202 		Z_PARAM_LONG(divisor)
1203 	ZEND_PARSE_PARAMETERS_END();
1204 
1205 	if (divisor == 0) {
1206 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
1207 		RETURN_THROWS();
1208 	} else if (divisor == -1 && dividend == ZEND_LONG_MIN) {
1209 		/* Prevent overflow error/crash ... really should not happen:
1210 		   We don't return a float here as that violates function contract */
1211 		zend_throw_exception_ex(zend_ce_arithmetic_error, 0, "Division of PHP_INT_MIN by -1 is not an integer");
1212 		RETURN_THROWS();
1213 	}
1214 
1215 	RETURN_LONG(dividend / divisor);
1216 }
1217 /* }}} */
1218