Coverage Report

Created: 2026-02-14 06:52

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