xref: /PHP-8.4/ext/bcmath/bcmath.c (revision c5b258fe)
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    | https://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    | Author: Andi Gutmans <andi@php.net>                                  |
14    +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20 
21 #include "php.h"
22 
23 #ifdef HAVE_BCMATH
24 
25 #include "php_ini.h"
26 #include "zend_exceptions.h"
27 #include "zend_interfaces.h"
28 #include "bcmath_arginfo.h"
29 #include "ext/standard/info.h"
30 #include "php_bcmath.h"
31 #include "libbcmath/src/bcmath.h"
32 
33 /* Always pair SETUP with TEARDOWN, and do so in the outer scope!
34  * Should not be used when data can escape the function. */
35 #define BC_ARENA_SETUP \
36 	char bc_arena[BC_ARENA_SIZE]; \
37 	BCG(arena) = bc_arena;
38 
39 #define BC_ARENA_TEARDOWN \
40 	BCG(arena) = NULL; \
41 	BCG(arena_offset) = 0;
42 
43 ZEND_DECLARE_MODULE_GLOBALS(bcmath)
44 static PHP_GINIT_FUNCTION(bcmath);
45 static PHP_GSHUTDOWN_FUNCTION(bcmath);
46 static PHP_MINIT_FUNCTION(bcmath);
47 static PHP_MSHUTDOWN_FUNCTION(bcmath);
48 static PHP_MINFO_FUNCTION(bcmath);
49 
50 zend_module_entry bcmath_module_entry = {
51 	STANDARD_MODULE_HEADER,
52 	"bcmath",
53 	ext_functions,
54 	PHP_MINIT(bcmath),
55 	PHP_MSHUTDOWN(bcmath),
56 	NULL,
57 	NULL,
58 	PHP_MINFO(bcmath),
59 	PHP_BCMATH_VERSION,
60 	PHP_MODULE_GLOBALS(bcmath),
61 	PHP_GINIT(bcmath),
62 	PHP_GSHUTDOWN(bcmath),
63 	NULL,
64 	STANDARD_MODULE_PROPERTIES_EX
65 };
66 
67 #ifdef COMPILE_DL_BCMATH
68 #ifdef ZTS
69 ZEND_TSRMLS_CACHE_DEFINE()
70 #endif
ZEND_GET_MODULE(bcmath)71 ZEND_GET_MODULE(bcmath)
72 #endif
73 
74 ZEND_INI_MH(OnUpdateScale)
75 {
76 	int *p;
77 	zend_long tmp;
78 
79 	tmp = zend_ini_parse_quantity_warn(new_value, entry->name);
80 	if (tmp < 0 || tmp > INT_MAX) {
81 		return FAILURE;
82 	}
83 
84 	p = (int *) ZEND_INI_GET_ADDR();
85 	*p = (int) tmp;
86 
87 	return SUCCESS;
88 }
89 
90 /* {{{ PHP_INI */
91 PHP_INI_BEGIN()
92 	STD_PHP_INI_ENTRY("bcmath.scale", "0", PHP_INI_ALL, OnUpdateScale, bc_precision, zend_bcmath_globals, bcmath_globals)
PHP_INI_END()93 PHP_INI_END()
94 /* }}} */
95 
96 /* {{{ PHP_GINIT_FUNCTION */
97 static PHP_GINIT_FUNCTION(bcmath)
98 {
99 #if defined(COMPILE_DL_BCMATH) && defined(ZTS)
100 	ZEND_TSRMLS_CACHE_UPDATE();
101 #endif
102 	bcmath_globals->bc_precision = 0;
103 	bcmath_globals->arena = NULL;
104 	bcmath_globals->arena_offset = 0;
105 	bc_init_numbers();
106 }
107 /* }}} */
108 
109 /* {{{ PHP_GSHUTDOWN_FUNCTION */
PHP_GSHUTDOWN_FUNCTION(bcmath)110 static PHP_GSHUTDOWN_FUNCTION(bcmath)
111 {
112 	_bc_free_num_ex(&bcmath_globals->_zero_, 1);
113 	_bc_free_num_ex(&bcmath_globals->_one_, 1);
114 	_bc_free_num_ex(&bcmath_globals->_two_, 1);
115 	bcmath_globals->arena = NULL;
116 	bcmath_globals->arena_offset = 0;
117 }
118 /* }}} */
119 
120 static void bcmath_number_register_class(void);
121 
122 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(bcmath)123 PHP_MINIT_FUNCTION(bcmath)
124 {
125 	REGISTER_INI_ENTRIES();
126 	bcmath_number_register_class();
127 
128 	return SUCCESS;
129 }
130 /* }}} */
131 
132 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(bcmath)133 PHP_MSHUTDOWN_FUNCTION(bcmath)
134 {
135 	UNREGISTER_INI_ENTRIES();
136 
137 	return SUCCESS;
138 }
139 /* }}} */
140 
141 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(bcmath)142 PHP_MINFO_FUNCTION(bcmath)
143 {
144 	php_info_print_table_start();
145 	php_info_print_table_row(2, "BCMath support", "enabled");
146 	php_info_print_table_end();
147 	DISPLAY_INI_ENTRIES();
148 }
149 /* }}} */
150 
bcmath_check_scale(zend_long scale,uint32_t arg_num)151 static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32_t arg_num)
152 {
153 	if (UNEXPECTED(scale < 0 || scale > INT_MAX)) {
154 		zend_argument_value_error(arg_num, "must be between 0 and %d", INT_MAX);
155 		return FAILURE;
156 	}
157 	return SUCCESS;
158 }
159 
php_long2num(bc_num * num,zend_long lval)160 static void php_long2num(bc_num *num, zend_long lval)
161 {
162 	*num = bc_long2num(lval);
163 }
164 
php_str2num_ex(bc_num * num,const zend_string * str,size_t * full_scale)165 static zend_result php_str2num_ex(bc_num *num, const zend_string *str, size_t *full_scale)
166 {
167 	if (!bc_str2num(num, ZSTR_VAL(str), ZSTR_VAL(str) + ZSTR_LEN(str), 0, full_scale, true)) {
168 		return FAILURE;
169 	}
170 
171 	return SUCCESS;
172 }
173 
174 /* {{{ php_str2num
175    Convert to bc_num detecting scale */
php_str2num(bc_num * num,const zend_string * str)176 static zend_result php_str2num(bc_num *num, const zend_string *str)
177 {
178 	return php_str2num_ex(num, str, NULL);
179 }
180 /* }}} */
181 
182 /* {{{ Returns the sum of two arbitrary precision numbers */
PHP_FUNCTION(bcadd)183 PHP_FUNCTION(bcadd)
184 {
185 	zend_string *left, *right;
186 	zend_long scale_param;
187 	bool scale_param_is_null = 1;
188 	bc_num first = NULL, second = NULL, result = NULL;
189 	int scale;
190 
191 	ZEND_PARSE_PARAMETERS_START(2, 3)
192 		Z_PARAM_STR(left)
193 		Z_PARAM_STR(right)
194 		Z_PARAM_OPTIONAL
195 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
196 	ZEND_PARSE_PARAMETERS_END();
197 
198 	if (scale_param_is_null) {
199 		scale = BCG(bc_precision);
200 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
201 		RETURN_THROWS();
202 	} else {
203 		scale = (int) scale_param;
204 	}
205 
206 	BC_ARENA_SETUP;
207 
208 	if (php_str2num(&first, left) == FAILURE) {
209 		zend_argument_value_error(1, "is not well-formed");
210 		goto cleanup;
211 	}
212 
213 	if (php_str2num(&second, right) == FAILURE) {
214 		zend_argument_value_error(2, "is not well-formed");
215 		goto cleanup;
216 	}
217 
218 	result = bc_add (first, second, scale);
219 
220 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));
221 
222 	cleanup: {
223 		bc_free_num(&first);
224 		bc_free_num(&second);
225 		bc_free_num(&result);
226 		BC_ARENA_TEARDOWN;
227 	};
228 }
229 /* }}} */
230 
231 /* {{{ Returns the difference between two arbitrary precision numbers */
PHP_FUNCTION(bcsub)232 PHP_FUNCTION(bcsub)
233 {
234 	zend_string *left, *right;
235 	zend_long scale_param;
236 	bool scale_param_is_null = 1;
237 	bc_num first = NULL, second = NULL, result = NULL;
238 	int scale;
239 
240 	ZEND_PARSE_PARAMETERS_START(2, 3)
241 		Z_PARAM_STR(left)
242 		Z_PARAM_STR(right)
243 		Z_PARAM_OPTIONAL
244 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
245 	ZEND_PARSE_PARAMETERS_END();
246 
247 	if (scale_param_is_null) {
248 		scale = BCG(bc_precision);
249 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
250 		RETURN_THROWS();
251 	} else {
252 		scale = (int) scale_param;
253 	}
254 
255 	BC_ARENA_SETUP;
256 
257 	if (php_str2num(&first, left) == FAILURE) {
258 		zend_argument_value_error(1, "is not well-formed");
259 		goto cleanup;
260 	}
261 
262 	if (php_str2num(&second, right) == FAILURE) {
263 		zend_argument_value_error(2, "is not well-formed");
264 		goto cleanup;
265 	}
266 
267 	result = bc_sub (first, second, scale);
268 
269 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));
270 
271 	cleanup: {
272 		bc_free_num(&first);
273 		bc_free_num(&second);
274 		bc_free_num(&result);
275 		BC_ARENA_TEARDOWN;
276 	};
277 }
278 /* }}} */
279 
280 /* {{{ Returns the multiplication of two arbitrary precision numbers */
PHP_FUNCTION(bcmul)281 PHP_FUNCTION(bcmul)
282 {
283 	zend_string *left, *right;
284 	zend_long scale_param;
285 	bool scale_param_is_null = 1;
286 	bc_num first = NULL, second = NULL, result = NULL;
287 	int scale;
288 
289 	ZEND_PARSE_PARAMETERS_START(2, 3)
290 		Z_PARAM_STR(left)
291 		Z_PARAM_STR(right)
292 		Z_PARAM_OPTIONAL
293 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
294 	ZEND_PARSE_PARAMETERS_END();
295 
296 	if (scale_param_is_null) {
297 		scale = BCG(bc_precision);
298 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
299 		RETURN_THROWS();
300 	} else {
301 		scale = (int) scale_param;
302 	}
303 
304 	BC_ARENA_SETUP;
305 
306 	if (php_str2num(&first, left) == FAILURE) {
307 		zend_argument_value_error(1, "is not well-formed");
308 		goto cleanup;
309 	}
310 
311 	if (php_str2num(&second, right) == FAILURE) {
312 		zend_argument_value_error(2, "is not well-formed");
313 		goto cleanup;
314 	}
315 
316 	result = bc_multiply (first, second, scale);
317 
318 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));
319 
320 	cleanup: {
321 		bc_free_num(&first);
322 		bc_free_num(&second);
323 		bc_free_num(&result);
324 		BC_ARENA_TEARDOWN;
325 	};
326 }
327 /* }}} */
328 
329 /* {{{ Returns the quotient of two arbitrary precision numbers (division) */
PHP_FUNCTION(bcdiv)330 PHP_FUNCTION(bcdiv)
331 {
332 	zend_string *left, *right;
333 	zend_long scale_param;
334 	bool scale_param_is_null = 1;
335 	bc_num first = NULL, second = NULL, result;
336 	int scale = BCG(bc_precision);
337 
338 	ZEND_PARSE_PARAMETERS_START(2, 3)
339 		Z_PARAM_STR(left)
340 		Z_PARAM_STR(right)
341 		Z_PARAM_OPTIONAL
342 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
343 	ZEND_PARSE_PARAMETERS_END();
344 
345 	if (scale_param_is_null) {
346 		scale = BCG(bc_precision);
347 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
348 		RETURN_THROWS();
349 	} else {
350 		scale = (int) scale_param;
351 	}
352 
353 	BC_ARENA_SETUP;
354 
355 	bc_init_num(&result);
356 
357 	if (php_str2num(&first, left) == FAILURE) {
358 		zend_argument_value_error(1, "is not well-formed");
359 		goto cleanup;
360 	}
361 
362 	if (php_str2num(&second, right) == FAILURE) {
363 		zend_argument_value_error(2, "is not well-formed");
364 		goto cleanup;
365 	}
366 
367 	if (!bc_divide(first, second, &result, scale)) {
368 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
369 		goto cleanup;
370 	}
371 
372 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));
373 
374 	cleanup: {
375 		bc_free_num(&first);
376 		bc_free_num(&second);
377 		bc_free_num(&result);
378 		BC_ARENA_TEARDOWN;
379 	};
380 }
381 /* }}} */
382 
383 /* {{{ Returns the modulus of the two arbitrary precision operands */
PHP_FUNCTION(bcmod)384 PHP_FUNCTION(bcmod)
385 {
386 	zend_string *left, *right;
387 	zend_long scale_param;
388 	bool scale_param_is_null = 1;
389 	bc_num first = NULL, second = NULL, result;
390 	int scale = BCG(bc_precision);
391 
392 	ZEND_PARSE_PARAMETERS_START(2, 3)
393 		Z_PARAM_STR(left)
394 		Z_PARAM_STR(right)
395 		Z_PARAM_OPTIONAL
396 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
397 	ZEND_PARSE_PARAMETERS_END();
398 
399 	if (scale_param_is_null) {
400 		scale = BCG(bc_precision);
401 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
402 		RETURN_THROWS();
403 	} else {
404 		scale = (int) scale_param;
405 	}
406 
407 	BC_ARENA_SETUP;
408 
409 	bc_init_num(&result);
410 
411 	if (php_str2num(&first, left) == FAILURE) {
412 		zend_argument_value_error(1, "is not well-formed");
413 		goto cleanup;
414 	}
415 
416 	if (php_str2num(&second, right) == FAILURE) {
417 		zend_argument_value_error(2, "is not well-formed");
418 		goto cleanup;
419 	}
420 
421 	if (!bc_modulo(first, second, &result, scale)) {
422 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Modulo by zero");
423 		goto cleanup;
424 	}
425 
426 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));
427 
428 	cleanup: {
429 		bc_free_num(&first);
430 		bc_free_num(&second);
431 		bc_free_num(&result);
432 		BC_ARENA_TEARDOWN;
433 	};
434 }
435 /* }}} */
436 
PHP_FUNCTION(bcdivmod)437 PHP_FUNCTION(bcdivmod)
438 {
439 	zend_string *left, *right;
440 	zend_long scale_param;
441 	bool scale_param_is_null = 1;
442 	bc_num first = NULL, second = NULL, quot = NULL, rem = NULL;
443 	int scale = BCG(bc_precision);
444 
445 	ZEND_PARSE_PARAMETERS_START(2, 3)
446 		Z_PARAM_STR(left)
447 		Z_PARAM_STR(right)
448 		Z_PARAM_OPTIONAL
449 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
450 	ZEND_PARSE_PARAMETERS_END();
451 
452 	if (scale_param_is_null) {
453 		scale = BCG(bc_precision);
454 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
455 		RETURN_THROWS();
456 	} else {
457 		scale = (int) scale_param;
458 	}
459 
460 	BC_ARENA_SETUP;
461 
462 	if (php_str2num(&first, left) == FAILURE) {
463 		zend_argument_value_error(1, "is not well-formed");
464 		goto cleanup;
465 	}
466 
467 	if (php_str2num(&second, right) == FAILURE) {
468 		zend_argument_value_error(2, "is not well-formed");
469 		goto cleanup;
470 	}
471 
472 	if (!bc_divmod(first, second, &quot, &rem, scale)) {
473 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
474 		goto cleanup;
475 	}
476 
477 	zval z_quot, z_rem;
478 	ZVAL_STR(&z_quot, bc_num2str_ex(quot, 0));
479 	ZVAL_STR(&z_rem, bc_num2str_ex(rem, scale));
480 
481 	RETVAL_ARR(zend_new_pair(&z_quot, &z_rem));
482 
483 	cleanup: {
484 		bc_free_num(&first);
485 		bc_free_num(&second);
486 		bc_free_num(&quot);
487 		bc_free_num(&rem);
488 		BC_ARENA_TEARDOWN;
489 	};
490 }
491 
492 /* {{{ Returns the value of an arbitrary precision number raised to the power of another reduced by a modulus */
PHP_FUNCTION(bcpowmod)493 PHP_FUNCTION(bcpowmod)
494 {
495 	zend_string *base_str, *exponent_str, *modulus_str;
496 	zend_long scale_param;
497 	bool scale_param_is_null = 1;
498 	bc_num bc_base = NULL, bc_expo = NULL, bc_modulus = NULL, result;
499 	int scale = BCG(bc_precision);
500 
501 	ZEND_PARSE_PARAMETERS_START(3, 4)
502 		Z_PARAM_STR(base_str)
503 		Z_PARAM_STR(exponent_str)
504 		Z_PARAM_STR(modulus_str)
505 		Z_PARAM_OPTIONAL
506 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
507 	ZEND_PARSE_PARAMETERS_END();
508 
509 	if (scale_param_is_null) {
510 		scale = BCG(bc_precision);
511 	} else if (bcmath_check_scale(scale_param, 4) == FAILURE) {
512 		RETURN_THROWS();
513 	} else {
514 		scale = (int) scale_param;
515 	}
516 
517 	BC_ARENA_SETUP;
518 
519 	bc_init_num(&result);
520 
521 	if (php_str2num(&bc_base, base_str) == FAILURE) {
522 		zend_argument_value_error(1, "is not well-formed");
523 		goto cleanup;
524 	}
525 
526 	if (php_str2num(&bc_expo, exponent_str) == FAILURE) {
527 		zend_argument_value_error(2, "is not well-formed");
528 		goto cleanup;
529 	}
530 
531 	if (php_str2num(&bc_modulus, modulus_str) == FAILURE) {
532 		zend_argument_value_error(3, "is not well-formed");
533 		goto cleanup;
534 	}
535 
536 	raise_mod_status status = bc_raisemod(bc_base, bc_expo, bc_modulus, &result, scale);
537 	switch (status) {
538 		case BASE_HAS_FRACTIONAL:
539 			zend_argument_value_error(1, "cannot have a fractional part");
540 			goto cleanup;
541 		case EXPO_HAS_FRACTIONAL:
542 			zend_argument_value_error(2, "cannot have a fractional part");
543 			goto cleanup;
544 		case EXPO_IS_NEGATIVE:
545 			zend_argument_value_error(2, "must be greater than or equal to 0");
546 			goto cleanup;
547 		case MOD_HAS_FRACTIONAL:
548 			zend_argument_value_error(3, "cannot have a fractional part");
549 			goto cleanup;
550 		case MOD_IS_ZERO:
551 			zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Modulo by zero");
552 			goto cleanup;
553 		case OK:
554 			RETVAL_NEW_STR(bc_num2str_ex(result, scale));
555 			break;
556 		EMPTY_SWITCH_DEFAULT_CASE();
557 	}
558 
559 	cleanup: {
560 		bc_free_num(&bc_base);
561 		bc_free_num(&bc_expo);
562 		bc_free_num(&bc_modulus);
563 		bc_free_num(&result);
564 		BC_ARENA_TEARDOWN;
565 	};
566 }
567 /* }}} */
568 
569 /* {{{ Returns the value of an arbitrary precision number raised to the power of another */
PHP_FUNCTION(bcpow)570 PHP_FUNCTION(bcpow)
571 {
572 	zend_string *base_str, *exponent_str;
573 	zend_long scale_param;
574 	bool scale_param_is_null = 1;
575 	bc_num first = NULL, bc_exponent = NULL, result;
576 	int scale = BCG(bc_precision);
577 
578 	ZEND_PARSE_PARAMETERS_START(2, 3)
579 		Z_PARAM_STR(base_str)
580 		Z_PARAM_STR(exponent_str)
581 		Z_PARAM_OPTIONAL
582 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
583 	ZEND_PARSE_PARAMETERS_END();
584 
585 	if (scale_param_is_null) {
586 		scale = BCG(bc_precision);
587 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
588 		RETURN_THROWS();
589 	} else {
590 		scale = (int) scale_param;
591 	}
592 
593 	BC_ARENA_SETUP;
594 
595 	bc_init_num(&result);
596 
597 	if (php_str2num(&first, base_str) == FAILURE) {
598 		zend_argument_value_error(1, "is not well-formed");
599 		goto cleanup;
600 	}
601 
602 	if (php_str2num(&bc_exponent, exponent_str) == FAILURE) {
603 		zend_argument_value_error(2, "is not well-formed");
604 		goto cleanup;
605 	}
606 
607 	/* Check the exponent for scale digits and convert to a long. */
608 	if (bc_exponent->n_scale != 0) {
609 		zend_argument_value_error(2, "cannot have a fractional part");
610 		goto cleanup;
611 	}
612 	long exponent = bc_num2long(bc_exponent);
613 	if (exponent == 0 && (bc_exponent->n_len > 1 || bc_exponent->n_value[0] != 0)) {
614 		zend_argument_value_error(2, "is too large");
615 		goto cleanup;
616 	}
617 
618 	bc_raise(first, exponent, &result, scale);
619 
620 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));
621 
622 	cleanup: {
623 		bc_free_num(&first);
624 		bc_free_num(&bc_exponent);
625 		bc_free_num(&result);
626 		BC_ARENA_TEARDOWN;
627 	};
628 }
629 /* }}} */
630 
631 /* {{{ Returns the square root of an arbitrary precision number */
PHP_FUNCTION(bcsqrt)632 PHP_FUNCTION(bcsqrt)
633 {
634 	zend_string *left;
635 	zend_long scale_param;
636 	bool scale_param_is_null = 1;
637 	bc_num result = NULL;
638 	int scale = BCG(bc_precision);
639 
640 	ZEND_PARSE_PARAMETERS_START(1, 2)
641 		Z_PARAM_STR(left)
642 		Z_PARAM_OPTIONAL
643 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
644 	ZEND_PARSE_PARAMETERS_END();
645 
646 	if (scale_param_is_null) {
647 		scale = BCG(bc_precision);
648 	} else if (bcmath_check_scale(scale_param, 2) == FAILURE) {
649 		RETURN_THROWS();
650 	} else {
651 		scale = (int) scale_param;
652 	}
653 
654 	BC_ARENA_SETUP;
655 
656 	if (php_str2num(&result, left) == FAILURE) {
657 		zend_argument_value_error(1, "is not well-formed");
658 		goto cleanup;
659 	}
660 
661 	if (bc_sqrt (&result, scale) != 0) {
662 		RETVAL_NEW_STR(bc_num2str_ex(result, scale));
663 	} else {
664 		zend_argument_value_error(1, "must be greater than or equal to 0");
665 	}
666 
667 	cleanup: {
668 		bc_free_num(&result);
669 		BC_ARENA_TEARDOWN;
670 	};
671 }
672 /* }}} */
673 
674 /* {{{ Compares two arbitrary precision numbers */
PHP_FUNCTION(bccomp)675 PHP_FUNCTION(bccomp)
676 {
677 	zend_string *left, *right;
678 	zend_long scale_param;
679 	bool scale_param_is_null = 1;
680 	bc_num first = NULL, second = NULL;
681 	int scale = BCG(bc_precision);
682 
683 	ZEND_PARSE_PARAMETERS_START(2, 3)
684 		Z_PARAM_STR(left)
685 		Z_PARAM_STR(right)
686 		Z_PARAM_OPTIONAL
687 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
688 	ZEND_PARSE_PARAMETERS_END();
689 
690 	if (scale_param_is_null) {
691 		scale = BCG(bc_precision);
692 	} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
693 		RETURN_THROWS();
694 	} else {
695 		scale = (int) scale_param;
696 	}
697 
698 	BC_ARENA_SETUP;
699 
700 	if (!bc_str2num(&first, ZSTR_VAL(left), ZSTR_VAL(left) + ZSTR_LEN(left), scale, NULL, false)) {
701 		zend_argument_value_error(1, "is not well-formed");
702 		goto cleanup;
703 	}
704 
705 	if (!bc_str2num(&second, ZSTR_VAL(right), ZSTR_VAL(right) + ZSTR_LEN(right), scale, NULL, false)) {
706 		zend_argument_value_error(2, "is not well-formed");
707 		goto cleanup;
708 	}
709 
710 	RETVAL_LONG(bc_compare(first, second, scale));
711 
712 	cleanup: {
713 		bc_free_num(&first);
714 		bc_free_num(&second);
715 		BC_ARENA_TEARDOWN;
716 	};
717 }
718 /* }}} */
719 
720 /* {{{ floor or ceil */
bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAMETERS,bool is_floor)721 static void bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAMETERS, bool is_floor)
722 {
723 	zend_string *numstr;
724 	bc_num num = NULL, result = NULL;
725 
726 	ZEND_PARSE_PARAMETERS_START(1, 1)
727 		Z_PARAM_STR(numstr)
728 	ZEND_PARSE_PARAMETERS_END();
729 
730 	BC_ARENA_SETUP;
731 
732 	if (php_str2num(&num, numstr) == FAILURE) {
733 		zend_argument_value_error(1, "is not well-formed");
734 		goto cleanup;
735 	}
736 
737 	result = bc_floor_or_ceil(num, is_floor);
738 	RETVAL_NEW_STR(bc_num2str_ex(result, 0));
739 
740 	cleanup: {
741 		bc_free_num(&num);
742 		bc_free_num(&result);
743 		BC_ARENA_TEARDOWN;
744 	};
745 }
746 /* }}} */
747 
748 /* {{{ Returns floor of num */
PHP_FUNCTION(bcfloor)749 PHP_FUNCTION(bcfloor)
750 {
751 	bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
752 }
753 /* }}} */
754 
755 /* {{{ Returns ceil of num */
PHP_FUNCTION(bcceil)756 PHP_FUNCTION(bcceil)
757 {
758 	bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
759 }
760 /* }}} */
761 
762 /* {{{ Returns num rounded to the digits specified by precision. */
PHP_FUNCTION(bcround)763 PHP_FUNCTION(bcround)
764 {
765 	zend_string *numstr;
766 	zend_long precision = 0;
767 	zend_long mode = PHP_ROUND_HALF_UP;
768 	zend_object *mode_object = NULL;
769 	bc_num num = NULL, result;
770 
771 	ZEND_PARSE_PARAMETERS_START(1, 3)
772 		Z_PARAM_STR(numstr)
773 		Z_PARAM_OPTIONAL
774 		Z_PARAM_LONG(precision)
775 		Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce)
776 	ZEND_PARSE_PARAMETERS_END();
777 
778 	if (mode_object != NULL) {
779 		mode = php_math_round_mode_from_enum(mode_object);
780 	}
781 
782 	switch (mode) {
783 		case PHP_ROUND_HALF_UP:
784 		case PHP_ROUND_HALF_DOWN:
785 		case PHP_ROUND_HALF_EVEN:
786 		case PHP_ROUND_HALF_ODD:
787 		case PHP_ROUND_CEILING:
788 		case PHP_ROUND_FLOOR:
789 		case PHP_ROUND_TOWARD_ZERO:
790 		case PHP_ROUND_AWAY_FROM_ZERO:
791 			break;
792 		default:
793 			/* This is currently unreachable, but might become reachable when new modes are added. */
794 			zend_argument_value_error(3, "is an unsupported rounding mode");
795 			return;
796 	}
797 
798 	BC_ARENA_SETUP;
799 
800 	bc_init_num(&result);
801 
802 	if (php_str2num(&num, numstr) == FAILURE) {
803 		zend_argument_value_error(1, "is not well-formed");
804 		goto cleanup;
805 	}
806 
807 	bc_round(num, precision, mode, &result);
808 	RETVAL_NEW_STR(bc_num2str_ex(result, result->n_scale));
809 
810 	cleanup: {
811 		bc_free_num(&num);
812 		bc_free_num(&result);
813 		BC_ARENA_TEARDOWN;
814 	};
815 }
816 /* }}} */
817 
818 /* {{{ Sets default scale parameter for all bc math functions */
PHP_FUNCTION(bcscale)819 PHP_FUNCTION(bcscale)
820 {
821 	zend_long old_scale, new_scale;
822 	bool new_scale_is_null = 1;
823 
824 	ZEND_PARSE_PARAMETERS_START(0, 1)
825 		Z_PARAM_OPTIONAL
826 		Z_PARAM_LONG_OR_NULL(new_scale, new_scale_is_null)
827 	ZEND_PARSE_PARAMETERS_END();
828 
829 	old_scale = BCG(bc_precision);
830 
831 	if (!new_scale_is_null) {
832 		if (bcmath_check_scale(new_scale, 1) == FAILURE) {
833 			RETURN_THROWS();
834 		}
835 
836 		zend_string *ini_name = ZSTR_INIT_LITERAL("bcmath.scale", 0);
837 		zend_string *new_scale_str = zend_long_to_str(new_scale);
838 		zend_alter_ini_entry(ini_name, new_scale_str, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
839 		zend_string_release(new_scale_str);
840 		zend_string_release(ini_name);
841 	}
842 
843 	RETURN_LONG(old_scale);
844 }
845 /* }}} */
846 
847 
848 
849 static zend_class_entry *bcmath_number_ce;
850 static zend_object_handlers bcmath_number_obj_handlers;
851 
852 static zend_result bcmath_number_do_operation(uint8_t opcode, zval *ret_val, zval *op1, zval *op2);
853 static int bcmath_number_compare(zval *op1, zval *op2);
854 
855 #if SIZEOF_SIZE_T >= 8
856 #  define CHECK_RET_SCALE_OVERFLOW(scale, origin_scale) (scale > INT_MAX)
857 #else
858 #  define CHECK_RET_SCALE_OVERFLOW(scale, origin_scale) (scale > INT_MAX || scale < origin_scale)
859 #endif
860 #define CHECK_SCALE_OVERFLOW(scale) (scale > INT_MAX)
861 
get_bcmath_number_from_obj(const zend_object * obj)862 static zend_always_inline bcmath_number_obj_t *get_bcmath_number_from_obj(const zend_object *obj)
863 {
864 	return (bcmath_number_obj_t*)((char*)(obj) - XtOffsetOf(bcmath_number_obj_t, std));
865 }
866 
get_bcmath_number_from_zval(const zval * zv)867 static zend_always_inline bcmath_number_obj_t *get_bcmath_number_from_zval(const zval *zv)
868 {
869 	return get_bcmath_number_from_obj(Z_OBJ_P(zv));
870 }
871 
bcmath_number_value_to_str(bcmath_number_obj_t * intern)872 static zend_always_inline zend_string *bcmath_number_value_to_str(bcmath_number_obj_t *intern)
873 {
874 	if (intern->value == NULL) {
875 		intern->value = bc_num2str_ex(intern->num, intern->scale);
876 	}
877 	return intern->value;
878 }
879 
bcmath_number_create(zend_class_entry * ce)880 static zend_object *bcmath_number_create(zend_class_entry *ce)
881 {
882 	bcmath_number_obj_t *intern = zend_object_alloc(sizeof(bcmath_number_obj_t), ce);
883 
884 	zend_object_std_init(&intern->std, ce);
885 	object_properties_init(&intern->std, ce);
886 
887 	intern->scale = 1;
888 
889 	return &intern->std;
890 }
891 
bcmath_number_free(zend_object * obj)892 static void bcmath_number_free(zend_object *obj)
893 {
894 	bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
895 	if (intern->num) {
896 		bc_free_num(&intern->num);
897 		intern->num = NULL;
898 	}
899 	if (intern->value) {
900 		zend_string_release(intern->value);
901 		intern->value = NULL;
902 	}
903 	zend_object_std_dtor(&intern->std);
904 }
905 
bcmath_number_clone(zend_object * obj)906 static zend_object *bcmath_number_clone(zend_object *obj)
907 {
908 	bcmath_number_obj_t *original = get_bcmath_number_from_obj(obj);
909 	bcmath_number_obj_t *clone = get_bcmath_number_from_obj(bcmath_number_create(bcmath_number_ce));
910 
911 	clone->num = bc_copy_num(original->num);
912 	if (original->value) {
913 		clone->value = zend_string_copy(original->value);
914 	}
915 	clone->scale = original->scale;
916 
917 	return &clone->std;
918 }
919 
bcmath_number_get_properties_for(zend_object * obj,zend_prop_purpose purpose)920 static HashTable *bcmath_number_get_properties_for(zend_object *obj, zend_prop_purpose purpose)
921 {
922 	zval zv;
923 	bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
924 	HashTable *props = zend_array_dup(zend_std_get_properties(obj));
925 
926 	ZVAL_STR_COPY(&zv, bcmath_number_value_to_str(intern));
927 	zend_hash_update(props, ZSTR_KNOWN(ZEND_STR_VALUE), &zv);
928 	ZVAL_LONG(&zv, intern->scale);
929 	zend_hash_str_update(props, ZEND_STRL("scale"), &zv);
930 
931 	return props;
932 }
933 
bcmath_number_write_property(zend_object * obj,zend_string * name,zval * value,void ** cache_slot)934 static zval *bcmath_number_write_property(zend_object *obj, zend_string *name, zval *value, void **cache_slot)
935 {
936 	if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_VALUE)) || zend_string_equals_literal(name, "scale")) {
937 		zend_readonly_property_modification_error_ex(ZSTR_VAL(obj->ce->name), ZSTR_VAL(name));
938 		return &EG(error_zval);
939 	}
940 
941 	return zend_std_write_property(obj, name, value, cache_slot);
942 }
943 
bcmath_number_unset_property(zend_object * obj,zend_string * name,void ** cache_slot)944 static void bcmath_number_unset_property(zend_object *obj, zend_string *name, void **cache_slot)
945 {
946 	if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_VALUE)) || zend_string_equals_literal(name, "scale")) {
947 		zend_throw_error(NULL, "Cannot unset readonly property %s::$%s", ZSTR_VAL(obj->ce->name), ZSTR_VAL(name));
948 		return;
949 	}
950 
951 	zend_std_unset_property(obj, name, cache_slot);
952 }
953 
bcmath_number_read_property(zend_object * obj,zend_string * name,int type,void ** cache_slot,zval * rv)954 static zval *bcmath_number_read_property(zend_object *obj, zend_string *name, int type, void **cache_slot, zval *rv)
955 {
956 	bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
957 
958 	if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_VALUE))) {
959 		ZVAL_STR_COPY(rv, bcmath_number_value_to_str(intern));
960 		return rv;
961 	}
962 
963 	if (zend_string_equals_literal(name, "scale")) {
964 		ZVAL_LONG(rv, intern->scale);
965 		return rv;
966 	}
967 
968 	return zend_std_read_property(obj, name, type, cache_slot, rv);
969 }
970 
bcmath_number_has_property(zend_object * obj,zend_string * name,int check_empty,void ** cache_slot)971 static int bcmath_number_has_property(zend_object *obj, zend_string *name, int check_empty, void **cache_slot)
972 {
973 	if (check_empty == ZEND_PROPERTY_NOT_EMPTY) {
974 		bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
975 
976 		if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_VALUE))) {
977 			return !bc_is_zero(intern->num);
978 		}
979 
980 		if (zend_string_equals_literal(name, "scale")) {
981 			return intern->scale != 0;
982 		}
983 	}
984 	return zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_VALUE)) || zend_string_equals_literal(name, "scale");
985 }
986 
bcmath_number_cast_object(zend_object * obj,zval * ret,int type)987 static zend_result bcmath_number_cast_object(zend_object *obj, zval *ret, int type)
988 {
989 	if (type == _IS_BOOL) {
990 		bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
991 		ZVAL_BOOL(ret, !bc_is_zero(intern->num));
992 		return SUCCESS;
993 	}
994 
995 	return zend_std_cast_object_tostring(obj, ret, type);
996 }
997 
bcmath_number_register_class(void)998 static void bcmath_number_register_class(void)
999 {
1000 	bcmath_number_ce = register_class_BcMath_Number(zend_ce_stringable);
1001 	bcmath_number_ce->create_object = bcmath_number_create;
1002 	bcmath_number_ce->default_object_handlers = &bcmath_number_obj_handlers;
1003 
1004 	memcpy(&bcmath_number_obj_handlers, &std_object_handlers, sizeof(zend_object_handlers));
1005 	bcmath_number_obj_handlers.offset = XtOffsetOf(bcmath_number_obj_t, std);
1006 	bcmath_number_obj_handlers.free_obj = bcmath_number_free;
1007 	bcmath_number_obj_handlers.clone_obj = bcmath_number_clone;
1008 	bcmath_number_obj_handlers.do_operation = bcmath_number_do_operation;
1009 	bcmath_number_obj_handlers.compare = bcmath_number_compare;
1010 	bcmath_number_obj_handlers.write_property = bcmath_number_write_property;
1011 	bcmath_number_obj_handlers.unset_property = bcmath_number_unset_property;
1012 	bcmath_number_obj_handlers.has_property = bcmath_number_has_property;
1013 	bcmath_number_obj_handlers.read_property = bcmath_number_read_property;
1014 	bcmath_number_obj_handlers.get_properties_for = bcmath_number_get_properties_for;
1015 	bcmath_number_obj_handlers.cast_object = bcmath_number_cast_object;
1016 }
1017 
bcmath_number_add_internal(bc_num n1,bc_num n2,bc_num * ret,size_t n1_full_scale,size_t n2_full_scale,size_t * scale,bool auto_scale)1018 static zend_always_inline void bcmath_number_add_internal(
1019 	bc_num n1, bc_num n2, bc_num *ret,
1020 	size_t n1_full_scale, size_t n2_full_scale, size_t *scale, bool auto_scale
1021 ) {
1022 	if (auto_scale) {
1023 		*scale = MAX(n1_full_scale, n2_full_scale);
1024 	}
1025 	*ret = bc_add(n1, n2, *scale);
1026 	(*ret)->n_scale = MIN(*scale, (*ret)->n_scale);
1027 	bc_rm_trailing_zeros(*ret);
1028 }
1029 
bcmath_number_sub_internal(bc_num n1,bc_num n2,bc_num * ret,size_t n1_full_scale,size_t n2_full_scale,size_t * scale,bool auto_scale)1030 static zend_always_inline void bcmath_number_sub_internal(
1031 	bc_num n1, bc_num n2, bc_num *ret,
1032 	size_t n1_full_scale, size_t n2_full_scale, size_t *scale, bool auto_scale
1033 ) {
1034 	if (auto_scale) {
1035 		*scale = MAX(n1_full_scale, n2_full_scale);
1036 	}
1037 	*ret = bc_sub(n1, n2, *scale);
1038 	(*ret)->n_scale = MIN(*scale, (*ret)->n_scale);
1039 	bc_rm_trailing_zeros(*ret);
1040 }
1041 
bcmath_number_mul_internal(bc_num n1,bc_num n2,bc_num * ret,size_t n1_full_scale,size_t n2_full_scale,size_t * scale,bool auto_scale)1042 static zend_always_inline zend_result bcmath_number_mul_internal(
1043 	bc_num n1, bc_num n2, bc_num *ret,
1044 	size_t n1_full_scale, size_t n2_full_scale, size_t *scale, bool auto_scale
1045 ) {
1046 	if (auto_scale) {
1047 		*scale = n1_full_scale + n2_full_scale;
1048 		if (UNEXPECTED(CHECK_RET_SCALE_OVERFLOW(*scale, n1_full_scale))) {
1049 			zend_value_error("scale of the result is too large");
1050 			return FAILURE;
1051 		}
1052 	}
1053 	*ret = bc_multiply(n1, n2, *scale);
1054 	(*ret)->n_scale = MIN(*scale, (*ret)->n_scale);
1055 	bc_rm_trailing_zeros(*ret);
1056 	return SUCCESS;
1057 }
1058 
bcmath_number_div_internal(bc_num n1,bc_num n2,bc_num * ret,size_t n1_full_scale,size_t * scale,bool auto_scale)1059 static zend_always_inline zend_result bcmath_number_div_internal(
1060 	bc_num n1, bc_num n2, bc_num *ret,
1061 	size_t n1_full_scale, size_t *scale, bool auto_scale
1062 ) {
1063 	if (auto_scale) {
1064 		*scale = n1_full_scale + BC_MATH_NUMBER_EXPAND_SCALE;
1065 		if (UNEXPECTED(CHECK_RET_SCALE_OVERFLOW(*scale, n1_full_scale))) {
1066 			zend_value_error("scale of the result is too large");
1067 			return FAILURE;
1068 		}
1069 	}
1070 	if (!bc_divide(n1, n2, ret, *scale)) {
1071 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
1072 		return FAILURE;
1073 	}
1074 	bc_rm_trailing_zeros(*ret);
1075 	if (auto_scale) {
1076 		size_t diff = *scale - (*ret)->n_scale;
1077 		*scale -= diff > BC_MATH_NUMBER_EXPAND_SCALE ? BC_MATH_NUMBER_EXPAND_SCALE : diff;
1078 	}
1079 	return SUCCESS;
1080 }
1081 
bcmath_number_mod_internal(bc_num n1,bc_num n2,bc_num * ret,size_t n1_full_scale,size_t n2_full_scale,size_t * scale,bool auto_scale)1082 static zend_always_inline zend_result bcmath_number_mod_internal(
1083 	bc_num n1, bc_num n2, bc_num *ret,
1084 	size_t n1_full_scale, size_t n2_full_scale, size_t *scale, bool auto_scale
1085 ) {
1086 	if (auto_scale) {
1087 		*scale = MAX(n1_full_scale, n2_full_scale);
1088 	}
1089 	if (!bc_modulo(n1, n2, ret, *scale)) {
1090 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Modulo by zero");
1091 		return FAILURE;
1092 	}
1093 	bc_rm_trailing_zeros(*ret);
1094 	return SUCCESS;
1095 }
1096 
bcmath_number_pow_internal(bc_num n1,bc_num n2,bc_num * ret,size_t n1_full_scale,size_t * scale,bool auto_scale,bool is_op)1097 static zend_result bcmath_number_pow_internal(
1098 	bc_num n1, bc_num n2, bc_num *ret,
1099 	size_t n1_full_scale, size_t *scale, bool auto_scale, bool is_op
1100 ) {
1101 	/* Check the exponent for scale digits and convert to a long. */
1102 	if (UNEXPECTED(n2->n_scale != 0)) {
1103 		if (is_op) {
1104 			zend_value_error("exponent cannot have a fractional part");
1105 		} else {
1106 			zend_argument_value_error(1, "exponent cannot have a fractional part");
1107 		}
1108 		return FAILURE;
1109 	}
1110 	long exponent = bc_num2long(n2);
1111 
1112 	bool scale_expand = false;
1113 	if (auto_scale) {
1114 		if (exponent > 0) {
1115 			*scale = n1_full_scale * exponent;
1116 			if (UNEXPECTED(*scale > INT_MAX || *scale < n1_full_scale)) {
1117 				zend_value_error("scale of the result is too large");
1118 				return FAILURE;
1119 			}
1120 		} else if (exponent < 0) {
1121 			*scale = n1_full_scale + BC_MATH_NUMBER_EXPAND_SCALE;
1122 			if (UNEXPECTED(CHECK_RET_SCALE_OVERFLOW(*scale, n1_full_scale))) {
1123 				zend_value_error("scale of the result is too large");
1124 				return FAILURE;
1125 			}
1126 			scale_expand = true;
1127 		} else {
1128 			*scale = 0;
1129 		}
1130 	}
1131 
1132 	/**
1133 	 * bc_num2long() returns 0 if exponent is too large.
1134 	 * Here, if n2->n_value is not 0 but exponent is 0, it is considered too large.
1135 	 */
1136 	if (UNEXPECTED(exponent == 0 && (n2->n_len > 1 || n2->n_value[0] != 0))) {
1137 		if (is_op) {
1138 			zend_value_error("exponent is too large");
1139 		} else {
1140 			zend_argument_value_error(1, "exponent is too large");
1141 		}
1142 		return FAILURE;
1143 	}
1144 	bc_raise(n1, exponent, ret, *scale);
1145 	bc_rm_trailing_zeros(*ret);
1146 	if (scale_expand) {
1147 		size_t diff = *scale - (*ret)->n_scale;
1148 		*scale -= diff > BC_MATH_NUMBER_EXPAND_SCALE ? BC_MATH_NUMBER_EXPAND_SCALE : diff;
1149 	}
1150 	return SUCCESS;
1151 }
1152 
bcmath_number_new_obj(bc_num ret,size_t scale)1153 static zend_always_inline bcmath_number_obj_t *bcmath_number_new_obj(bc_num ret, size_t scale)
1154 {
1155 	bcmath_number_obj_t *intern = get_bcmath_number_from_obj(bcmath_number_create(bcmath_number_ce));
1156 	intern->num = ret;
1157 	intern->scale = scale;
1158 	return intern;
1159 }
1160 
bcmath_number_parse_num(zval * zv,zend_object ** obj,zend_string ** str,zend_long * lval)1161 static zend_result bcmath_number_parse_num(zval *zv, zend_object **obj, zend_string **str, zend_long *lval)
1162 {
1163 	if (Z_TYPE_P(zv) == IS_OBJECT && instanceof_function(Z_OBJCE_P(zv), bcmath_number_ce)) {
1164 		*obj = Z_OBJ_P(zv);
1165 		return SUCCESS;
1166 	} else {
1167 		switch (Z_TYPE_P(zv)) {
1168 			case IS_LONG:
1169 				*lval = Z_LVAL_P(zv);
1170 				return SUCCESS;
1171 
1172 			case IS_STRING:
1173 				*str = Z_STR_P(zv);
1174 				return SUCCESS;
1175 
1176 			case IS_NULL:
1177 				*lval = 0;
1178 				return FAILURE;
1179 
1180 			default:
1181 				return zend_parse_arg_long_slow(zv, lval, 1 /* dummy */) ? SUCCESS : FAILURE;
1182 		}
1183 	}
1184 }
1185 
bc_num_from_obj_or_str_or_long(bc_num * num,size_t * full_scale,const zend_object * obj,const zend_string * str,zend_long lval)1186 static zend_result bc_num_from_obj_or_str_or_long(
1187 	bc_num *num, size_t *full_scale, const zend_object *obj, const zend_string *str, zend_long lval)
1188 {
1189 	if (obj) {
1190 		bcmath_number_obj_t *intern = get_bcmath_number_from_obj(obj);
1191 		*num = intern->num;
1192 		if (full_scale) {
1193 			*full_scale = intern->scale;
1194 		}
1195 		return SUCCESS;
1196 	} else if (str) {
1197 		return php_str2num_ex(num, str, full_scale);
1198 	} else {
1199 		php_long2num(num, lval);
1200 		if (full_scale) {
1201 			*full_scale = 0;
1202 		}
1203 		return SUCCESS;
1204 	}
1205 }
1206 
bcmath_number_do_operation(uint8_t opcode,zval * ret_val,zval * op1,zval * op2)1207 static zend_result bcmath_number_do_operation(uint8_t opcode, zval *ret_val, zval *op1, zval *op2)
1208 {
1209 	switch (opcode) {
1210 		case ZEND_ADD:
1211 		case ZEND_SUB:
1212 		case ZEND_MUL:
1213 		case ZEND_DIV:
1214 		case ZEND_MOD:
1215 		case ZEND_POW:
1216 			break;
1217 		default:
1218 			return FAILURE;
1219 	}
1220 
1221 	zend_object *obj1 = NULL;
1222 	zend_string *str1 = NULL;
1223 	zend_long lval1 = 0;
1224 
1225 	zend_object *obj2 = NULL;
1226 	zend_string *str2 = NULL;
1227 	zend_long lval2 = 0;
1228 
1229 	if (UNEXPECTED(bcmath_number_parse_num(op1, &obj1, &str1, &lval1) == FAILURE || bcmath_number_parse_num(op2, &obj2, &str2, &lval2) == FAILURE)) {
1230 		return FAILURE;
1231 	}
1232 
1233 	bc_num n1 = NULL;
1234 	bc_num n2 = NULL;
1235 	size_t n1_full_scale;
1236 	size_t n2_full_scale;
1237 	if (UNEXPECTED(bc_num_from_obj_or_str_or_long(&n1, &n1_full_scale, obj1, str1, lval1) == FAILURE)) {
1238 		zend_value_error("Left string operand cannot be converted to BcMath\\Number");
1239 		goto fail;
1240 	}
1241 	if (UNEXPECTED(bc_num_from_obj_or_str_or_long(&n2, &n2_full_scale, obj2, str2, lval2) == FAILURE)) {
1242 		zend_value_error("Right string operand cannot be converted to BcMath\\Number");
1243 		goto fail;
1244 	}
1245 
1246 	if (UNEXPECTED(CHECK_SCALE_OVERFLOW(n1_full_scale) || CHECK_SCALE_OVERFLOW(n2_full_scale))) {
1247 		zend_value_error("scale must be between 0 and %d", INT_MAX);
1248 		goto fail;
1249 	}
1250 
1251 	bc_num ret = NULL;
1252 	size_t scale;
1253 	switch (opcode) {
1254 		case ZEND_ADD:
1255 			bcmath_number_add_internal(n1, n2, &ret, n1_full_scale, n2_full_scale, &scale, true);
1256 			break;
1257 		case ZEND_SUB:
1258 			bcmath_number_sub_internal(n1, n2, &ret, n1_full_scale, n2_full_scale, &scale, true);
1259 			break;
1260 		case ZEND_MUL:
1261 			if (UNEXPECTED(bcmath_number_mul_internal(n1, n2, &ret, n1_full_scale, n2_full_scale, &scale, true) == FAILURE)) {
1262 				goto fail;
1263 			}
1264 			break;
1265 		case ZEND_DIV:
1266 			if (UNEXPECTED(bcmath_number_div_internal(n1, n2, &ret, n1_full_scale, &scale, true) == FAILURE)) {
1267 				goto fail;
1268 			}
1269 			break;
1270 		case ZEND_MOD:
1271 			if (UNEXPECTED(bcmath_number_mod_internal(n1, n2, &ret, n1_full_scale, n2_full_scale, &scale, true) == FAILURE)) {
1272 				goto fail;
1273 			}
1274 			break;
1275 		case ZEND_POW:
1276 			if (UNEXPECTED(bcmath_number_pow_internal(n1, n2, &ret, n1_full_scale, &scale, true, true) == FAILURE)) {
1277 				goto fail;
1278 			}
1279 			break;
1280 		EMPTY_SWITCH_DEFAULT_CASE();
1281 	}
1282 
1283 	if (Z_TYPE_P(op1) != IS_OBJECT) {
1284 		bc_free_num(&n1);
1285 	}
1286 	if (Z_TYPE_P(op2) != IS_OBJECT) {
1287 		bc_free_num(&n2);
1288 	}
1289 
1290 	bcmath_number_obj_t *intern = bcmath_number_new_obj(ret, scale);
1291 
1292 	/* For increment and decrement, etc */
1293 	if (ret_val == op1) {
1294 		zval_ptr_dtor(ret_val);
1295 	}
1296 	ZVAL_OBJ(ret_val, &intern->std);
1297 
1298 	return SUCCESS;
1299 
1300 fail:
1301 	if (Z_TYPE_P(op1) != IS_OBJECT) {
1302 		bc_free_num(&n1);
1303 	}
1304 	if (Z_TYPE_P(op2) != IS_OBJECT) {
1305 		bc_free_num(&n2);
1306 	}
1307 	return FAILURE;
1308 }
1309 
bcmath_number_compare(zval * op1,zval * op2)1310 static int bcmath_number_compare(zval *op1, zval *op2)
1311 {
1312 	zend_object *obj1 = NULL;
1313 	zend_string *str1 = NULL;
1314 	zend_long lval1 = 0;
1315 
1316 	zend_object *obj2 = NULL;
1317 	zend_string *str2 = NULL;
1318 	zend_long lval2 = 0;
1319 
1320 	bc_num n1 = NULL;
1321 	bc_num n2 = NULL;
1322 
1323 	int ret = ZEND_UNCOMPARABLE;
1324 
1325 	if (UNEXPECTED(bcmath_number_parse_num(op1, &obj1, &str1, &lval1) == FAILURE)) {
1326 		goto failure;
1327 	}
1328 
1329 	if (UNEXPECTED(bcmath_number_parse_num(op2, &obj2, &str2, &lval2) == FAILURE)) {
1330 		goto failure;
1331 	}
1332 
1333 	size_t n1_full_scale;
1334 	size_t n2_full_scale;
1335 	if (UNEXPECTED(bc_num_from_obj_or_str_or_long(&n1, &n1_full_scale, obj1, str1, lval1) == FAILURE ||
1336 		bc_num_from_obj_or_str_or_long(&n2, &n2_full_scale, obj2, str2, lval2) == FAILURE)) {
1337 		goto failure;
1338 	}
1339 
1340 	if (UNEXPECTED(CHECK_SCALE_OVERFLOW(n1_full_scale) || CHECK_SCALE_OVERFLOW(n2_full_scale))) {
1341 		zend_value_error("scale must be between 0 and %d", INT_MAX);
1342 		goto failure;
1343 	}
1344 
1345 	ret = bc_compare(n1, n2, MAX(n1->n_scale, n2->n_scale));
1346 
1347 failure:
1348 	if (Z_TYPE_P(op1) != IS_OBJECT) {
1349 		bc_free_num(&n1);
1350 	}
1351 	if (Z_TYPE_P(op2) != IS_OBJECT) {
1352 		bc_free_num(&n2);
1353 	}
1354 
1355 	return ret;
1356 }
1357 
1358 #define BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(dest_obj, ce, dest_str, dest_long) \
1359 	Z_PARAM_PROLOGUE(0, 0); \
1360 	if (UNEXPECTED(!(zend_parse_arg_obj(_arg, &(dest_obj), ce, 0) || \
1361 		zend_parse_arg_str_or_long(_arg, &(dest_str), &(dest_long), &_dummy, 0, _i)))) { \
1362 		zend_argument_type_error(_i, "must be of type int, string, or %s, %s given", \
1363 			ZSTR_VAL(bcmath_number_ce->name), zend_zval_value_name(_arg));             \
1364 		_error_code = ZPP_ERROR_FAILURE; \
1365  		break; \
1366  	}
1367 
bc_num_from_obj_or_str_or_long_with_err(bc_num * num,size_t * scale,zend_object * obj,zend_string * str,zend_long lval,uint32_t arg_num)1368 static zend_always_inline zend_result bc_num_from_obj_or_str_or_long_with_err(
1369 	bc_num *num, size_t *scale, zend_object *obj, zend_string *str, zend_long lval, uint32_t arg_num)
1370 {
1371 	size_t full_scale = 0;
1372 	if (UNEXPECTED(bc_num_from_obj_or_str_or_long(num, &full_scale, obj, str, lval) == FAILURE)) {
1373 		zend_argument_value_error(arg_num, "is not well-formed");
1374 		return FAILURE;
1375 	}
1376 	if (UNEXPECTED(CHECK_SCALE_OVERFLOW(full_scale))) {
1377 		zend_argument_value_error(arg_num, "must be between 0 and %d", INT_MAX);
1378 		return FAILURE;
1379 	}
1380 	if (scale != NULL) {
1381 		*scale = full_scale;
1382 	}
1383 	return SUCCESS;
1384 }
1385 
PHP_METHOD(BcMath_Number,__construct)1386 PHP_METHOD(BcMath_Number, __construct)
1387 {
1388 	zend_string *str = NULL;
1389 	zend_long lval = 0;
1390 
1391 	ZEND_PARSE_PARAMETERS_START(1, 1)
1392 		Z_PARAM_STR_OR_LONG(str, lval);
1393 	ZEND_PARSE_PARAMETERS_END();
1394 
1395 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1396 	if (UNEXPECTED(intern->num != NULL)) {
1397 		zend_readonly_property_modification_error_ex(ZSTR_VAL(bcmath_number_ce->name), "value");
1398 		RETURN_THROWS();
1399 	}
1400 
1401 	bc_num num = NULL;
1402 	size_t scale = 0;
1403 	if (bc_num_from_obj_or_str_or_long_with_err(&num, &scale, NULL, str, lval, 1) == FAILURE) {
1404 		bc_free_num(&num);
1405 		RETURN_THROWS();
1406 	}
1407 
1408 	intern->num = num;
1409 	intern->scale = scale;
1410 }
1411 
bcmath_number_calc_method(INTERNAL_FUNCTION_PARAMETERS,uint8_t opcode)1412 static void bcmath_number_calc_method(INTERNAL_FUNCTION_PARAMETERS, uint8_t opcode)
1413 {
1414 	zend_object *num_obj = NULL;
1415 	zend_string *num_str = NULL;
1416 	zend_long num_lval = 0;
1417 	zend_long scale_lval = 0;
1418 	bool scale_is_null = true;
1419 
1420 	ZEND_PARSE_PARAMETERS_START(1, 2)
1421 		BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(num_obj, bcmath_number_ce, num_str, num_lval);
1422 		Z_PARAM_OPTIONAL
1423 		Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
1424 	ZEND_PARSE_PARAMETERS_END();
1425 
1426 	bc_num num = NULL;
1427 	size_t num_full_scale = 0;
1428 	if (bc_num_from_obj_or_str_or_long_with_err(&num, &num_full_scale, num_obj, num_str, num_lval, 1) == FAILURE) {
1429 		goto fail;
1430 	}
1431 	if (bcmath_check_scale(scale_lval, 2) == FAILURE) {
1432 		goto fail;
1433 	}
1434 
1435 	bc_num ret = NULL;
1436 	size_t scale = scale_lval;
1437 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1438 
1439 	switch (opcode) {
1440 		case ZEND_ADD:
1441 			bcmath_number_add_internal(intern->num, num, &ret, intern->scale, num_full_scale, &scale, scale_is_null);
1442 			break;
1443 		case ZEND_SUB:
1444 			bcmath_number_sub_internal(intern->num, num, &ret, intern->scale, num_full_scale, &scale, scale_is_null);
1445 			break;
1446 		case ZEND_MUL:
1447 			if (UNEXPECTED(bcmath_number_mul_internal(intern->num, num, &ret, intern->scale, num_full_scale, &scale, scale_is_null) == FAILURE)) {
1448 				goto fail;
1449 			}
1450 			break;
1451 		case ZEND_DIV:
1452 			if (UNEXPECTED(bcmath_number_div_internal(intern->num, num, &ret, intern->scale, &scale, scale_is_null) == FAILURE)) {
1453 				goto fail;
1454 			}
1455 			break;
1456 		case ZEND_MOD:
1457 			if (UNEXPECTED(bcmath_number_mod_internal(intern->num, num, &ret, intern->scale, num_full_scale, &scale, scale_is_null) == FAILURE)) {
1458 				goto fail;
1459 			}
1460 			break;
1461 		case ZEND_POW:
1462 			if (UNEXPECTED(bcmath_number_pow_internal(intern->num, num, &ret, intern->scale, &scale, scale_is_null, false) == FAILURE)) {
1463 				goto fail;
1464 			}
1465 			break;
1466 		EMPTY_SWITCH_DEFAULT_CASE();
1467 	}
1468 
1469 	if (num_obj == NULL) {
1470 		bc_free_num(&num);
1471 	}
1472 
1473 	bcmath_number_obj_t *new_intern = bcmath_number_new_obj(ret, scale);
1474 	RETURN_OBJ(&new_intern->std);
1475 
1476 fail:
1477 	if (num_obj == NULL) {
1478 		bc_free_num(&num);
1479 	}
1480 	RETURN_THROWS();
1481 }
1482 
PHP_METHOD(BcMath_Number,add)1483 PHP_METHOD(BcMath_Number, add)
1484 {
1485 	bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ADD);
1486 }
1487 
PHP_METHOD(BcMath_Number,sub)1488 PHP_METHOD(BcMath_Number, sub)
1489 {
1490 	bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_SUB);
1491 }
1492 
PHP_METHOD(BcMath_Number,mul)1493 PHP_METHOD(BcMath_Number, mul)
1494 {
1495 	bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_MUL);
1496 }
1497 
PHP_METHOD(BcMath_Number,div)1498 PHP_METHOD(BcMath_Number, div)
1499 {
1500 	bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_DIV);
1501 }
1502 
PHP_METHOD(BcMath_Number,mod)1503 PHP_METHOD(BcMath_Number, mod)
1504 {
1505 	bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_MOD);
1506 }
1507 
PHP_METHOD(BcMath_Number,pow)1508 PHP_METHOD(BcMath_Number, pow)
1509 {
1510 	bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_POW);
1511 }
1512 
PHP_METHOD(BcMath_Number,divmod)1513 PHP_METHOD(BcMath_Number, divmod)
1514 {
1515 	zend_object *num_obj = NULL;
1516 	zend_string *num_str = NULL;
1517 	zend_long num_lval = 0;
1518 	zend_long scale_lval = 0;
1519 	bool scale_is_null = true;
1520 
1521 	ZEND_PARSE_PARAMETERS_START(1, 2)
1522 		BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(num_obj, bcmath_number_ce, num_str, num_lval);
1523 		Z_PARAM_OPTIONAL
1524 		Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
1525 	ZEND_PARSE_PARAMETERS_END();
1526 
1527 	bc_num num = NULL;
1528 	size_t num_full_scale;
1529 	if (bc_num_from_obj_or_str_or_long_with_err(&num, &num_full_scale, num_obj, num_str, num_lval, 1) == FAILURE) {
1530 		goto fail;
1531 	}
1532 	if (bcmath_check_scale(scale_lval, 2) == FAILURE) {
1533 		goto fail;
1534 	}
1535 
1536 	bc_num quot = NULL;
1537 	bc_num rem = NULL;
1538 	size_t scale = scale_lval;
1539 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1540 
1541 	if (scale_is_null) {
1542 		scale = MAX(intern->scale, num_full_scale);
1543 	}
1544 
1545 	if (!bc_divmod(intern->num, num, &quot, &rem, scale)) {
1546 		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
1547 		goto fail;
1548 	}
1549 	bc_rm_trailing_zeros(quot);
1550 	bc_rm_trailing_zeros(rem);
1551 
1552 	if (num_obj == NULL) {
1553 		bc_free_num(&num);
1554 	}
1555 
1556 	bcmath_number_obj_t *quot_intern = bcmath_number_new_obj(quot, 0);
1557 	bcmath_number_obj_t *rem_intern = bcmath_number_new_obj(rem, scale);
1558 
1559 	zval z_quot, z_rem;
1560 	ZVAL_OBJ(&z_quot, &quot_intern->std);
1561 	ZVAL_OBJ(&z_rem, &rem_intern->std);
1562 
1563 	RETURN_ARR(zend_new_pair(&z_quot, &z_rem));
1564 
1565 fail:
1566 	if (num_obj == NULL) {
1567 		bc_free_num(&num);
1568 	}
1569 	RETURN_THROWS();
1570 }
1571 
PHP_METHOD(BcMath_Number,powmod)1572 PHP_METHOD(BcMath_Number, powmod)
1573 {
1574 	zend_object *exponent_obj = NULL;
1575 	zend_string *exponent_str = NULL;
1576 	zend_long exponent_lval = 0;
1577 
1578 	zend_object *modulus_obj = NULL;
1579 	zend_string *modulus_str = NULL;
1580 	zend_long modulus_lval = 0;
1581 
1582 	zend_long scale_lval = 0;
1583 	bool scale_is_null = true;
1584 
1585 	ZEND_PARSE_PARAMETERS_START(2, 3)
1586 		BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(exponent_obj, bcmath_number_ce, exponent_str, exponent_lval);
1587 		BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(modulus_obj, bcmath_number_ce, modulus_str, modulus_lval);
1588 		Z_PARAM_OPTIONAL
1589 		Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
1590 	ZEND_PARSE_PARAMETERS_END();
1591 
1592 	bc_num exponent_num = NULL;
1593 	bc_num modulus_num = NULL;
1594 	if (bc_num_from_obj_or_str_or_long_with_err(&exponent_num, NULL, exponent_obj, exponent_str, exponent_lval, 1) == FAILURE) {
1595 		goto cleanup;
1596 	}
1597 	if (bc_num_from_obj_or_str_or_long_with_err(&modulus_num, NULL, modulus_obj, modulus_str, modulus_lval, 2) == FAILURE) {
1598 		goto cleanup;
1599 	}
1600 	if (bcmath_check_scale(scale_lval, 3) == FAILURE) {
1601 		goto cleanup;
1602 	}
1603 
1604 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1605 	bc_num ret = NULL;
1606 	size_t scale = scale_lval;
1607 	raise_mod_status status = bc_raisemod(intern->num, exponent_num, modulus_num, &ret, scale);
1608 	switch (status) {
1609 		case BASE_HAS_FRACTIONAL:
1610 			zend_value_error("Base number cannot have a fractional part");
1611 			goto cleanup;
1612 		case EXPO_HAS_FRACTIONAL:
1613 			zend_argument_value_error(1, "cannot have a fractional part");
1614 			goto cleanup;
1615 		case EXPO_IS_NEGATIVE:
1616 			zend_argument_value_error(1, "must be greater than or equal to 0");
1617 			goto cleanup;
1618 		case MOD_HAS_FRACTIONAL:
1619 			zend_argument_value_error(2, "cannot have a fractional part");
1620 			goto cleanup;
1621 		case MOD_IS_ZERO:
1622 			zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Modulo by zero");
1623 			goto cleanup;
1624 		case OK:
1625 			break;
1626 		EMPTY_SWITCH_DEFAULT_CASE();
1627 	}
1628 
1629 	bc_rm_trailing_zeros(ret);
1630 
1631 	if (exponent_obj == NULL) {
1632 		bc_free_num(&exponent_num);
1633 	}
1634 	if (modulus_obj == NULL) {
1635 		bc_free_num(&modulus_num);
1636 	}
1637 
1638 	bcmath_number_obj_t *new_intern = bcmath_number_new_obj(ret, scale);
1639 	RETURN_OBJ(&new_intern->std);
1640 
1641 cleanup:
1642 	if (exponent_obj == NULL) {
1643 		bc_free_num(&exponent_num);
1644 	}
1645 	if (modulus_obj == NULL) {
1646 		bc_free_num(&modulus_num);
1647 	}
1648 	RETURN_THROWS();
1649 }
1650 
PHP_METHOD(BcMath_Number,sqrt)1651 PHP_METHOD(BcMath_Number, sqrt)
1652 {
1653 	zend_long scale_lval = 0;
1654 	bool scale_is_null = true;
1655 
1656 	ZEND_PARSE_PARAMETERS_START(0, 1)
1657 		Z_PARAM_OPTIONAL
1658 		Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
1659 	ZEND_PARSE_PARAMETERS_END();
1660 
1661 	if (bcmath_check_scale(scale_lval, 1) == FAILURE) {
1662 		RETURN_THROWS();
1663 	}
1664 
1665 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1666 
1667 	size_t scale;
1668 	if (scale_is_null) {
1669 		scale = intern->scale + BC_MATH_NUMBER_EXPAND_SCALE;
1670 		if (UNEXPECTED(CHECK_RET_SCALE_OVERFLOW(scale, intern->scale))) {
1671 			zend_value_error("scale of the result is too large");
1672 			RETURN_THROWS();
1673 		}
1674 	} else {
1675 		scale = scale_lval;
1676 	}
1677 
1678 	bc_num ret = bc_copy_num(intern->num);
1679 	if (!bc_sqrt (&ret, scale)) {
1680 		zend_value_error("Base number must be greater than or equal to 0");
1681 		bc_free_num(&ret);
1682 		RETURN_THROWS();
1683 	}
1684 
1685 	ret->n_scale = MIN(scale, ret->n_scale);
1686 	bc_rm_trailing_zeros(ret);
1687 	if (scale_is_null) {
1688 		size_t diff = scale - ret->n_scale;
1689 		scale -= diff > BC_MATH_NUMBER_EXPAND_SCALE ? BC_MATH_NUMBER_EXPAND_SCALE : diff;
1690 	}
1691 
1692 	bcmath_number_obj_t *new_intern = bcmath_number_new_obj(ret, scale);
1693 	RETURN_OBJ(&new_intern->std);
1694 }
1695 
PHP_METHOD(BcMath_Number,compare)1696 PHP_METHOD(BcMath_Number, compare)
1697 {
1698 	zend_object *num_obj = NULL;
1699 	zend_string *num_str = NULL;
1700 	zend_long num_lval = 0;
1701 	zend_long scale_lval = 0;
1702 	bool scale_is_null = true;
1703 
1704 	ZEND_PARSE_PARAMETERS_START(1, 2)
1705 		BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(num_obj, bcmath_number_ce, num_str, num_lval);
1706 		Z_PARAM_OPTIONAL
1707 		Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
1708 	ZEND_PARSE_PARAMETERS_END();
1709 
1710 	bc_num num = NULL;
1711 	size_t num_full_scale = 0;
1712 	if (bc_num_from_obj_or_str_or_long_with_err(&num, &num_full_scale, num_obj, num_str, num_lval, 1) == FAILURE) {
1713 		goto fail;
1714 	}
1715 	if (bcmath_check_scale(scale_lval, 2) == FAILURE) {
1716 		goto fail;
1717 	}
1718 
1719 	size_t scale;
1720 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1721 	if (scale_is_null) {
1722 		scale = MAX(intern->num->n_scale, num->n_scale);
1723 	} else {
1724 		scale = scale_lval;
1725 	}
1726 	zend_long ret = bc_compare(intern->num, num, scale);
1727 
1728 	if (num_obj == NULL) {
1729 		bc_free_num(&num);
1730 	}
1731 	RETURN_LONG(ret);
1732 
1733 fail:
1734 	if (num_obj == NULL) {
1735 		bc_free_num(&num);
1736 	}
1737 	RETURN_THROWS();
1738 }
1739 
bcmath_number_floor_or_ceil(INTERNAL_FUNCTION_PARAMETERS,bool is_floor)1740 static void bcmath_number_floor_or_ceil(INTERNAL_FUNCTION_PARAMETERS, bool is_floor)
1741 {
1742 	ZEND_PARSE_PARAMETERS_NONE();
1743 
1744 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1745 
1746 	bc_num ret = bc_floor_or_ceil(intern->num, is_floor);
1747 
1748 	bcmath_number_obj_t *new_intern = bcmath_number_new_obj(ret, 0);
1749 	RETURN_OBJ(&new_intern->std);
1750 }
1751 
PHP_METHOD(BcMath_Number,floor)1752 PHP_METHOD(BcMath_Number, floor)
1753 {
1754 	bcmath_number_floor_or_ceil(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1755 }
1756 
PHP_METHOD(BcMath_Number,ceil)1757 PHP_METHOD(BcMath_Number, ceil)
1758 {
1759 	bcmath_number_floor_or_ceil(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1760 }
1761 
PHP_METHOD(BcMath_Number,round)1762 PHP_METHOD(BcMath_Number, round)
1763 {
1764 	zend_long precision = 0;
1765 	zend_long rounding_mode = PHP_ROUND_HALF_UP;
1766 	zend_object *mode_object = NULL;
1767 
1768 	ZEND_PARSE_PARAMETERS_START(0, 2)
1769 		Z_PARAM_OPTIONAL
1770 		Z_PARAM_LONG(precision);
1771 		Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce);
1772 	ZEND_PARSE_PARAMETERS_END();
1773 
1774 	if (mode_object != NULL) {
1775 		rounding_mode = php_math_round_mode_from_enum(mode_object);
1776 	}
1777 
1778 	switch (rounding_mode) {
1779 		case PHP_ROUND_HALF_UP:
1780 		case PHP_ROUND_HALF_DOWN:
1781 		case PHP_ROUND_HALF_EVEN:
1782 		case PHP_ROUND_HALF_ODD:
1783 		case PHP_ROUND_CEILING:
1784 		case PHP_ROUND_FLOOR:
1785 		case PHP_ROUND_TOWARD_ZERO:
1786 		case PHP_ROUND_AWAY_FROM_ZERO:
1787 			break;
1788 		default:
1789 			zend_argument_value_error(2, "is an unsupported rounding mode");
1790 			RETURN_THROWS();
1791 	}
1792 
1793 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1794 
1795 	bc_num ret = NULL;
1796 	bc_round(intern->num, precision, rounding_mode, &ret);
1797 
1798 	bc_rm_trailing_zeros(ret);
1799 
1800 	bcmath_number_obj_t *new_intern = bcmath_number_new_obj(ret, ret->n_scale);
1801 	RETURN_OBJ(&new_intern->std);
1802 }
1803 
PHP_METHOD(BcMath_Number,__toString)1804 PHP_METHOD(BcMath_Number, __toString)
1805 {
1806 	ZEND_PARSE_PARAMETERS_NONE();
1807 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1808 	RETURN_STR_COPY(bcmath_number_value_to_str(intern));
1809 }
1810 
PHP_METHOD(BcMath_Number,__serialize)1811 PHP_METHOD(BcMath_Number, __serialize)
1812 {
1813 	ZEND_PARSE_PARAMETERS_NONE();
1814 
1815 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1816 
1817 	array_init(return_value);
1818 	HashTable *props = Z_ARRVAL_P(return_value);
1819 
1820 	zval zv;
1821 	ZVAL_STR_COPY(&zv, bcmath_number_value_to_str(intern));
1822 	zend_hash_update(props, ZSTR_KNOWN(ZEND_STR_VALUE), &zv);
1823 }
1824 
PHP_METHOD(BcMath_Number,__unserialize)1825 PHP_METHOD(BcMath_Number, __unserialize)
1826 {
1827 	HashTable *props;
1828 
1829 	ZEND_PARSE_PARAMETERS_START(1, 1)
1830 		Z_PARAM_ARRAY_HT(props)
1831 	ZEND_PARSE_PARAMETERS_END();
1832 
1833 	zval *zv = zend_hash_find(props, ZSTR_KNOWN(ZEND_STR_VALUE));
1834 	if (!zv || Z_TYPE_P(zv) != IS_STRING || Z_STRLEN_P(zv) == 0) {
1835 		goto fail;
1836 	}
1837 
1838 	bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1839 	if (UNEXPECTED(intern->num != NULL)) {
1840 		zend_readonly_property_modification_error_ex(ZSTR_VAL(bcmath_number_ce->name), "value");
1841 		RETURN_THROWS();
1842 	}
1843 
1844 	bc_num num = NULL;
1845 	size_t scale = 0;
1846 	if (php_str2num_ex(&num, Z_STR_P(zv), &scale) == FAILURE || CHECK_SCALE_OVERFLOW(scale)) {
1847 		bc_free_num(&num);
1848 		goto fail;
1849 	}
1850 
1851 	intern->num = num;
1852 	intern->scale = scale;
1853 
1854 	return;
1855 
1856 fail:
1857 	zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(bcmath_number_ce->name));
1858 	RETURN_THROWS();
1859 }
1860 
1861 #endif
1862