Coverage Report

Created: 2026-10-28 06:20

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
32
static inline double php_intpow10(int power) {
38
  /* Not in lookup table */
39
32
  if (power < 0 || power > 22) {
40
0
    return pow(10.0, (double)power);
41
0
  }
42
43
32
  static const double powers[] = {
44
32
      1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
45
32
      1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
46
47
32
  return powers[power];
48
32
}
49
/* }}} */
50
51
static zend_always_inline double php_round_get_basic_edge_case(double integral, double exponent, int places)
52
32
{
53
32
  return (places > 0)
54
32
    ? fabs((integral + copysign(0.5, integral)) / exponent)
55
32
    : fabs((integral + copysign(0.5, integral)) * exponent);
56
32
}
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
32
static inline double php_round_helper(double integral, double value, double exponent, int places, int mode) {
68
32
  double value_abs = fabs(value);
69
32
  double edge_case;
70
71
32
  switch (mode) {
72
32
    case PHP_ROUND_HALF_UP:
73
32
      edge_case = php_round_get_basic_edge_case(integral, exponent, places);
74
32
      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
16
        return integral + copysign(1.0, integral);
84
16
      }
85
86
16
      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
32
  }
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
42
PHPAPI double _php_math_round(double value, int places, int mode) {
167
42
  double exponent, tmp_value, tmp_value2;
168
169
42
  if (!zend_finite(value) || value == 0.0) {
170
0
    return value;
171
0
  }
172
173
42
  if (places == 0 && value == trunc(value)) {
174
10
        return value;
175
10
    }
176
177
32
  places = places < INT_MIN+1 ? INT_MIN+1 : places;
178
179
32
  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
32
  if (value >= 0.0) {
201
32
    tmp_value = floor(places > 0 ? value * exponent : value / exponent);
202
32
    tmp_value2 = tmp_value + 1.0;
203
32
  } else {
204
0
    tmp_value = ceil(places > 0 ? value * exponent : value / exponent);
205
0
    tmp_value2 = tmp_value - 1.0;
206
0
  }
207
208
32
  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
32
  if (fabs(tmp_value) >= 1e16) {
214
0
    return value;
215
0
  }
216
217
  /* round the temp value */
218
32
  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
32
  if (abs(places) < 23) {
222
32
    if (places > 0) {
223
0
      tmp_value = tmp_value / exponent;
224
32
    } else {
225
32
      tmp_value = tmp_value * exponent;
226
32
    }
227
32
  } 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
32
  return tmp_value;
246
32
}
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
0
{
294
0
  zval *value;
295
296
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
297
0
    Z_PARAM_NUMBER(value)
298
0
  ZEND_PARSE_PARAMETERS_END();
299
300
0
  switch (Z_TYPE_P(value)) {
301
0
    case IS_LONG:
302
0
      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
0
  }
307
0
}
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
0
{
425
0
  zval *zvalue, *zmin, *zmax;
426
427
0
  ZEND_PARSE_PARAMETERS_START(3, 3)
428
0
    Z_PARAM_ZVAL(zvalue)
429
0
    Z_PARAM_ZVAL(zmin)
430
0
    Z_PARAM_ZVAL(zmax)
431
0
  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
1
{
452
1
  double num;
453
454
3
  ZEND_PARSE_PARAMETERS_START(1, 1)
455
4
    Z_PARAM_DOUBLE(num)
456
1
  ZEND_PARSE_PARAMETERS_END();
457
1
  RETURN_DOUBLE(sin(num));
458
1
}
459
/* }}} */
460
461
/* {{{ Returns the cosine of the number in radians */
462
PHP_FUNCTION(cos)
463
0
{
464
0
  double num;
465
466
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
467
0
    Z_PARAM_DOUBLE(num)
468
0
  ZEND_PARSE_PARAMETERS_END();
469
0
  RETURN_DOUBLE(cos(num));
470
0
}
471
/* }}} */
472
473
/* {{{ Returns the tangent of the number in radians */
474
PHP_FUNCTION(tan)
475
0
{
476
0
  double num;
477
478
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
479
0
    Z_PARAM_DOUBLE(num)
480
0
  ZEND_PARSE_PARAMETERS_END();
481
0
  RETURN_DOUBLE(tan(num));
482
0
}
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
6
{
609
6
  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
0
{
618
0
  double dval;
619
620
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
621
0
    Z_PARAM_DOUBLE(dval)
622
0
  ZEND_PARSE_PARAMETERS_END();
623
0
  RETURN_BOOL(zend_finite(dval));
624
0
}
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
0
{
642
0
  double dval;
643
644
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
645
0
    Z_PARAM_DOUBLE(dval)
646
0
  ZEND_PARSE_PARAMETERS_END();
647
0
  RETURN_BOOL(zend_isnan(dval));
648
0
}
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
0
{
707
0
  double num, base = 0;
708
709
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
710
0
    Z_PARAM_DOUBLE(num)
711
0
    Z_PARAM_OPTIONAL
712
0
    Z_PARAM_DOUBLE(base)
713
0
  ZEND_PARSE_PARAMETERS_END();
714
715
0
  if (ZEND_NUM_ARGS() == 1) {
716
0
    RETURN_DOUBLE(log(num));
717
0
  }
718
719
0
  if (base == 2.0) {
720
0
    RETURN_DOUBLE(log2(num));
721
0
  }
722
723
0
  if (base == 10.0) {
724
0
    RETURN_DOUBLE(log10(num));
725
0
  }
726
727
0
  if (base == 1.0) {
728
0
    RETURN_DOUBLE(ZEND_NAN);
729
0
  }
730
731
0
  if (base <= 0.0) {
732
0
    zend_argument_value_error(2, "must be greater than 0");
733
0
    RETURN_THROWS();
734
0
  }
735
736
0
  RETURN_DOUBLE(log(num) / log(base));
737
0
}
738
/* }}} */
739
740
/* {{{ Returns the base-10 logarithm of the number */
741
PHP_FUNCTION(log10)
742
0
{
743
0
  double num;
744
745
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
746
0
    Z_PARAM_DOUBLE(num)
747
0
  ZEND_PARSE_PARAMETERS_END();
748
749
0
  RETURN_DOUBLE(log10(num));
750
0
}
751
/* }}} */
752
753
/* {{{ Returns the square root of the number */
754
PHP_FUNCTION(sqrt)
755
0
{
756
0
  double num;
757
758
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
759
0
    Z_PARAM_DOUBLE(num)
760
0
  ZEND_PARSE_PARAMETERS_END();
761
762
0
  RETURN_DOUBLE(sqrt(num));
763
0
}
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
0
{
783
0
  double deg;
784
785
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
786
0
    Z_PARAM_DOUBLE(deg)
787
0
  ZEND_PARSE_PARAMETERS_END();
788
0
  RETURN_DOUBLE((deg / 180.0) * M_PI);
789
0
}
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
0
{
855
0
  zend_long num = 0;
856
0
  double fnum = 0;
857
0
  int mode = 0;
858
0
  char c, *s, *e;
859
0
  zend_long cutoff;
860
0
  int cutlim;
861
0
  int invalidchars = 0;
862
863
0
  s = ZSTR_VAL(str);
864
0
  e = s + ZSTR_LEN(str);
865
866
  /* Skip leading whitespace */
867
0
  while (s < e && isspace((unsigned char)*s)) s++;
868
  /* Skip trailing whitespace */
869
0
  while (s < e && isspace((unsigned char)e[-1])) e--;
870
871
0
  if (e - s >= 2) {
872
0
    if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2;
873
0
    if (base == 8 && s[0] == '0' && (s[1] == 'o' || s[1] == 'O')) s += 2;
874
0
    if (base == 2 && s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) s += 2;
875
0
  }
876
877
0
  cutoff = ZEND_LONG_MAX / base;
878
0
  cutlim = ZEND_LONG_MAX % base;
879
880
0
  while (s < e) {
881
0
    c = *s++;
882
883
    /* might not work for EBCDIC */
884
0
    if (c >= '0' && c <= '9')
885
0
      c -= '0';
886
0
    else if (c >= 'A' && c <= 'Z')
887
0
      c -= 'A' - 10;
888
0
    else if (c >= 'a' && c <= 'z')
889
0
      c -= 'a' - 10;
890
0
    else {
891
0
      invalidchars++;
892
0
      continue;
893
0
    }
894
895
0
    if (c >= base) {
896
0
      invalidchars++;
897
0
      continue;
898
0
    }
899
900
0
    switch (mode) {
901
0
    case 0: /* Integer */
902
0
      if (num < cutoff || (num == cutoff && c <= cutlim)) {
903
0
        num = num * base + c;
904
0
        break;
905
0
      } else {
906
0
        zend_error(E_NOTICE, "Input number is larger than PHP_INT_MAX, precision has been lost in conversion");
907
0
        fnum = (double)num;
908
0
        mode = 1;
909
0
      }
910
0
      ZEND_FALLTHROUGH;
911
0
    case 1: /* Float */
912
0
      fnum = fnum * base + c;
913
0
    }
914
0
  }
915
916
0
  if (invalidchars > 0) {
917
0
    zend_error(E_DEPRECATED, "Invalid characters passed for attempted conversion, these have been ignored");
918
0
  }
919
920
0
  if (mode == 1) {
921
0
    ZVAL_DOUBLE(ret, fnum);
922
0
  } else {
923
0
    ZVAL_LONG(ret, num);
924
0
  }
925
0
}
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
0
{
935
0
  static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
936
0
  char buf[(sizeof(zend_ulong) << 3) + 1];
937
0
  char *ptr, *end;
938
0
  zend_ulong value;
939
940
0
  if (base < 2 || base > 36) {
941
0
    return ZSTR_EMPTY_ALLOC();
942
0
  }
943
944
0
  value = arg;
945
946
0
  end = ptr = buf + sizeof(buf) - 1;
947
0
  *ptr = '\0';
948
949
0
  do {
950
0
    ZEND_ASSERT(ptr > buf);
951
0
    *--ptr = digits[value % base];
952
0
    value /= base;
953
0
  } while (value);
954
955
0
  return zend_string_init(ptr, end - ptr, 0);
956
0
}
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
0
{
1001
0
  static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
1002
1003
0
  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
0
  if (Z_TYPE_P(arg) == IS_DOUBLE) {
1008
0
    double fvalue = floor(Z_DVAL_P(arg)); /* floor it just in case */
1009
0
    char *ptr, *end;
1010
0
    char buf[ZEND_DOUBLE_MAX_LENGTH];
1011
1012
    /* Don't try to convert +/- infinity */
1013
0
    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
0
    end = ptr = buf + sizeof(buf) - 1;
1019
0
    *ptr = '\0';
1020
1021
0
    do {
1022
0
      *--ptr = digits[(int) fmod(fvalue, base)];
1023
0
      fvalue /= base;
1024
0
    } while (ptr > buf && fabs(fvalue) >= 1);
1025
1026
0
    return zend_string_init(ptr, end - ptr, 0);
1027
0
  }
1028
1029
0
  return _php_math_longtobase(Z_LVAL_P(arg), base);
1030
0
}
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
0
{
1125
0
  zval temp;
1126
0
  zend_string *number;
1127
0
  zend_long frombase, tobase;
1128
0
  zend_string *result;
1129
1130
0
  ZEND_PARSE_PARAMETERS_START(3, 3)
1131
0
    Z_PARAM_STR(number)
1132
0
    Z_PARAM_LONG(frombase)
1133
0
    Z_PARAM_LONG(tobase)
1134
0
  ZEND_PARSE_PARAMETERS_END();
1135
1136
0
  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
0
  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
0
  _php_math_basetozval(number, (int)frombase, &temp);
1146
0
  result = _php_math_zvaltobase(&temp, (int)tobase);
1147
0
  if (!result) {
1148
0
    RETURN_THROWS();
1149
0
  }
1150
1151
0
  RETVAL_STR(result);
1152
0
}
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
0
{
1471
0
  double dividend, divisor;
1472
1473
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
1474
0
    Z_PARAM_DOUBLE(dividend)
1475
0
    Z_PARAM_DOUBLE(divisor)
1476
0
  ZEND_PARSE_PARAMETERS_END();
1477
1478
0
  RETURN_DOUBLE(dividend / divisor);
1479
0
}
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
0
{
1499
0
  zend_long dividend, divisor;
1500
1501
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
1502
0
    Z_PARAM_LONG(dividend)
1503
0
    Z_PARAM_LONG(divisor)
1504
0
  ZEND_PARSE_PARAMETERS_END();
1505
1506
0
  if (divisor == 0) {
1507
0
    zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
1508
0
    RETURN_THROWS();
1509
0
  } 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
0
  RETURN_LONG(dividend / divisor);
1517
0
}
1518
/* }}} */