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