Skip to content

Commit db98d1d

Browse files
committed
ceil/floor/round keeps given int type if possible
1 parent 2e8cdd8 commit db98d1d

15 files changed

+772
-232
lines changed

ext/standard/basic_functions.stub.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,13 +3069,13 @@ function mail(string $to, string $subject, string $message, array|string $additi
30693069
function abs(int|float $num): int|float {}
30703070

30713071
/** @compile-time-eval */
3072-
function ceil(int|float $num): float {}
3072+
function ceil(int|float $num): int|float {}
30733073

30743074
/** @compile-time-eval */
3075-
function floor(int|float $num): float {}
3075+
function floor(int|float $num): int|float {}
30763076

30773077
/** @compile-time-eval */
3078-
function round(int|float $num, int $precision = 0, int $mode = PHP_ROUND_HALF_UP): float {}
3078+
function round(int|float $num, int $precision = 0, int $mode = PHP_ROUND_HALF_UP): int|float {}
30793079

30803080
/** @compile-time-eval */
30813081
function sin(float $num): float {}

ext/standard/basic_functions_arginfo.h

Lines changed: 4 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/math.c

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ static inline double php_round_helper(double value, int mode) {
164164

165165
/* {{{ _php_math_round */
166166
/*
167-
* Rounds a number to a certain number of decimal places in a certain rounding
167+
* Rounds a floating point to a certain number of decimal places in a certain rounding
168168
* mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding
169169
*/
170170
PHPAPI double _php_math_round(double value, int places, int mode) {
@@ -249,6 +249,108 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
249249
}
250250
/* }}} */
251251

252+
/* {{{ _php_math_round_long */
253+
/*
254+
* Rounds a zend_long to a certain number of decimal places in a certain rounding
255+
* mode. For the specifics of the algorithm, see TODO: http://wiki.php.net/rfc/int_rounding
256+
*/
257+
PHPAPI zend_result _php_math_round_long(zend_long value, int places, int mode, zend_long *result) {
258+
static const zend_long powers[] = {
259+
1, 10, 100, 1000, 10000,
260+
100000, 1000000, 10000000, 100000000, 1000000000,
261+
#if SIZEOF_ZEND_LONG >= 8
262+
10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000,
263+
1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000
264+
#elif SIZEOF_ZEND_LONG > 8
265+
# error "Unknown SIZEOF_ZEND_LONG"
266+
#endif
267+
};
268+
269+
zend_long tmp_value = value;
270+
zend_long power;
271+
zend_long power_half;
272+
zend_long rest;
273+
274+
// Boolean if the number of places used matches the number of places possible
275+
int max_places = 0;
276+
277+
// Simple case - long that does not need to be rounded
278+
if (places >= 0) {
279+
*result = tmp_value;
280+
return SUCCESS;
281+
}
282+
283+
if (-places > sizeof(powers) / sizeof(powers[0]) - 1) {
284+
// Special case for rounding to the same number of places as max length possible
285+
// as this would overflow the power of 10
286+
if (places == -MAX_LENGTH_OF_LONG + 1) {
287+
max_places = 1;
288+
rest = tmp_value;
289+
tmp_value = 0;
290+
power_half = powers[-places - 1] * 5;
291+
} else {
292+
// Rounding more places will allways be zero
293+
*result = 0;
294+
return SUCCESS;
295+
}
296+
} else {
297+
power = powers[-places];
298+
rest = tmp_value % power;
299+
power_half = power / 2;
300+
tmp_value = tmp_value / power;
301+
}
302+
303+
if (value >= 0) {
304+
if ((mode == PHP_ROUND_HALF_UP && rest >= power_half)
305+
|| (mode == PHP_ROUND_HALF_DOWN && rest > power_half)
306+
|| (mode == PHP_ROUND_HALF_EVEN && (rest > power_half || (rest == power_half && tmp_value % 2 == 1)))
307+
|| (mode == PHP_ROUND_HALF_ODD && (rest > power_half || (rest == power_half && tmp_value % 2 == 0)))
308+
) {
309+
if (max_places) {
310+
return FAILURE; // would overflow
311+
}
312+
313+
tmp_value = tmp_value * power;
314+
315+
if (tmp_value > ZEND_LONG_MAX - power) {
316+
return FAILURE; // would overflow
317+
}
318+
tmp_value = tmp_value + power;
319+
} else if (max_places) {
320+
tmp_value = 0;
321+
} else {
322+
tmp_value = tmp_value * power;
323+
}
324+
} else {
325+
if ((mode == PHP_ROUND_HALF_UP && rest <= -power_half)
326+
|| (mode == PHP_ROUND_HALF_DOWN && rest < -power_half)
327+
|| (mode == PHP_ROUND_HALF_EVEN && (rest < -power_half || (rest == -power_half && tmp_value % 2 == -1)))
328+
|| (mode == PHP_ROUND_HALF_ODD && (rest < -power_half || (rest == -power_half && tmp_value % 2 == 0)))
329+
) {
330+
if (max_places) {
331+
return FAILURE; // would underflow
332+
}
333+
334+
tmp_value = tmp_value * power;
335+
336+
if (tmp_value < ZEND_LONG_MIN + power) {
337+
return FAILURE; // would underflow
338+
}
339+
340+
tmp_value = tmp_value - power;
341+
} else if (max_places) {
342+
tmp_value = 0;
343+
} else {
344+
tmp_value = tmp_value * power;
345+
}
346+
}
347+
348+
*result = tmp_value;
349+
return SUCCESS;
350+
}
351+
/* }}} */
352+
353+
252354
/* {{{ Return the absolute value of the number */
253355
PHP_FUNCTION(abs)
254356
{
@@ -283,7 +385,7 @@ PHP_FUNCTION(ceil)
283385

284386
switch (Z_TYPE_P(value)) {
285387
case IS_LONG:
286-
RETURN_DOUBLE(zval_get_double(value));
388+
RETURN_ZVAL(value, 1, 0);
287389
case IS_DOUBLE:
288390
RETURN_DOUBLE(ceil(Z_DVAL_P(value)));
289391
EMPTY_SWITCH_DEFAULT_CASE();
@@ -302,7 +404,7 @@ PHP_FUNCTION(floor)
302404

303405
switch (Z_TYPE_P(value)) {
304406
case IS_LONG:
305-
RETURN_DOUBLE(zval_get_double(value));
407+
RETURN_ZVAL(value, 1, 0);
306408
case IS_DOUBLE:
307409
RETURN_DOUBLE(floor(Z_DVAL_P(value)));
308410
EMPTY_SWITCH_DEFAULT_CASE();
@@ -317,6 +419,7 @@ PHP_FUNCTION(round)
317419
int places = 0;
318420
zend_long precision = 0;
319421
zend_long mode = PHP_ROUND_HALF_UP;
422+
zend_long return_val_l;
320423

321424
ZEND_PARSE_PARAMETERS_START(1, 3)
322425
Z_PARAM_NUMBER(value)
@@ -346,10 +449,10 @@ PHP_FUNCTION(round)
346449

347450
switch (Z_TYPE_P(value)) {
348451
case IS_LONG:
349-
/* Simple case - long that doesn't need to be rounded. */
350-
if (places >= 0) {
351-
RETURN_DOUBLE(zval_get_double(value));
452+
if (_php_math_round_long(Z_LVAL_P(value), places, (int)mode, &return_val_l) == SUCCESS) {
453+
RETURN_LONG(return_val_l);
352454
}
455+
353456
ZEND_FALLTHROUGH;
354457

355458
case IS_DOUBLE:

ext/standard/php_math.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define PHP_MATH_H
2020

2121
PHPAPI double _php_math_round(double value, int places, int mode);
22+
PHPAPI zend_result _php_math_round_long(zend_long value, int places, int mode, zend_long *result);
2223
PHPAPI zend_string *_php_math_number_format(double d, int dec, char dec_point, char thousand_sep);
2324
PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, const char *dec_point, size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len);
2425
PHPAPI zend_string *_php_math_number_format_long(zend_long num, zend_long dec, const char *dec_point, size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len);

ext/standard/tests/math/ceil_basic.phpt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ precision=14
77
echo "*** Testing ceil() : basic functionality ***\n";
88
$values = array(0,
99
-0,
10+
0.0,
11+
-0.0,
1012
0.5,
1113
-0.5,
1214
1,
1315
-1,
16+
1.0,
17+
-1.0,
1418
1.5,
1519
-1.5,
1620
2.6,
@@ -35,25 +39,29 @@ for ($i = 0; $i < count($values); $i++) {
3539
?>
3640
--EXPECTF--
3741
*** Testing ceil() : basic functionality ***
42+
int(0)
43+
int(0)
3844
float(0)
39-
float(0)
45+
float(-0)
4046
float(1)
4147
float(-0)
48+
int(1)
49+
int(-1)
4250
float(1)
4351
float(-1)
4452
float(2)
4553
float(-1)
4654
float(3)
4755
float(-2)
48-
float(31)
49-
float(95)
56+
int(31)
57+
int(95)
5058
float(11)
5159
float(-10)
5260
float(3950)
5361
float(-3950)
54-
float(39)
55-
float(1)
56-
float(0)
62+
int(39)
63+
int(1)
64+
int(0)
5765

5866
Deprecated: ceil(): Passing null to parameter #1 ($num) of type int|float is deprecated in %s on line %d
59-
float(0)
67+
int(0)

ext/standard/tests/math/ceil_basiclong_64bit.phpt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,32 @@ foreach ($longVals as $longVal) {
2727
?>
2828
--EXPECT--
2929
--- testing: 9223372036854775807 ---
30-
float(9.223372036854776E+18)
30+
int(9223372036854775807)
3131
--- testing: -9223372036854775808 ---
32-
float(-9.223372036854776E+18)
32+
int(-9223372036854775808)
3333
--- testing: 2147483647 ---
34-
float(2147483647)
34+
int(2147483647)
3535
--- testing: -2147483648 ---
36-
float(-2147483648)
36+
int(-2147483648)
3737
--- testing: 9223372034707292160 ---
38-
float(9.223372034707292E+18)
38+
int(9223372034707292160)
3939
--- testing: -9223372034707292160 ---
40-
float(-9.223372034707292E+18)
40+
int(-9223372034707292160)
4141
--- testing: 2147483648 ---
42-
float(2147483648)
42+
int(2147483648)
4343
--- testing: -2147483649 ---
44-
float(-2147483649)
44+
int(-2147483649)
4545
--- testing: 4294967294 ---
46-
float(4294967294)
46+
int(4294967294)
4747
--- testing: 4294967295 ---
48-
float(4294967295)
48+
int(4294967295)
4949
--- testing: 4294967293 ---
50-
float(4294967293)
50+
int(4294967293)
5151
--- testing: 9223372036854775806 ---
52-
float(9.223372036854776E+18)
52+
int(9223372036854775806)
5353
--- testing: 9.2233720368548E+18 ---
5454
float(9.223372036854776E+18)
5555
--- testing: -9223372036854775807 ---
56-
float(-9.223372036854776E+18)
56+
int(-9223372036854775807)
5757
--- testing: -9.2233720368548E+18 ---
5858
float(-9.223372036854776E+18)

ext/standard/tests/math/ceil_variation1.phpt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,24 @@ fclose($fp);
7777
-- Iteration 1 --
7878

7979
Deprecated: ceil(): Passing null to parameter #1 ($num) of type int|float is deprecated in %s on line %d
80-
float(0)
80+
int(0)
8181

8282
-- Iteration 2 --
8383

8484
Deprecated: ceil(): Passing null to parameter #1 ($num) of type int|float is deprecated in %s on line %d
85-
float(0)
85+
int(0)
8686

8787
-- Iteration 3 --
88-
float(1)
88+
int(1)
8989

9090
-- Iteration 4 --
91-
float(0)
91+
int(0)
9292

9393
-- Iteration 5 --
94-
float(1)
94+
int(1)
9595

9696
-- Iteration 6 --
97-
float(0)
97+
int(0)
9898

9999
-- Iteration 7 --
100100
ceil(): Argument #1 ($num) must be of type int|float, string given
@@ -120,12 +120,12 @@ ceil(): Argument #1 ($num) must be of type int|float, classA given
120120
-- Iteration 14 --
121121

122122
Deprecated: ceil(): Passing null to parameter #1 ($num) of type int|float is deprecated in %s on line %d
123-
float(0)
123+
int(0)
124124

125125
-- Iteration 15 --
126126

127127
Deprecated: ceil(): Passing null to parameter #1 ($num) of type int|float is deprecated in %s on line %d
128-
float(0)
128+
int(0)
129129

130130
-- Iteration 16 --
131131
ceil(): Argument #1 ($num) must be of type int|float, resource given

0 commit comments

Comments
 (0)