Coverage Report

Created: 2026-09-14 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/math.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Jim Winstead <jimw@php.net>                                 |
12
   |          Stig Sæther Bakken <ssb@php.net>                            |
13
   |          Zeev Suraski <zeev@php.net>                                 |
14
   | PHP 4.0 patches by Thies C. Arntzen <thies@thieso.net>               |
15
   +----------------------------------------------------------------------+
16
*/
17
18
#include "php.h"
19
#include "php_math.h"
20
#include "zend_bitset.h"
21
#include "zend_enum.h"
22
#include "zend_exceptions.h"
23
#include "zend_multiply.h"
24
#include "zend_portability.h"
25
#include "zend_strtod.h"
26
27
#include <float.h>
28
#include <math.h>
29
#include <stdlib.h>
30
31
#include "basic_functions.h"
32
33
PHPAPI zend_class_entry *rounding_mode_ce;
34
35
/* {{{ php_intpow10
36
       Returns pow(10.0, (double)power), uses fast lookup table for exact powers */
37
6
static inline double php_intpow10(int power) {
38
  /* Not in lookup table */
39
6
  if (power < 0 || power > 22) {
40
0
    return pow(10.0, (double)power);
41
0
  }
42
43
6
  static const double powers[] = {
44
6
      1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
45
6
      1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
46
47
6
  return powers[power];
48
6
}
49
/* }}} */
50
51
static zend_always_inline double php_round_get_basic_edge_case(double integral, double exponent, int places)
52
6
{
53
6
  return (places > 0)
54
6
    ? fabs((integral + copysign(0.5, integral)) / exponent)
55
6
    : fabs((integral + copysign(0.5, integral)) * exponent);
56
6
}
57
58
static zend_always_inline double php_round_get_zero_edge_case(double integral, double exponent, int places)
59
0
{
60
0
  return (places > 0)
61
0
    ? fabs((integral) / exponent)
62
0
    : fabs((integral) * exponent);
63
0
}
64
65
/* {{{ php_round_helper
66
     Actually performs the rounding of a value to integer in a certain mode */
67
6
static inline double php_round_helper(double integral, double value, double exponent, int places, int mode) {
68
6
  double value_abs = fabs(value);
69
6
  double edge_case;
70
71
6
  switch (mode) {
72
6
    case PHP_ROUND_HALF_UP:
73
6
      edge_case = php_round_get_basic_edge_case(integral, exponent, places);
74
6
      if (value_abs >= edge_case) {
75
        /* We must increase the magnitude of the integral part
76
         * (rounding up / towards infinity). copysign(1.0, integral)
77
         * will either result in 1.0 or -1.0 depending on the sign
78
         * of the input, thus increasing the magnitude, but without
79
         * generating branches in the assembly.
80
         *
81
         * This pattern is equally used for all the other modes.
82
         */
83
3
        return integral + copysign(1.0, integral);
84
3
      }
85
86
3
      return integral;
87
88
0
    case PHP_ROUND_HALF_DOWN:
89
0
      edge_case = php_round_get_basic_edge_case(integral, exponent, places);
90
0
      if (value_abs > edge_case) {
91
0
        return integral + copysign(1.0, integral);
92
0
      }
93
94
0
      return integral;
95
96
0
    case PHP_ROUND_CEILING:
97
0
      edge_case = php_round_get_zero_edge_case(integral, exponent, places);
98
0
      if (value > 0.0 && value_abs > edge_case) {
99
0
        return integral + 1.0;
100
0
      }
101
102
0
      return integral;
103
104
0
    case PHP_ROUND_FLOOR:
105
0
      edge_case = php_round_get_zero_edge_case(integral, exponent, places);
106
0
      if (value < 0.0 && value_abs > edge_case) {
107
0
        return integral - 1.0;
108
0
      }
109
110
0
      return integral;
111
112
0
    case PHP_ROUND_TOWARD_ZERO:
113
0
      return integral;
114
115
0
    case PHP_ROUND_AWAY_FROM_ZERO:
116
0
      edge_case = php_round_get_zero_edge_case(integral, exponent, places);
117
0
      if (value_abs > edge_case) {
118
0
        return integral + copysign(1.0, integral);
119
0
      }
120
121
0
      return integral;
122
123
0
    case PHP_ROUND_HALF_EVEN:
124
0
      edge_case = php_round_get_basic_edge_case(integral, exponent, places);
125
0
      if (value_abs > edge_case) {
126
0
        return integral + copysign(1.0, integral);
127
0
      } else if (UNEXPECTED(value_abs == edge_case)) {
128
0
        bool even = !fmod(integral, 2.0);
129
130
        /* If the integral part is not even we can make it even
131
         * by adding one in the direction of the existing sign.
132
         */
133
0
        if (!even) {
134
0
          return integral + copysign(1.0, integral);
135
0
        }
136
0
      }
137
138
0
      return integral;
139
140
0
    case PHP_ROUND_HALF_ODD:
141
0
      edge_case = php_round_get_basic_edge_case(integral, exponent, places);
142
0
      if (value_abs > edge_case) {
143
0
        return integral + copysign(1.0, integral);
144
0
      } else if (UNEXPECTED(value_abs == edge_case)) {
145
0
        bool even = !fmod(integral, 2.0);
146
147
0
        if (even) {
148
0
          return integral + copysign(1.0, integral);
149
0
        }
150
0
      }
151
152
0
      return integral;
153
154
0
    default: ZEND_UNREACHABLE();
155
6
  }
156
  // FIXME: GCC bug, branch is considered reachable.
157
0
  ZEND_UNREACHABLE();
158
0
}
159
/* }}} */
160
161
/* {{{ _php_math_round */
162
/*
163
 * Rounds a number to a certain number of decimal places in a certain rounding
164
 * mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding
165
 */
166
6
PHPAPI double _php_math_round(double value, int places, int mode) {
167
6
  double exponent, tmp_value, tmp_value2;
168
169
6
  if (!zend_finite(value) || value == 0.0) {
170
0
    return value;
171
0
  }
172
173
6
  if (places == 0 && value == trunc(value)) {
174
0
        return value;
175
0
    }
176
177
6
  places = places < INT_MIN+1 ? INT_MIN+1 : places;
178
179
6
  exponent = php_intpow10(abs(places));
180
181
  /**
182
   * When extracting the integer part, the result may be incorrect as a decimal
183
   * number due to floating point errors.
184
   * e.g.
185
   * 0.285 * 10000000000 => 2849999999.9999995
186
   * floor(0.285 * 10000000000) => 2849999999
187
   *
188
   * Add 1 to the absolute value of the value adjusted by floor or ceil, use the
189
   * exponent to return it to its original precision, and compare it with value.
190
   * If it is equal to value, it is assumed that the absolute value is 1 smaller
191
   * due to error and will be corrected.
192
   * e.g.
193
   * 0.285 * 10000000000 => 2849999999.9999995
194
   * floor(0.285 * 10000000000) => 2849999999 (tmp_value)
195
   * tmp_value2 = 2849999999 + 1 => 2850000000
196
   * 2850000000 / 10000000000 == 0.285 => true
197
   * tmp_value = tmp_value2
198
   */
199
200
6
  if (value >= 0.0) {
201
6
    tmp_value = floor(places > 0 ? value * exponent : value / exponent);
202
6
    tmp_value2 = tmp_value + 1.0;
203
6
  } else {
204
0
    tmp_value = ceil(places > 0 ? value * exponent : value / exponent);
205
0
    tmp_value2 = tmp_value - 1.0;
206
0
  }
207
208
6
  if ((places > 0 ? tmp_value2 / exponent : tmp_value2 * exponent) == value) {
209
0
    tmp_value = tmp_value2;
210
0
  }
211
212
  /* This value is beyond our precision, so rounding it is pointless */
213
6
  if (fabs(tmp_value) >= 1e16) {
214
0
    return value;
215
0
  }
216
217
  /* round the temp value */
218
6
  tmp_value = php_round_helper(tmp_value, value, exponent, places, mode);
219
220
  /* see if it makes sense to use simple division to round the value */
221
6
  if (abs(places) < 23) {
222
6
    if (places > 0) {
223
0
      tmp_value = tmp_value / exponent;
224
6
    } else {
225
6
      tmp_value = tmp_value * exponent;
226
6
    }
227
6
  } else {
228
    /* Simple division can't be used since that will cause wrong results.
229
       Instead, the number is converted to a string and back again using
230
       strtod(). strtod() will return the nearest possible FP value for
231
       that string. */
232
233
    /* 40 Bytes should be more than enough for this format string. The
234
       float won't be larger than 1e15 anyway. But just in case, use
235
       snprintf() and make sure the buffer is zero-terminated */
236
0
    char buf[40];
237
0
    snprintf(buf, 39, "%15fe%d", tmp_value, -places);
238
0
    buf[39] = '\0';
239
0
    tmp_value = zend_strtod(buf, NULL);
240
    /* couldn't convert to string and back */
241
0
    if (!zend_finite(tmp_value) || zend_isnan(tmp_value)) {
242
0
      tmp_value = value;
243
0
    }
244
0
  }
245
6
  return tmp_value;
246
6
}
247
/* }}} */
248
249
/* {{{ Return the absolute value of the number */
250
PHP_FUNCTION(abs)
251
0
{
252
0
  zval *value;
253
254
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
255
0
    Z_PARAM_NUMBER(value)
256
0
  ZEND_PARSE_PARAMETERS_END();
257
258
0
  switch (Z_TYPE_P(value)) {
259
0
    case IS_LONG:
260
0
      if (UNEXPECTED(Z_LVAL_P(value) == ZEND_LONG_MIN)) {
261
0
        RETURN_DOUBLE(-(double)ZEND_LONG_MIN);
262
0
      } else {
263
0
        RETURN_LONG(Z_LVAL_P(value) < 0 ? -Z_LVAL_P(value) : Z_LVAL_P(value));
264
0
      }
265
0
    case IS_DOUBLE:
266
0
      RETURN_DOUBLE(fabs(Z_DVAL_P(value)));
267
0
    default: ZEND_UNREACHABLE();
268
0
  }
269
0
}
270
/* }}} */
271
272
/* {{{ Returns the next highest integer value of the number */
273
PHP_FUNCTION(ceil)
274
0
{
275
0
  zval *value;
276
277
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
278
0
    Z_PARAM_NUMBER(value)
279
0
  ZEND_PARSE_PARAMETERS_END();
280
281
0
  switch (Z_TYPE_P(value)) {
282
0
    case IS_LONG:
283
0
      RETURN_DOUBLE(zval_get_double(value));
284
0
    case IS_DOUBLE:
285
0
      RETURN_DOUBLE(ceil(Z_DVAL_P(value)));
286
0
    default: ZEND_UNREACHABLE();
287
0
  }
288
0
}
289
/* }}} */
290
291
/* {{{ Returns the next lowest integer value from the number */
292
PHP_FUNCTION(floor)
293
7
{
294
7
  zval *value;
295
296
21
  ZEND_PARSE_PARAMETERS_START(1, 1)
297
28
    Z_PARAM_NUMBER(value)
298
7
  ZEND_PARSE_PARAMETERS_END();
299
300
7
  switch (Z_TYPE_P(value)) {
301
7
    case IS_LONG:
302
7
      RETURN_DOUBLE(zval_get_double(value));
303
0
    case IS_DOUBLE:
304
0
      RETURN_DOUBLE(floor(Z_DVAL_P(value)));
305
0
    default: ZEND_UNREACHABLE();
306
7
  }
307
7
}
308
/* }}} */
309
310
PHPAPI int php_math_round_mode_from_enum(zend_enum_RoundingMode mode)
311
0
{
312
0
  switch (mode) {
313
0
    case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
314
0
      return PHP_ROUND_HALF_UP;
315
0
    case ZEND_ENUM_RoundingMode_HalfTowardsZero:
316
0
      return PHP_ROUND_HALF_DOWN;
317
0
    case ZEND_ENUM_RoundingMode_HalfEven:
318
0
      return PHP_ROUND_HALF_EVEN;
319
0
    case ZEND_ENUM_RoundingMode_HalfOdd:
320
0
      return PHP_ROUND_HALF_ODD;
321
0
    case ZEND_ENUM_RoundingMode_TowardsZero:
322
0
      return PHP_ROUND_TOWARD_ZERO;
323
0
    case ZEND_ENUM_RoundingMode_AwayFromZero:
324
0
      return PHP_ROUND_AWAY_FROM_ZERO;
325
0
    case ZEND_ENUM_RoundingMode_NegativeInfinity:
326
0
      return PHP_ROUND_FLOOR;
327
0
    case ZEND_ENUM_RoundingMode_PositiveInfinity:
328
0
      return PHP_ROUND_CEILING;
329
0
  }
330
331
0
  ZEND_UNREACHABLE();
332
0
}
333
334
/* {{{ Returns the number rounded to specified precision */
335
PHP_FUNCTION(round)
336
0
{
337
0
  zval *value;
338
0
  int places = 0;
339
0
  zend_long precision = 0;
340
0
  zend_long mode = PHP_ROUND_HALF_UP;
341
0
  zend_object *mode_object = NULL;
342
343
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
344
0
    Z_PARAM_NUMBER(value)
345
0
    Z_PARAM_OPTIONAL
346
0
    Z_PARAM_LONG(precision)
347
0
    Z_PARAM_OBJ_OF_CLASS_OR_LONG(mode_object, rounding_mode_ce, mode)
348
0
  ZEND_PARSE_PARAMETERS_END();
349
350
0
  if (ZEND_NUM_ARGS() >= 2) {
351
0
    if (precision >= 0) {
352
0
      places = ZEND_LONG_INT_OVFL(precision) ? INT_MAX : (int)precision;
353
0
    } else {
354
0
      places = ZEND_LONG_INT_UDFL(precision) ? INT_MIN : (int)precision;
355
0
    }
356
0
  }
357
358
0
  if (mode_object != NULL) {
359
0
    mode = php_math_round_mode_from_enum(zend_enum_fetch_case_id(mode_object));
360
0
  }
361
362
0
  switch (mode) {
363
0
    case PHP_ROUND_HALF_UP:
364
0
    case PHP_ROUND_HALF_DOWN:
365
0
    case PHP_ROUND_HALF_EVEN:
366
0
    case PHP_ROUND_HALF_ODD:
367
0
    case PHP_ROUND_AWAY_FROM_ZERO:
368
0
    case PHP_ROUND_TOWARD_ZERO:
369
0
    case PHP_ROUND_CEILING:
370
0
    case PHP_ROUND_FLOOR:
371
0
      break;
372
0
    default:
373
0
      zend_argument_value_error(3, "must be a valid rounding mode (RoundingMode::*)");
374
0
      RETURN_THROWS();
375
0
  }
376
377
0
  switch (Z_TYPE_P(value)) {
378
0
    case IS_LONG:
379
      /* Simple case - long that doesn't need to be rounded. */
380
0
      if (places >= 0) {
381
0
        RETURN_DOUBLE(zval_get_double(value));
382
0
      }
383
0
      ZEND_FALLTHROUGH;
384
385
0
    case IS_DOUBLE:
386
0
      RETURN_DOUBLE(_php_math_round(zval_get_double(value), (int)places, (int)mode));
387
388
0
    default: ZEND_UNREACHABLE();
389
0
  }
390
0
}
391
/* }}} */
392
393
/* Return the given value if in range of min and max */
394
static void php_math_clamp(zval *return_value, zval *value, zval *min, zval *max)
395
0
{
396
0
  if (Z_TYPE_P(min) == IS_DOUBLE && UNEXPECTED(zend_isnan(Z_DVAL_P(min)))) {
397
0
    zend_argument_value_error(2, "must not be NAN");
398
0
    RETURN_THROWS();
399
0
  }
400
401
0
  if (Z_TYPE_P(max) == IS_DOUBLE && UNEXPECTED(zend_isnan(Z_DVAL_P(max)))) {
402
0
    zend_argument_value_error(3, "must not be NAN");
403
0
    RETURN_THROWS();
404
0
  }
405
406
0
  if (zend_compare(max, min) == -1) {
407
0
    zend_argument_value_error(2, "must be smaller than or equal to argument #3 ($max)");
408
0
    RETURN_THROWS();
409
0
  }
410
411
0
  if (zend_compare(max, value) == -1) {
412
0
    RETURN_COPY(max);
413
0
  }
414
415
0
  if (zend_compare(value, min) == -1) {
416
0
    RETURN_COPY(min);
417
0
  }
418
419
0
  RETURN_COPY(value);
420
0
}
421
422
/* {{{ Return the given value if in range of min and max */
423
PHP_FUNCTION(clamp)
424
1
{
425
1
  zval *zvalue, *zmin, *zmax;
426
427
2
  ZEND_PARSE_PARAMETERS_START(3, 3)
428
2
    Z_PARAM_ZVAL(zvalue)
429
0
    Z_PARAM_ZVAL(zmin)
430
0
    Z_PARAM_ZVAL(zmax)
431
1
  ZEND_PARSE_PARAMETERS_END();
432
433
0
  php_math_clamp(return_value, zvalue, zmin, zmax);
434
0
}
435
/* }}} */
436
437
/* {{{ Return the given value if in range of min and max */
438
ZEND_FRAMELESS_FUNCTION(clamp, 3)
439
0
{
440
0
  zval *zvalue, *zmin, *zmax;
441
0
  Z_FLF_PARAM_ZVAL(1, zvalue);
442
0
  Z_FLF_PARAM_ZVAL(2, zmin);
443
0
  Z_FLF_PARAM_ZVAL(3, zmax);
444
445
0
  php_math_clamp(return_value, zvalue, zmin, zmax);
446
0
}
447
/* }}} */
448
449
/* {{{ Returns the sine of the number in radians */
450
PHP_FUNCTION(sin)
451
20
{
452
20
  double num;
453
454
60
  ZEND_PARSE_PARAMETERS_START(1, 1)
455
80
    Z_PARAM_DOUBLE(num)
456
20
  ZEND_PARSE_PARAMETERS_END();
457
20
  RETURN_DOUBLE(sin(num));
458
20
}
459
/* }}} */
460
461
/* {{{ Returns the cosine of the number in radians */
462
PHP_FUNCTION(cos)
463
19
{
464
19
  double num;
465
466
57
  ZEND_PARSE_PARAMETERS_START(1, 1)
467
76
    Z_PARAM_DOUBLE(num)
468
19
  ZEND_PARSE_PARAMETERS_END();
469
19
  RETURN_DOUBLE(cos(num));
470
19
}
471
/* }}} */
472
473
/* {{{ Returns the tangent of the number in radians */
474
PHP_FUNCTION(tan)
475
12
{
476
12
  double num;
477
478
36
  ZEND_PARSE_PARAMETERS_START(1, 1)
479
48
    Z_PARAM_DOUBLE(num)
480
12
  ZEND_PARSE_PARAMETERS_END();
481
12
  RETURN_DOUBLE(tan(num));
482
12
}
483
/* }}} */
484
485
/* {{{ Returns the arc sine of the number in radians */
486
PHP_FUNCTION(asin)
487
0
{
488
0
  double num;
489
490
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
491
0
    Z_PARAM_DOUBLE(num)
492
0
  ZEND_PARSE_PARAMETERS_END();
493
0
  RETURN_DOUBLE(asin(num));
494
0
}
495
/* }}} */
496
497
/* {{{ Return the arc cosine of the number in radians */
498
PHP_FUNCTION(acos)
499
0
{
500
0
  double num;
501
502
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
503
0
    Z_PARAM_DOUBLE(num)
504
0
  ZEND_PARSE_PARAMETERS_END();
505
0
  RETURN_DOUBLE(acos(num));
506
0
}
507
/* }}} */
508
509
/* {{{ Returns the arc tangent of the number in radians */
510
PHP_FUNCTION(atan)
511
0
{
512
0
  double num;
513
514
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
515
0
    Z_PARAM_DOUBLE(num)
516
0
  ZEND_PARSE_PARAMETERS_END();
517
0
  RETURN_DOUBLE(atan(num));
518
0
}
519
/* }}} */
520
521
/* {{{ Returns the arc tangent of y/x, with the resulting quadrant determined by the signs of y and x */
522
PHP_FUNCTION(atan2)
523
0
{
524
0
  double num1, num2;
525
526
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
527
0
    Z_PARAM_DOUBLE(num1)
528
0
    Z_PARAM_DOUBLE(num2)
529
0
  ZEND_PARSE_PARAMETERS_END();
530
0
  RETURN_DOUBLE(atan2(num1, num2));
531
0
}
532
/* }}} */
533
534
/* {{{ Returns the hyperbolic sine of the number, defined as (exp(number) - exp(-number))/2 */
535
PHP_FUNCTION(sinh)
536
0
{
537
0
  double num;
538
539
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
540
0
    Z_PARAM_DOUBLE(num)
541
0
  ZEND_PARSE_PARAMETERS_END();
542
0
  RETURN_DOUBLE(sinh(num));
543
0
}
544
/* }}} */
545
546
/* {{{ Returns the hyperbolic cosine of the number, defined as (exp(number) + exp(-number))/2 */
547
PHP_FUNCTION(cosh)
548
0
{
549
0
  double num;
550
551
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
552
0
    Z_PARAM_DOUBLE(num)
553
0
  ZEND_PARSE_PARAMETERS_END();
554
0
  RETURN_DOUBLE(cosh(num));
555
0
}
556
/* }}} */
557
558
/* {{{ Returns the hyperbolic tangent of the number, defined as sinh(number)/cosh(number) */
559
PHP_FUNCTION(tanh)
560
0
{
561
0
  double num;
562
563
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
564
0
    Z_PARAM_DOUBLE(num)
565
0
  ZEND_PARSE_PARAMETERS_END();
566
0
  RETURN_DOUBLE(tanh(num));
567
0
}
568
/* }}} */
569
570
/* {{{ Returns the inverse hyperbolic sine of the number, i.e. the value whose hyperbolic sine is number */
571
PHP_FUNCTION(asinh)
572
0
{
573
0
  double num;
574
575
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
576
0
    Z_PARAM_DOUBLE(num)
577
0
  ZEND_PARSE_PARAMETERS_END();
578
0
  RETURN_DOUBLE(asinh(num));
579
0
}
580
/* }}} */
581
582
/* {{{ Returns the inverse hyperbolic cosine of the number, i.e. the value whose hyperbolic cosine is number */
583
PHP_FUNCTION(acosh)
584
0
{
585
0
  double num;
586
587
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
588
0
    Z_PARAM_DOUBLE(num)
589
0
  ZEND_PARSE_PARAMETERS_END();
590
0
  RETURN_DOUBLE(acosh(num));
591
0
}
592
/* }}} */
593
594
/* {{{ Returns the inverse hyperbolic tangent of the number, i.e. the value whose hyperbolic tangent is number */
595
PHP_FUNCTION(atanh)
596
0
{
597
0
  double num;
598
599
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
600
0
    Z_PARAM_DOUBLE(num)
601
0
  ZEND_PARSE_PARAMETERS_END();
602
0
  RETURN_DOUBLE(atanh(num));
603
0
}
604
/* }}} */
605
606
/* {{{ Returns an approximation of pi */
607
PHP_FUNCTION(pi)
608
0
{
609
0
  ZEND_PARSE_PARAMETERS_NONE();
610
611
0
  RETURN_DOUBLE(M_PI);
612
0
}
613
/* }}} */
614
615
/* {{{ Returns whether argument is finite */
616
PHP_FUNCTION(is_finite)
617
7
{
618
7
  double dval;
619
620
21
  ZEND_PARSE_PARAMETERS_START(1, 1)
621
28
    Z_PARAM_DOUBLE(dval)
622
7
  ZEND_PARSE_PARAMETERS_END();
623
7
  RETURN_BOOL(zend_finite(dval));
624
7
}
625
/* }}} */
626
627
/* {{{ Returns whether argument is infinite */
628
PHP_FUNCTION(is_infinite)
629
0
{
630
0
  double dval;
631
632
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
633
0
    Z_PARAM_DOUBLE(dval)
634
0
  ZEND_PARSE_PARAMETERS_END();
635
0
  RETURN_BOOL(zend_isinf(dval));
636
0
}
637
/* }}} */
638
639
/* {{{ Returns whether argument is not a number */
640
PHP_FUNCTION(is_nan)
641
183
{
642
183
  double dval;
643
644
549
  ZEND_PARSE_PARAMETERS_START(1, 1)
645
732
    Z_PARAM_DOUBLE(dval)
646
183
  ZEND_PARSE_PARAMETERS_END();
647
183
  RETURN_BOOL(zend_isnan(dval));
648
183
}
649
/* }}} */
650
651
/* {{{ Returns base raised to the power of exponent. Returns integer result when possible */
652
PHP_FUNCTION(pow)
653
0
{
654
0
  zval *zbase, *zexp;
655
656
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
657
0
    Z_PARAM_ZVAL(zbase)
658
0
    Z_PARAM_ZVAL(zexp)
659
0
  ZEND_PARSE_PARAMETERS_END();
660
661
0
  pow_function(return_value, zbase, zexp);
662
0
}
663
/* }}} */
664
665
/* {{{ Returns e raised to the power of the number */
666
PHP_FUNCTION(exp)
667
0
{
668
0
  double num;
669
670
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
671
0
    Z_PARAM_DOUBLE(num)
672
0
  ZEND_PARSE_PARAMETERS_END();
673
674
0
  RETURN_DOUBLE(exp(num));
675
0
}
676
/* }}} */
677
678
/* {{{ Returns exp(number) - 1, computed in a way that accurate even when the value of number is close to zero */
679
PHP_FUNCTION(expm1)
680
0
{
681
0
  double num;
682
683
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
684
0
    Z_PARAM_DOUBLE(num)
685
0
  ZEND_PARSE_PARAMETERS_END();
686
687
0
  RETURN_DOUBLE(expm1(num));
688
0
}
689
/* }}} */
690
691
/* {{{ Returns log(1 + number), computed in a way that accurate even when the value of number is close to zero */
692
PHP_FUNCTION(log1p)
693
0
{
694
0
  double num;
695
696
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
697
0
    Z_PARAM_DOUBLE(num)
698
0
  ZEND_PARSE_PARAMETERS_END();
699
700
0
  RETURN_DOUBLE(log1p(num));
701
0
}
702
/* }}} */
703
704
/* {{{ Returns the natural logarithm of the number, or the base log if base is specified */
705
PHP_FUNCTION(log)
706
22
{
707
22
  double num, base = 0;
708
709
65
  ZEND_PARSE_PARAMETERS_START(1, 2)
710
84
    Z_PARAM_DOUBLE(num)
711
20
    Z_PARAM_OPTIONAL
712
48
    Z_PARAM_DOUBLE(base)
713
22
  ZEND_PARSE_PARAMETERS_END();
714
715
20
  if (ZEND_NUM_ARGS() == 1) {
716
16
    RETURN_DOUBLE(log(num));
717
16
  }
718
719
4
  if (base == 2.0) {
720
0
    RETURN_DOUBLE(log2(num));
721
0
  }
722
723
4
  if (base == 10.0) {
724
0
    RETURN_DOUBLE(log10(num));
725
0
  }
726
727
4
  if (base == 1.0) {
728
1
    RETURN_DOUBLE(ZEND_NAN);
729
1
  }
730
731
3
  if (base <= 0.0) {
732
2
    zend_argument_value_error(2, "must be greater than 0");
733
2
    RETURN_THROWS();
734
2
  }
735
736
1
  RETURN_DOUBLE(log(num) / log(base));
737
1
}
738
/* }}} */
739
740
/* {{{ Returns the base-10 logarithm of the number */
741
PHP_FUNCTION(log10)
742
28
{
743
28
  double num;
744
745
84
  ZEND_PARSE_PARAMETERS_START(1, 1)
746
112
    Z_PARAM_DOUBLE(num)
747
28
  ZEND_PARSE_PARAMETERS_END();
748
749
28
  RETURN_DOUBLE(log10(num));
750
28
}
751
/* }}} */
752
753
/* {{{ Returns the square root of the number */
754
PHP_FUNCTION(sqrt)
755
22
{
756
22
  double num;
757
758
65
  ZEND_PARSE_PARAMETERS_START(1, 1)
759
84
    Z_PARAM_DOUBLE(num)
760
22
  ZEND_PARSE_PARAMETERS_END();
761
762
21
  RETURN_DOUBLE(sqrt(num));
763
21
}
764
/* }}} */
765
766
/* {{{ Returns sqrt(num1*num1 + num2*num2) */
767
PHP_FUNCTION(hypot)
768
0
{
769
0
  double num1, num2;
770
771
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
772
0
    Z_PARAM_DOUBLE(num1)
773
0
    Z_PARAM_DOUBLE(num2)
774
0
  ZEND_PARSE_PARAMETERS_END();
775
776
0
  RETURN_DOUBLE(hypot(num1, num2));
777
0
}
778
/* }}} */
779
780
/* {{{ Converts the number in degrees to the radian equivalent */
781
PHP_FUNCTION(deg2rad)
782
281
{
783
281
  double deg;
784
785
843
  ZEND_PARSE_PARAMETERS_START(1, 1)
786
1.12k
    Z_PARAM_DOUBLE(deg)
787
281
  ZEND_PARSE_PARAMETERS_END();
788
281
  RETURN_DOUBLE((deg / 180.0) * M_PI);
789
281
}
790
/* }}} */
791
792
/* {{{ Converts the radian number to the equivalent number in degrees */
793
PHP_FUNCTION(rad2deg)
794
0
{
795
0
  double rad;
796
797
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
798
0
    Z_PARAM_DOUBLE(rad)
799
0
  ZEND_PARSE_PARAMETERS_END();
800
801
0
  RETURN_DOUBLE((rad / M_PI) * 180);
802
0
}
803
/* }}} */
804
805
/* {{{ _php_math_basetolong */
806
/*
807
 * Convert a string representation of a base(2-36) number to a long.
808
 */
809
PHPAPI zend_long _php_math_basetolong(zval *arg, int base)
810
0
{
811
0
  zend_long num = 0, digit, onum;
812
0
  zend_long i;
813
0
  char c, *s;
814
815
0
  if (Z_TYPE_P(arg) != IS_STRING || base < 2 || base > 36) {
816
0
    return 0;
817
0
  }
818
819
0
  s = Z_STRVAL_P(arg);
820
821
0
  for (i = Z_STRLEN_P(arg); i > 0; i--) {
822
0
    c = *s++;
823
824
0
    digit = (c >= '0' && c <= '9') ? c - '0'
825
0
      : (c >= 'A' && c <= 'Z') ? c - 'A' + 10
826
0
      : (c >= 'a' && c <= 'z') ? c - 'a' + 10
827
0
      : base;
828
829
0
    if (digit >= base) {
830
0
      continue;
831
0
    }
832
833
0
    onum = num;
834
0
    num = num * base + digit;
835
0
    if (num > onum)
836
0
      continue;
837
838
0
    {
839
840
0
      php_error_docref(NULL, E_WARNING, "Number %s is too big to fit in long", s);
841
0
      return ZEND_LONG_MAX;
842
0
    }
843
0
  }
844
845
0
  return num;
846
0
}
847
/* }}} */
848
849
/* {{{ _php_math_basetozval */
850
/*
851
 * Convert a string representation of a base(2-36) number to a zval.
852
 */
853
PHPAPI void _php_math_basetozval(zend_string *str, int base, zval *ret)
854
30
{
855
30
  zend_long num = 0;
856
30
  double fnum = 0;
857
30
  int mode = 0;
858
30
  char c, *s, *e;
859
30
  zend_long cutoff;
860
30
  int cutlim;
861
30
  int invalidchars = 0;
862
863
30
  s = ZSTR_VAL(str);
864
30
  e = s + ZSTR_LEN(str);
865
866
  /* Skip leading whitespace */
867
30
  while (s < e && isspace((unsigned char)*s)) s++;
868
  /* Skip trailing whitespace */
869
30
  while (s < e && isspace((unsigned char)e[-1])) e--;
870
871
30
  if (e - s >= 2) {
872
26
    if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2;
873
26
    if (base == 8 && s[0] == '0' && (s[1] == 'o' || s[1] == 'O')) s += 2;
874
26
    if (base == 2 && s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) s += 2;
875
26
  }
876
877
30
  cutoff = ZEND_LONG_MAX / base;
878
30
  cutlim = ZEND_LONG_MAX % base;
879
880
1.60k
  while (s < e) {
881
1.57k
    c = *s++;
882
883
    /* might not work for EBCDIC */
884
1.57k
    if (c >= '0' && c <= '9')
885
1.56k
      c -= '0';
886
10
    else if (c >= 'A' && c <= 'Z')
887
0
      c -= 'A' - 10;
888
10
    else if (c >= 'a' && c <= 'z')
889
10
      c -= 'a' - 10;
890
0
    else {
891
0
      invalidchars++;
892
0
      continue;
893
0
    }
894
895
1.57k
    if (c >= base) {
896
10
      invalidchars++;
897
10
      continue;
898
10
    }
899
900
1.56k
    switch (mode) {
901
1.55k
    case 0: /* Integer */
902
1.55k
      if (num < cutoff || (num == cutoff && c <= cutlim)) {
903
1.53k
        num = num * base + c;
904
1.53k
        break;
905
1.53k
      } else {
906
14
        zend_error(E_NOTICE, "Input number is larger than PHP_INT_MAX, precision has been lost in conversion");
907
14
        fnum = (double)num;
908
14
        mode = 1;
909
14
      }
910
14
      ZEND_FALLTHROUGH;
911
28
    case 1: /* Float */
912
28
      fnum = fnum * base + c;
913
1.56k
    }
914
1.56k
  }
915
916
30
  if (invalidchars > 0) {
917
2
    zend_error(E_DEPRECATED, "Invalid characters passed for attempted conversion, these have been ignored");
918
2
  }
919
920
30
  if (mode == 1) {
921
14
    ZVAL_DOUBLE(ret, fnum);
922
16
  } else {
923
16
    ZVAL_LONG(ret, num);
924
16
  }
925
30
}
926
/* }}} */
927
928
/* {{{ _php_math_longtobase */
929
/*
930
 * Convert a long to a string containing a base(2-36) representation of
931
 * the number.
932
 */
933
PHPAPI zend_string * _php_math_longtobase(zend_long arg, int base)
934
16
{
935
16
  static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
936
16
  char buf[(sizeof(zend_ulong) << 3) + 1];
937
16
  char *ptr, *end;
938
16
  zend_ulong value;
939
940
16
  if (base < 2 || base > 36) {
941
0
    return ZSTR_EMPTY_ALLOC();
942
0
  }
943
944
16
  value = arg;
945
946
16
  end = ptr = buf + sizeof(buf) - 1;
947
16
  *ptr = '\0';
948
949
16
  do {
950
16
    ZEND_ASSERT(ptr > buf);
951
16
    *--ptr = digits[value % base];
952
16
    value /= base;
953
16
  } while (value);
954
955
16
  return zend_string_init(ptr, end - ptr, 0);
956
16
}
957
/* }}} */
958
959
/* {{{ _php_math_longtobase_pwr2 */
960
/*
961
 * Convert a long to a string containing a base(2,4,6,16,32) representation of
962
 * the number.
963
 */
964
static zend_always_inline zend_string * _php_math_longtobase_pwr2(zend_long arg, int base_log2)
965
0
{
966
0
  static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
967
0
  zend_ulong value;
968
0
  size_t len;
969
0
  zend_string *ret;
970
0
  char *ptr;
971
972
0
  value = arg;
973
974
0
  if (value == 0) {
975
0
    len = 1;
976
0
  } else {
977
0
    len = ((sizeof(value) * 8 - zend_ulong_nlz(value)) + (base_log2 - 1)) / base_log2;
978
0
  }
979
980
0
  ret = zend_string_alloc(len, 0);
981
0
  ptr = ZSTR_VAL(ret) + len;
982
0
  *ptr = '\0';
983
984
0
  do {
985
0
    ZEND_ASSERT(ptr > ZSTR_VAL(ret));
986
0
    *--ptr = digits[value & ((1 << base_log2) - 1)];
987
0
    value >>= base_log2;
988
0
  } while (value);
989
990
0
  return ret;
991
0
}
992
/* }}} */
993
994
/* {{{ _php_math_zvaltobase */
995
/*
996
 * Convert a zval to a string containing a base(2-36) representation of
997
 * the number.
998
 */
999
PHPAPI zend_string * _php_math_zvaltobase(zval *arg, int base)
1000
30
{
1001
30
  static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
1002
1003
30
  if ((Z_TYPE_P(arg) != IS_LONG && Z_TYPE_P(arg) != IS_DOUBLE) || base < 2 || base > 36) {
1004
0
    return ZSTR_EMPTY_ALLOC();
1005
0
  }
1006
1007
30
  if (Z_TYPE_P(arg) == IS_DOUBLE) {
1008
14
    double fvalue = floor(Z_DVAL_P(arg)); /* floor it just in case */
1009
14
    char *ptr, *end;
1010
14
    char buf[ZEND_DOUBLE_MAX_LENGTH];
1011
1012
    /* Don't try to convert +/- infinity */
1013
14
    if (fvalue == ZEND_INFINITY || fvalue == -ZEND_INFINITY) {
1014
0
      zend_value_error("An infinite value cannot be converted to base %d", base);
1015
0
      return NULL;
1016
0
    }
1017
1018
14
    end = ptr = buf + sizeof(buf) - 1;
1019
14
    *ptr = '\0';
1020
1021
280
    do {
1022
280
      *--ptr = digits[(int) fmod(fvalue, base)];
1023
280
      fvalue /= base;
1024
280
    } while (ptr > buf && fabs(fvalue) >= 1);
1025
1026
14
    return zend_string_init(ptr, end - ptr, 0);
1027
14
  }
1028
1029
16
  return _php_math_longtobase(Z_LVAL_P(arg), base);
1030
30
}
1031
/* }}} */
1032
1033
/* {{{ Returns the decimal equivalent of the binary number */
1034
PHP_FUNCTION(bindec)
1035
0
{
1036
0
  zend_string *arg;
1037
1038
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1039
0
    Z_PARAM_STR(arg)
1040
0
  ZEND_PARSE_PARAMETERS_END();
1041
1042
0
  _php_math_basetozval(arg, 2, return_value);
1043
0
}
1044
/* }}} */
1045
1046
/* {{{ Returns the decimal equivalent of the hexadecimal number */
1047
PHP_FUNCTION(hexdec)
1048
0
{
1049
0
  zend_string *arg;
1050
1051
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1052
0
    Z_PARAM_STR(arg)
1053
0
  ZEND_PARSE_PARAMETERS_END();
1054
1055
0
  _php_math_basetozval(arg, 16, return_value);
1056
0
}
1057
/* }}} */
1058
1059
/* {{{ Returns the decimal equivalent of an octal string */
1060
PHP_FUNCTION(octdec)
1061
0
{
1062
0
  zend_string *arg;
1063
1064
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1065
0
    Z_PARAM_STR(arg)
1066
0
  ZEND_PARSE_PARAMETERS_END();
1067
1068
0
  _php_math_basetozval(arg, 8, return_value);
1069
0
}
1070
/* }}} */
1071
1072
/* {{{ Returns a string containing a binary representation of the number */
1073
PHP_FUNCTION(decbin)
1074
0
{
1075
0
  zend_long arg;
1076
1077
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1078
0
    Z_PARAM_LONG(arg)
1079
0
  ZEND_PARSE_PARAMETERS_END();
1080
1081
0
  RETURN_STR(_php_math_longtobase_pwr2(arg, 1));
1082
0
}
1083
/* }}} */
1084
1085
/* {{{ Returns a string containing an octal representation of the given number */
1086
PHP_FUNCTION(decoct)
1087
0
{
1088
0
  zend_long arg;
1089
1090
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1091
0
    Z_PARAM_LONG(arg)
1092
0
  ZEND_PARSE_PARAMETERS_END();
1093
1094
0
  RETURN_STR(_php_math_longtobase_pwr2(arg, 3));
1095
0
}
1096
/* }}} */
1097
1098
/* {{{ Returns a string containing a hexadecimal representation of the given number */
1099
PHP_FUNCTION(dechex)
1100
0
{
1101
0
  zend_long arg;
1102
1103
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
1104
0
    Z_PARAM_LONG(arg)
1105
0
  ZEND_PARSE_PARAMETERS_END();
1106
1107
0
  RETURN_STR(_php_math_longtobase_pwr2(arg, 4));
1108
0
}
1109
/* }}} */
1110
1111
ZEND_FRAMELESS_FUNCTION(dechex, 1)
1112
0
{
1113
0
  zend_long arg;
1114
1115
0
  Z_FLF_PARAM_LONG(1, arg);
1116
1117
0
  RETVAL_STR(_php_math_longtobase_pwr2(arg, 4));
1118
1119
0
flf_clean:;
1120
0
}
1121
1122
/* {{{ Converts a number in a string from any base <= 36 to any base <= 36 */
1123
PHP_FUNCTION(base_convert)
1124
30
{
1125
30
  zval temp;
1126
30
  zend_string *number;
1127
30
  zend_long frombase, tobase;
1128
30
  zend_string *result;
1129
1130
90
  ZEND_PARSE_PARAMETERS_START(3, 3)
1131
120
    Z_PARAM_STR(number)
1132
150
    Z_PARAM_LONG(frombase)
1133
150
    Z_PARAM_LONG(tobase)
1134
30
  ZEND_PARSE_PARAMETERS_END();
1135
1136
30
  if (frombase < 2 || frombase > 36) {
1137
0
    zend_argument_value_error(2, "must be between 2 and 36 (inclusive)");
1138
0
    RETURN_THROWS();
1139
0
  }
1140
30
  if (tobase < 2 || tobase > 36) {
1141
0
    zend_argument_value_error(3, "must be between 2 and 36 (inclusive)");
1142
0
    RETURN_THROWS();
1143
0
  }
1144
1145
30
  _php_math_basetozval(number, (int)frombase, &temp);
1146
30
  result = _php_math_zvaltobase(&temp, (int)tobase);
1147
30
  if (!result) {
1148
0
    RETURN_THROWS();
1149
0
  }
1150
1151
30
  RETVAL_STR(result);
1152
30
}
1153
/* }}} */
1154
1155
/* {{{ _php_math_number_format */
1156
PHPAPI zend_string *_php_math_number_format(double d, int dec, char dec_point, char thousand_sep)
1157
0
{
1158
0
  return _php_math_number_format_ex(d, dec, &dec_point, 1, &thousand_sep, 1);
1159
0
}
1160
1161
PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, const char *dec_point,
1162
    size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len)
1163
0
{
1164
0
  zend_string *res;
1165
0
  zend_string *tmpbuf;
1166
0
  char *s, *t;  /* source, target */
1167
0
  char *dp;
1168
0
  size_t integral;
1169
0
  size_t reslen = 0;
1170
0
  int count = 0;
1171
0
  int is_negative = 0;
1172
1173
0
  if (d < 0) {
1174
0
    is_negative = 1;
1175
0
    d = -d;
1176
0
  }
1177
1178
0
  d = _php_math_round(d, dec, PHP_ROUND_HALF_UP);
1179
0
  dec = MAX(0, dec);
1180
0
  tmpbuf = strpprintf(0, "%.*F", dec, d);
1181
0
  if (tmpbuf == NULL) {
1182
0
    return NULL;
1183
0
  } else if (!isdigit((unsigned char)ZSTR_VAL(tmpbuf)[0])) {
1184
0
    return tmpbuf;
1185
0
  }
1186
1187
  /* Check if the number is no longer negative after rounding */
1188
0
  if (is_negative && d == 0) {
1189
0
    is_negative = 0;
1190
0
  }
1191
1192
  /* find decimal point, if expected */
1193
0
  if (dec) {
1194
0
    dp = strpbrk(ZSTR_VAL(tmpbuf), ".,");
1195
0
  } else {
1196
0
    dp = NULL;
1197
0
  }
1198
1199
  /* calculate the length of the return buffer */
1200
0
  if (dp) {
1201
0
    integral = (dp - ZSTR_VAL(tmpbuf));
1202
0
  } else {
1203
    /* no decimal point was found */
1204
0
    integral = ZSTR_LEN(tmpbuf);
1205
0
  }
1206
1207
  /* allow for thousand separators */
1208
0
  if (thousand_sep) {
1209
0
    integral = zend_safe_addmult((integral-1)/3, thousand_sep_len, integral, "number formatting");
1210
0
  }
1211
1212
0
  reslen = integral;
1213
1214
0
  if (dec) {
1215
0
    reslen += dec;
1216
1217
0
    if (dec_point) {
1218
0
      reslen = zend_safe_addmult(reslen, 1, dec_point_len, "number formatting");
1219
0
    }
1220
0
  }
1221
1222
  /* add a byte for minus sign */
1223
0
  if (is_negative) {
1224
0
    reslen++;
1225
0
  }
1226
0
  res = zend_string_alloc(reslen, 0);
1227
1228
0
  s = ZSTR_VAL(tmpbuf) + ZSTR_LEN(tmpbuf) - 1;
1229
0
  t = ZSTR_VAL(res) + reslen;
1230
0
  *t-- = '\0';
1231
1232
  /* copy the decimal places.
1233
   * Take care, as the sprintf implementation may return less places than
1234
   * we requested due to internal buffer limitations */
1235
0
  if (dec) {
1236
0
    size_t declen = (dp ? s - dp : 0);
1237
0
    size_t topad = (size_t)dec > declen ? dec - declen : 0;
1238
1239
    /* pad with '0's */
1240
0
    while (topad--) {
1241
0
      *t-- = '0';
1242
0
    }
1243
1244
0
    if (dp) {
1245
0
      s -= declen + 1; /* +1 to skip the point */
1246
0
      t -= declen;
1247
1248
      /* now copy the chars after the point */
1249
0
      memcpy(t + 1, dp + 1, declen);
1250
0
    }
1251
1252
    /* add decimal point */
1253
0
    if (dec_point) {
1254
0
      t -= dec_point_len;
1255
0
      memcpy(t + 1, dec_point, dec_point_len);
1256
0
    }
1257
0
  }
1258
1259
  /* copy the numbers before the decimal point, adding thousand
1260
   * separator every three digits */
1261
0
  while (s >= ZSTR_VAL(tmpbuf)) {
1262
0
    *t-- = *s--;
1263
0
    if (thousand_sep && (++count%3)==0 && s >= ZSTR_VAL(tmpbuf)) {
1264
0
      t -= thousand_sep_len;
1265
0
      memcpy(t + 1, thousand_sep, thousand_sep_len);
1266
0
    }
1267
0
  }
1268
1269
  /* and a minus sign, if needed */
1270
0
  if (is_negative) {
1271
0
    *t-- = '-';
1272
0
  }
1273
1274
0
  ZSTR_LEN(res) = reslen;
1275
0
  zend_string_release_ex(tmpbuf, 0);
1276
0
  return res;
1277
0
}
1278
1279
PHPAPI zend_string *_php_math_number_format_long(zend_long num, zend_long dec, const char *dec_point,
1280
    size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len)
1281
0
{
1282
0
  static const zend_ulong powers[] = {
1283
0
    1, 10, 100, 1000, 10000,
1284
0
    100000, 1000000, 10000000, 100000000, 1000000000,
1285
0
#if SIZEOF_ZEND_LONG == 8
1286
0
    10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000,
1287
0
    1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, 10000000000000000000ul
1288
#elif SIZEOF_ZEND_LONG > 8
1289
# error "Unknown SIZEOF_ZEND_LONG"
1290
#endif
1291
0
  };
1292
1293
0
  int is_negative = 0;
1294
0
  zend_ulong tmpnum;
1295
0
  zend_ulong power;
1296
0
  zend_ulong power_half;
1297
0
  zend_ulong rest;
1298
1299
0
  zend_string *tmpbuf;
1300
0
  zend_string *res;
1301
0
  size_t reslen;
1302
0
  char *s, *t;  /* source, target */
1303
0
  int count = 0;
1304
0
  size_t topad;
1305
1306
  // unsigned absolute number and memorize negative sign
1307
0
  if (num < 0) {
1308
0
    is_negative = 1;
1309
0
    tmpnum = ((zend_ulong)-(num + 1)) + 1;
1310
0
  } else {
1311
0
    tmpnum = (zend_ulong)num;
1312
0
  }
1313
1314
  // rounding the number
1315
0
  if (dec < 0) {
1316
    // Check rounding to more negative places than possible
1317
0
    if (dec < -(sizeof(powers) / sizeof(powers[0]) - 1)) {
1318
0
      tmpnum = 0;
1319
0
    } else {
1320
0
      power = powers[-dec];
1321
0
      power_half = power / 2;
1322
0
      rest = tmpnum % power;
1323
0
      tmpnum = tmpnum / power;
1324
1325
0
      if (rest >= power_half) {
1326
0
        tmpnum = tmpnum * power + power;
1327
0
      } else {
1328
0
        tmpnum = tmpnum * power;
1329
0
      }
1330
0
    }
1331
1332
    // prevent resulting in negative zero
1333
0
    if (tmpnum == 0) {
1334
0
      is_negative = 0;
1335
0
    }
1336
0
  }
1337
1338
0
  tmpbuf = strpprintf(0, ZEND_ULONG_FMT, tmpnum);
1339
0
  reslen = ZSTR_LEN(tmpbuf);
1340
1341
  /* allow for thousand separators */
1342
0
  if (thousand_sep) {
1343
0
    reslen = zend_safe_addmult((reslen-1)/3, thousand_sep_len, reslen, "number formatting");
1344
0
  }
1345
1346
0
  reslen += is_negative;
1347
1348
0
  if (dec > 0) {
1349
0
    reslen += dec;
1350
1351
0
    if (dec_point) {
1352
0
      reslen = zend_safe_addmult(reslen, 1, dec_point_len, "number formatting");
1353
0
    }
1354
0
  }
1355
1356
0
  res = zend_string_alloc(reslen, 0);
1357
1358
0
  s = ZSTR_VAL(tmpbuf) + ZSTR_LEN(tmpbuf) - 1;
1359
0
  t = ZSTR_VAL(res) + reslen;
1360
0
  *t-- = '\0';
1361
1362
  /* copy the decimal places. */
1363
0
  if (dec > 0) {
1364
0
    topad = (size_t)dec;
1365
1366
    /* pad with '0's */
1367
0
    while (topad--) {
1368
0
      *t-- = '0';
1369
0
    }
1370
1371
    /* add decimal point */
1372
0
    if (dec_point) {
1373
0
      t -= dec_point_len;
1374
0
      memcpy(t + 1, dec_point, dec_point_len);
1375
0
    }
1376
0
  }
1377
1378
  /* copy the numbers before the decimal point, adding thousand
1379
   * separator every three digits */
1380
0
  while (s >= ZSTR_VAL(tmpbuf)) {
1381
0
    *t-- = *s--;
1382
0
    if (thousand_sep && (++count % 3) == 0 && s >= ZSTR_VAL(tmpbuf)) {
1383
0
      t -= thousand_sep_len;
1384
0
      memcpy(t + 1, thousand_sep, thousand_sep_len);
1385
0
    }
1386
0
  }
1387
1388
0
  if (is_negative) {
1389
0
    *t-- = '-';
1390
0
  }
1391
1392
0
  ZSTR_LEN(res) = reslen;
1393
0
  zend_string_release_ex(tmpbuf, 0);
1394
0
  return res;
1395
0
}
1396
1397
/* {{{ Formats a number with grouped thousands */
1398
PHP_FUNCTION(number_format)
1399
0
{
1400
0
  zval* num;
1401
0
  zend_long dec = 0;
1402
0
  int dec_int;
1403
0
  char *thousand_sep = NULL, *dec_point = NULL;
1404
0
  size_t thousand_sep_len = 0, dec_point_len = 0;
1405
1406
0
  ZEND_PARSE_PARAMETERS_START(1, 4)
1407
0
    Z_PARAM_NUMBER(num)
1408
0
    Z_PARAM_OPTIONAL
1409
0
    Z_PARAM_LONG(dec)
1410
0
    Z_PARAM_STRING_OR_NULL(dec_point, dec_point_len)
1411
0
    Z_PARAM_STRING_OR_NULL(thousand_sep, thousand_sep_len)
1412
0
  ZEND_PARSE_PARAMETERS_END();
1413
1414
0
  if (dec_point == NULL) {
1415
0
    dec_point = ".";
1416
0
    dec_point_len = 1;
1417
0
  }
1418
0
  if (thousand_sep == NULL) {
1419
0
    thousand_sep = ",";
1420
0
    thousand_sep_len = 1;
1421
0
  }
1422
1423
0
  if (UNEXPECTED(dec > INT_MAX || dec < INT_MIN)) {
1424
0
    zend_argument_value_error(2, "must be between %d and %d", INT_MIN, INT_MAX);
1425
0
    RETURN_THROWS();
1426
0
  }
1427
1428
0
  switch (Z_TYPE_P(num)) {
1429
0
    case IS_LONG:
1430
0
      RETURN_STR(_php_math_number_format_long(Z_LVAL_P(num), dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1431
1432
0
    case IS_DOUBLE:
1433
      // double values of >= 2^52 can not have fractional digits anymore
1434
      // Casting to long on 64bit will not loose precision on rounding
1435
0
      if (UNEXPECTED(
1436
0
        (Z_DVAL_P(num) >= 4503599627370496.0 || Z_DVAL_P(num) <= -4503599627370496.0)
1437
0
        && ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(num))
1438
0
      )) {
1439
0
        RETURN_STR(_php_math_number_format_long((zend_long)Z_DVAL_P(num), dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1440
0
      }
1441
1442
0
      dec_int = (int) dec;
1443
0
      RETURN_STR(_php_math_number_format_ex(Z_DVAL_P(num), dec_int, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1444
1445
0
    default: ZEND_UNREACHABLE();
1446
0
  }
1447
0
}
1448
/* }}} */
1449
1450
/* {{{ Returns the remainder of dividing x by y as a float */
1451
PHP_FUNCTION(fmod)
1452
0
{
1453
0
  double num1, num2;
1454
1455
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
1456
0
    Z_PARAM_DOUBLE(num1)
1457
0
    Z_PARAM_DOUBLE(num2)
1458
0
  ZEND_PARSE_PARAMETERS_END();
1459
1460
0
  RETURN_DOUBLE(fmod(num1, num2));
1461
0
}
1462
/* }}} */
1463
1464
/* {{{ Perform floating-point division of dividend / divisor
1465
   with IEEE-754 semantics for division by zero. */
1466
#ifdef __clang__
1467
__attribute__((no_sanitize("float-divide-by-zero")))
1468
#endif
1469
PHP_FUNCTION(fdiv)
1470
216
{
1471
216
  double dividend, divisor;
1472
1473
646
  ZEND_PARSE_PARAMETERS_START(2, 2)
1474
856
    Z_PARAM_DOUBLE(dividend)
1475
1.07k
    Z_PARAM_DOUBLE(divisor)
1476
216
  ZEND_PARSE_PARAMETERS_END();
1477
1478
214
  RETURN_DOUBLE(dividend / divisor);
1479
214
}
1480
/* }}} */
1481
1482
/* {{{ Perform floating-point exponentiation with IEEE-754 semantics. */
1483
PHP_FUNCTION(fpow)
1484
0
{
1485
0
  double base, exponent;
1486
1487
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
1488
0
    Z_PARAM_DOUBLE(base)
1489
0
    Z_PARAM_DOUBLE(exponent)
1490
0
  ZEND_PARSE_PARAMETERS_END();
1491
1492
0
  RETURN_DOUBLE(pow(base, exponent));
1493
0
}
1494
/* }}} */
1495
1496
/* {{{ Returns the integer quotient of the division of dividend by divisor */
1497
PHP_FUNCTION(intdiv)
1498
1.19k
{
1499
1.19k
  zend_long dividend, divisor;
1500
1501
3.58k
  ZEND_PARSE_PARAMETERS_START(2, 2)
1502
4.77k
    Z_PARAM_LONG(dividend)
1503
5.97k
    Z_PARAM_LONG(divisor)
1504
1.19k
  ZEND_PARSE_PARAMETERS_END();
1505
1506
1.19k
  if (divisor == 0) {
1507
1
    zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
1508
1
    RETURN_THROWS();
1509
1.19k
  } else if (divisor == -1 && dividend == ZEND_LONG_MIN) {
1510
    /* Prevent overflow error/crash ... really should not happen:
1511
       We don't return a float here as that violates function contract */
1512
0
    zend_throw_exception_ex(zend_ce_arithmetic_error, 0, "Division of PHP_INT_MIN by -1 is not an integer");
1513
0
    RETURN_THROWS();
1514
0
  }
1515
1516
1.19k
  RETURN_LONG(dividend / divisor);
1517
1.19k
}
1518
/* }}} */