xref: /PHP-8.0/ext/bcmath/bcmath.c (revision 94a151a0)
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    | 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 "bcmath_arginfo.h"
28 #include "ext/standard/info.h"
29 #include "php_bcmath.h"
30 #include "libbcmath/src/bcmath.h"
31 
32 ZEND_DECLARE_MODULE_GLOBALS(bcmath)
33 static PHP_GINIT_FUNCTION(bcmath);
34 static PHP_GSHUTDOWN_FUNCTION(bcmath);
35 
36 zend_module_entry bcmath_module_entry = {
37 	STANDARD_MODULE_HEADER,
38 	"bcmath",
39 	ext_functions,
40 	PHP_MINIT(bcmath),
41 	PHP_MSHUTDOWN(bcmath),
42 	NULL,
43 	NULL,
44 	PHP_MINFO(bcmath),
45 	PHP_BCMATH_VERSION,
46 	PHP_MODULE_GLOBALS(bcmath),
47 	PHP_GINIT(bcmath),
48 	PHP_GSHUTDOWN(bcmath),
49 	NULL,
50 	STANDARD_MODULE_PROPERTIES_EX
51 };
52 
53 #ifdef COMPILE_DL_BCMATH
54 #ifdef ZTS
55 ZEND_TSRMLS_CACHE_DEFINE()
56 #endif
ZEND_GET_MODULE(bcmath)57 ZEND_GET_MODULE(bcmath)
58 #endif
59 
60 ZEND_INI_MH(OnUpdateScale)
61 {
62 	int *p;
63 	zend_long tmp;
64 
65 	tmp = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value));
66 	if (tmp < 0 || tmp > INT_MAX) {
67 		return FAILURE;
68 	}
69 
70 	p = (int *) ZEND_INI_GET_ADDR();
71 	*p = (int) tmp;
72 
73 	return SUCCESS;
74 }
75 
76 /* {{{ PHP_INI */
77 PHP_INI_BEGIN()
78 	STD_PHP_INI_ENTRY("bcmath.scale", "0", PHP_INI_ALL, OnUpdateScale, bc_precision, zend_bcmath_globals, bcmath_globals)
PHP_INI_END()79 PHP_INI_END()
80 /* }}} */
81 
82 /* {{{ PHP_GINIT_FUNCTION */
83 static PHP_GINIT_FUNCTION(bcmath)
84 {
85 #if defined(COMPILE_DL_BCMATH) && defined(ZTS)
86 	ZEND_TSRMLS_CACHE_UPDATE();
87 #endif
88 	bcmath_globals->bc_precision = 0;
89 	bc_init_numbers();
90 }
91 /* }}} */
92 
93 /* {{{ PHP_GSHUTDOWN_FUNCTION */
PHP_GSHUTDOWN_FUNCTION(bcmath)94 static PHP_GSHUTDOWN_FUNCTION(bcmath)
95 {
96 	_bc_free_num_ex(&bcmath_globals->_zero_, 1);
97 	_bc_free_num_ex(&bcmath_globals->_one_, 1);
98 	_bc_free_num_ex(&bcmath_globals->_two_, 1);
99 }
100 /* }}} */
101 
102 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(bcmath)103 PHP_MINIT_FUNCTION(bcmath)
104 {
105 	REGISTER_INI_ENTRIES();
106 
107 	return SUCCESS;
108 }
109 /* }}} */
110 
111 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(bcmath)112 PHP_MSHUTDOWN_FUNCTION(bcmath)
113 {
114 	UNREGISTER_INI_ENTRIES();
115 
116 	return SUCCESS;
117 }
118 /* }}} */
119 
120 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(bcmath)121 PHP_MINFO_FUNCTION(bcmath)
122 {
123 	php_info_print_table_start();
124 	php_info_print_table_row(2, "BCMath support", "enabled");
125 	php_info_print_table_end();
126 	DISPLAY_INI_ENTRIES();
127 }
128 /* }}} */
129 
130 /* {{{ php_str2num
131    Convert to bc_num detecting scale */
php_str2num(bc_num * num,char * str)132 static zend_result php_str2num(bc_num *num, char *str)
133 {
134 	char *p;
135 
136 	if (!(p = strchr(str, '.'))) {
137 		if (!bc_str2num(num, str, 0)) {
138 			return FAILURE;
139 		}
140 
141 		return SUCCESS;
142 	}
143 
144 	if (!bc_str2num(num, str, strlen(p+1))) {
145 		return FAILURE;
146 	}
147 
148 	return SUCCESS;
149 }
150 /* }}} */
151 
152 /* {{{ Returns the sum of two arbitrary precision numbers */
PHP_FUNCTION(bcadd)153 PHP_FUNCTION(bcadd)
154 {
155 	zend_string *left, *right;
156 	zend_long scale_param;
157 	zend_bool scale_param_is_null = 1;
158 	bc_num first, second, result;
159 	int scale;
160 
161 	ZEND_PARSE_PARAMETERS_START(2, 3)
162 		Z_PARAM_STR(left)
163 		Z_PARAM_STR(right)
164 		Z_PARAM_OPTIONAL
165 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
166 	ZEND_PARSE_PARAMETERS_END();
167 
168 	if (scale_param_is_null) {
169 		scale = BCG(bc_precision);
170 	} else if (scale_param < 0 || scale_param > INT_MAX) {
171 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
172 		RETURN_THROWS();
173 	} else {
174 		scale = (int) scale_param;
175 	}
176 
177 	bc_init_num(&first);
178 	bc_init_num(&second);
179 	bc_init_num(&result);
180 
181 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
182 		zend_argument_value_error(1, "is not well-formed");
183 		goto cleanup;
184 	}
185 
186 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
187 		zend_argument_value_error(2, "is not well-formed");
188 		goto cleanup;
189 	}
190 
191 	bc_add (first, second, &result, scale);
192 
193 	RETVAL_STR(bc_num2str_ex(result, scale));
194 
195 	cleanup: {
196 		bc_free_num(&first);
197 		bc_free_num(&second);
198 		bc_free_num(&result);
199 	};
200 }
201 /* }}} */
202 
203 /* {{{ Returns the difference between two arbitrary precision numbers */
PHP_FUNCTION(bcsub)204 PHP_FUNCTION(bcsub)
205 {
206 	zend_string *left, *right;
207 	zend_long scale_param;
208 	zend_bool scale_param_is_null = 1;
209 	bc_num first, second, result;
210 	int scale;
211 
212 	ZEND_PARSE_PARAMETERS_START(2, 3)
213 		Z_PARAM_STR(left)
214 		Z_PARAM_STR(right)
215 		Z_PARAM_OPTIONAL
216 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
217 	ZEND_PARSE_PARAMETERS_END();
218 
219 	if (scale_param_is_null) {
220 		scale = BCG(bc_precision);
221 	} else if (scale_param < 0 || scale_param > INT_MAX) {
222 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
223 		RETURN_THROWS();
224 	} else {
225 		scale = (int) scale_param;
226 	}
227 
228 	bc_init_num(&first);
229 	bc_init_num(&second);
230 	bc_init_num(&result);
231 
232 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
233 		zend_argument_value_error(1, "is not well-formed");
234 		goto cleanup;
235 	}
236 
237 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
238 		zend_argument_value_error(2, "is not well-formed");
239 		goto cleanup;
240 	}
241 
242 	bc_sub (first, second, &result, scale);
243 
244 	RETVAL_STR(bc_num2str_ex(result, scale));
245 
246 	cleanup: {
247 		bc_free_num(&first);
248 		bc_free_num(&second);
249 		bc_free_num(&result);
250 	};
251 }
252 /* }}} */
253 
254 /* {{{ Returns the multiplication of two arbitrary precision numbers */
PHP_FUNCTION(bcmul)255 PHP_FUNCTION(bcmul)
256 {
257 	zend_string *left, *right;
258 	zend_long scale_param;
259 	zend_bool scale_param_is_null = 1;
260 	bc_num first, second, result;
261 	int scale;
262 
263 	ZEND_PARSE_PARAMETERS_START(2, 3)
264 		Z_PARAM_STR(left)
265 		Z_PARAM_STR(right)
266 		Z_PARAM_OPTIONAL
267 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
268 	ZEND_PARSE_PARAMETERS_END();
269 
270 	if (scale_param_is_null) {
271 		scale = BCG(bc_precision);
272 	} else if (scale_param < 0 || scale_param > INT_MAX) {
273 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
274 		RETURN_THROWS();
275 	} else {
276 		scale = (int) scale_param;
277 	}
278 
279 	bc_init_num(&first);
280 	bc_init_num(&second);
281 	bc_init_num(&result);
282 
283 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
284 		zend_argument_value_error(1, "is not well-formed");
285 		goto cleanup;
286 	}
287 
288 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
289 		zend_argument_value_error(2, "is not well-formed");
290 		goto cleanup;
291 	}
292 
293 	bc_multiply (first, second, &result, scale);
294 
295 	RETVAL_STR(bc_num2str_ex(result, scale));
296 
297 	cleanup: {
298 		bc_free_num(&first);
299 		bc_free_num(&second);
300 		bc_free_num(&result);
301 	};
302 }
303 /* }}} */
304 
305 /* {{{ Returns the quotient of two arbitrary precision numbers (division) */
PHP_FUNCTION(bcdiv)306 PHP_FUNCTION(bcdiv)
307 {
308 	zend_string *left, *right;
309 	zend_long scale_param;
310 	zend_bool scale_param_is_null = 1;
311 	bc_num first, second, result;
312 	int scale = BCG(bc_precision);
313 
314 	ZEND_PARSE_PARAMETERS_START(2, 3)
315 		Z_PARAM_STR(left)
316 		Z_PARAM_STR(right)
317 		Z_PARAM_OPTIONAL
318 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
319 	ZEND_PARSE_PARAMETERS_END();
320 
321 	if (scale_param_is_null) {
322 		scale = BCG(bc_precision);
323 	} else if (scale_param < 0 || scale_param > INT_MAX) {
324 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
325 		RETURN_THROWS();
326 	} else {
327 		scale = (int) scale_param;
328 	}
329 
330 	bc_init_num(&first);
331 	bc_init_num(&second);
332 	bc_init_num(&result);
333 
334 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
335 		zend_argument_value_error(1, "is not well-formed");
336 		goto cleanup;
337 	}
338 
339 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
340 		zend_argument_value_error(2, "is not well-formed");
341 		goto cleanup;
342 	}
343 
344 	switch (bc_divide(first, second, &result, scale)) {
345 		case 0: /* OK */
346 			RETVAL_STR(bc_num2str_ex(result, scale));
347 			break;
348 		case -1: /* division by zero */
349 			zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
350 			break;
351 	}
352 
353 	cleanup: {
354 		bc_free_num(&first);
355 		bc_free_num(&second);
356 		bc_free_num(&result);
357 	};
358 }
359 /* }}} */
360 
361 /* {{{ Returns the modulus of the two arbitrary precision operands */
PHP_FUNCTION(bcmod)362 PHP_FUNCTION(bcmod)
363 {
364 	zend_string *left, *right;
365 	zend_long scale_param;
366 	zend_bool scale_param_is_null = 1;
367 	bc_num first, second, result;
368 	int scale = BCG(bc_precision);
369 
370 	ZEND_PARSE_PARAMETERS_START(2, 3)
371 		Z_PARAM_STR(left)
372 		Z_PARAM_STR(right)
373 		Z_PARAM_OPTIONAL
374 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
375 	ZEND_PARSE_PARAMETERS_END();
376 
377 	if (scale_param_is_null) {
378 		scale = BCG(bc_precision);
379 	} else if (scale_param < 0 || scale_param > INT_MAX) {
380 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
381 		RETURN_THROWS();
382 	} else {
383 		scale = (int) scale_param;
384 	}
385 
386 	bc_init_num(&first);
387 	bc_init_num(&second);
388 	bc_init_num(&result);
389 
390 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
391 		zend_argument_value_error(1, "is not well-formed");
392 		goto cleanup;
393 	}
394 
395 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
396 		zend_argument_value_error(2, "is not well-formed");
397 		goto cleanup;
398 	}
399 
400 	switch (bc_modulo(first, second, &result, scale)) {
401 		case 0:
402 			RETVAL_STR(bc_num2str_ex(result, scale));
403 			break;
404 		case -1:
405 			zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Modulo by zero");
406 			break;
407 	}
408 
409 	cleanup: {
410 		bc_free_num(&first);
411 		bc_free_num(&second);
412 		bc_free_num(&result);
413 	};
414 }
415 /* }}} */
416 
417 /* {{{ Returns the value of an arbitrary precision number raised to the power of another reduced by a modulus */
PHP_FUNCTION(bcpowmod)418 PHP_FUNCTION(bcpowmod)
419 {
420 	zend_string *left, *right, *modulus;
421 	zend_long scale_param;
422 	zend_bool scale_param_is_null = 1;
423 	bc_num first, second, mod, result;
424 	int scale = BCG(bc_precision);
425 
426 	ZEND_PARSE_PARAMETERS_START(3, 4)
427 		Z_PARAM_STR(left)
428 		Z_PARAM_STR(right)
429 		Z_PARAM_STR(modulus)
430 		Z_PARAM_OPTIONAL
431 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
432 	ZEND_PARSE_PARAMETERS_END();
433 
434 	if (scale_param_is_null) {
435 		scale = BCG(bc_precision);
436 	} else if (scale_param < 0 || scale_param > INT_MAX) {
437 		zend_argument_value_error(4, "must be between 0 and %d", INT_MAX);
438 		RETURN_THROWS();
439 	} else {
440 		scale = (int) scale_param;
441 	}
442 
443 	bc_init_num(&first);
444 	bc_init_num(&second);
445 	bc_init_num(&mod);
446 	bc_init_num(&result);
447 
448 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
449 		zend_argument_value_error(1, "is not well-formed");
450 		goto cleanup;
451 	}
452 
453 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
454 		zend_argument_value_error(2, "is not well-formed");
455 		goto cleanup;
456 	}
457 
458 	if (php_str2num(&mod, ZSTR_VAL(modulus)) == FAILURE) {
459 		zend_argument_value_error(3, "is not well-formed");
460 		goto cleanup;
461 	}
462 
463 	if (bc_raisemod(first, second, mod, &result, scale) == SUCCESS) {
464 		RETVAL_STR(bc_num2str_ex(result, scale));
465 	}
466 
467 	cleanup: {
468 		bc_free_num(&first);
469 		bc_free_num(&second);
470 		bc_free_num(&mod);
471 		bc_free_num(&result);
472 	};
473 }
474 /* }}} */
475 
476 /* {{{ Returns the value of an arbitrary precision number raised to the power of another */
PHP_FUNCTION(bcpow)477 PHP_FUNCTION(bcpow)
478 {
479 	zend_string *left, *right;
480 	zend_long scale_param;
481 	zend_bool scale_param_is_null = 1;
482 	bc_num first, second, result;
483 	int scale = BCG(bc_precision);
484 
485 	ZEND_PARSE_PARAMETERS_START(2, 3)
486 		Z_PARAM_STR(left)
487 		Z_PARAM_STR(right)
488 		Z_PARAM_OPTIONAL
489 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
490 	ZEND_PARSE_PARAMETERS_END();
491 
492 	if (scale_param_is_null) {
493 		scale = BCG(bc_precision);
494 	} else if (scale_param < 0 || scale_param > INT_MAX) {
495 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
496 		RETURN_THROWS();
497 	} else {
498 		scale = (int) scale_param;
499 	}
500 
501 	bc_init_num(&first);
502 	bc_init_num(&second);
503 	bc_init_num(&result);
504 
505 	if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
506 		zend_argument_value_error(1, "is not well-formed");
507 		goto cleanup;
508 	}
509 
510 	if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
511 		zend_argument_value_error(2, "is not well-formed");
512 		goto cleanup;
513 	}
514 
515 	bc_raise (first, second, &result, scale);
516 
517 	RETVAL_STR(bc_num2str_ex(result, scale));
518 
519 	cleanup: {
520 		bc_free_num(&first);
521 		bc_free_num(&second);
522 		bc_free_num(&result);
523 	};
524 }
525 /* }}} */
526 
527 /* {{{ Returns the square root of an arbitrary precision number */
PHP_FUNCTION(bcsqrt)528 PHP_FUNCTION(bcsqrt)
529 {
530 	zend_string *left;
531 	zend_long scale_param;
532 	zend_bool scale_param_is_null = 1;
533 	bc_num result;
534 	int scale = BCG(bc_precision);
535 
536 	ZEND_PARSE_PARAMETERS_START(1, 2)
537 		Z_PARAM_STR(left)
538 		Z_PARAM_OPTIONAL
539 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
540 	ZEND_PARSE_PARAMETERS_END();
541 
542 	if (scale_param_is_null) {
543 		scale = BCG(bc_precision);
544 	} else if (scale_param < 0 || scale_param > INT_MAX) {
545 		zend_argument_value_error(2, "must be between 0 and %d", INT_MAX);
546 		RETURN_THROWS();
547 	} else {
548 		scale = (int) scale_param;
549 	}
550 
551 	bc_init_num(&result);
552 
553 	if (php_str2num(&result, ZSTR_VAL(left)) == FAILURE) {
554 		zend_argument_value_error(1, "is not well-formed");
555 		goto cleanup;
556 	}
557 
558 	if (bc_sqrt (&result, scale) != 0) {
559 		RETVAL_STR(bc_num2str_ex(result, scale));
560 	} else {
561 		zend_argument_value_error(1, "must be greater than or equal to 0");
562 	}
563 
564 	cleanup: {
565 		bc_free_num(&result);
566 	};
567 }
568 /* }}} */
569 
570 /* {{{ Compares two arbitrary precision numbers */
PHP_FUNCTION(bccomp)571 PHP_FUNCTION(bccomp)
572 {
573 	zend_string *left, *right;
574 	zend_long scale_param;
575 	zend_bool scale_param_is_null = 1;
576 	bc_num first, second;
577 	int scale = BCG(bc_precision);
578 
579 	ZEND_PARSE_PARAMETERS_START(2, 3)
580 		Z_PARAM_STR(left)
581 		Z_PARAM_STR(right)
582 		Z_PARAM_OPTIONAL
583 		Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
584 	ZEND_PARSE_PARAMETERS_END();
585 
586 	if (scale_param_is_null) {
587 		scale = BCG(bc_precision);
588 	} else if (scale_param < 0 || scale_param > INT_MAX) {
589 		zend_argument_value_error(3, "must be between 0 and %d", INT_MAX);
590 		RETURN_THROWS();
591 	} else {
592 		scale = (int) scale_param;
593 	}
594 
595 	bc_init_num(&first);
596 	bc_init_num(&second);
597 
598 	if (!bc_str2num(&first, ZSTR_VAL(left), scale)) {
599 		zend_argument_value_error(1, "is not well-formed");
600 		goto cleanup;
601 	}
602 
603 	if (!bc_str2num(&second, ZSTR_VAL(right), scale)) {
604 		zend_argument_value_error(2, "is not well-formed");
605 		goto cleanup;
606 	}
607 
608 	RETVAL_LONG(bc_compare(first, second));
609 
610 	cleanup: {
611 		bc_free_num(&first);
612 		bc_free_num(&second);
613 	};
614 }
615 /* }}} */
616 
617 /* {{{ Sets default scale parameter for all bc math functions */
PHP_FUNCTION(bcscale)618 PHP_FUNCTION(bcscale)
619 {
620 	zend_long old_scale, new_scale;
621 	zend_bool new_scale_is_null = 1;
622 
623 	ZEND_PARSE_PARAMETERS_START(0, 1)
624 		Z_PARAM_OPTIONAL
625 		Z_PARAM_LONG_OR_NULL(new_scale, new_scale_is_null)
626 	ZEND_PARSE_PARAMETERS_END();
627 
628 	old_scale = BCG(bc_precision);
629 
630 	if (!new_scale_is_null) {
631 		if (new_scale < 0 || new_scale > INT_MAX) {
632 			zend_argument_value_error(1, "must be between 0 and %d", INT_MAX);
633 			RETURN_THROWS();
634 		}
635 
636 		zend_string *ini_name = zend_string_init("bcmath.scale", sizeof("bcmath.scale") - 1, 0);
637 		zend_string *new_scale_str = zend_long_to_str(new_scale);
638 		zend_alter_ini_entry(ini_name, new_scale_str, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
639 		zend_string_release(new_scale_str);
640 		zend_string_release(ini_name);
641 	}
642 
643 	RETURN_LONG(old_scale);
644 }
645 /* }}} */
646 
647 
648 #endif
649