Skip to content

Commit 1029ddb

Browse files
committed
ceil/floor/round keeps given int type if possible
1 parent d58e3c0 commit 1029ddb

15 files changed

+548
-193
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: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ static inline double php_round_helper(double value, int mode) {
119119

120120
/* {{{ _php_math_round */
121121
/*
122-
* Rounds a number to a certain number of decimal places in a certain rounding
122+
* Rounds a floating point to a certain number of decimal places in a certain rounding
123123
* mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding
124124
*/
125125
PHPAPI double _php_math_round(double value, int places, int mode) {
@@ -204,6 +204,108 @@ PHPAPI double _php_math_round(double value, int places, int mode) {
204204
}
205205
/* }}} */
206206

207+
/* {{{ _php_math_round_long */
208+
/*
209+
* Rounds a zend_long to a certain number of decimal places in a certain rounding
210+
* mode. For the specifics of the algorithm, see TODO: http://wiki.php.net/rfc/int_rounding
211+
*/
212+
PHPAPI zend_result _php_math_round_long(zend_long value, int places, int mode, zend_long *result) {
213+
static const zend_long powers[] = {
214+
1, 10, 100, 1000, 10000,
215+
100000, 1000000, 10000000, 100000000, 1000000000,
216+
#if SIZEOF_ZEND_LONG >= 8
217+
10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000,
218+
1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000
219+
#elif SIZEOF_ZEND_LONG > 8
220+
# error "Unknown SIZEOF_ZEND_LONG"
221+
#endif
222+
};
223+
224+
zend_long tmp_value = value;
225+
zend_long power;
226+
zend_long power_half;
227+
zend_long rest;
228+
229+
// Boolean if the number of places used matches the number of places possible
230+
int max_places = 0;
231+
232+
// Simple case - long that does not need to be rounded
233+
if (places >= 0) {
234+
*result = tmp_value;
235+
return SUCCESS;
236+
}
237+
238+
if (-places > sizeof(powers) / sizeof(powers[0]) - 1) {
239+
// Special case for rounding to the same number of places as max length possible
240+
// as this would overflow the power of 10
241+
if (places == -MAX_LENGTH_OF_LONG + 1) {
242+
max_places = 1;
243+
rest = tmp_value;
244+
tmp_value = 0;
245+
power_half = powers[-places - 1] * 5;
246+
} else {
247+
// Rounding more places will allways be zero
248+
*result = 0;
249+
return SUCCESS;
250+
}
251+
} else {
252+
power = powers[-places];
253+
rest = tmp_value % power;
254+
power_half = power / 2;
255+
tmp_value = tmp_value / power;
256+
}
257+
258+
if (value >= 0) {
259+
if ((mode == PHP_ROUND_HALF_UP && rest >= power_half)
260+
|| (mode == PHP_ROUND_HALF_DOWN && rest > power_half)
261+
|| (mode == PHP_ROUND_HALF_EVEN && (rest > power_half || (rest == power_half && tmp_value % 2 == 1)))
262+
|| (mode == PHP_ROUND_HALF_ODD && (rest > power_half || (rest == power_half && tmp_value % 2 == 0)))
263+
) {
264+
if (max_places) {
265+
return FAILURE; // would overflow
266+
}
267+
268+
tmp_value = tmp_value * power;
269+
270+
if (tmp_value > ZEND_LONG_MAX - power) {
271+
return FAILURE; // would overflow
272+
}
273+
tmp_value = tmp_value + power;
274+
} else if (max_places) {
275+
tmp_value = 0;
276+
} else {
277+
tmp_value = tmp_value * power;
278+
}
279+
} else {
280+
if ((mode == PHP_ROUND_HALF_UP && rest <= -power_half)
281+
|| (mode == PHP_ROUND_HALF_DOWN && rest < -power_half)
282+
|| (mode == PHP_ROUND_HALF_EVEN && (rest < -power_half || (rest == -power_half && tmp_value % 2 == -1)))
283+
|| (mode == PHP_ROUND_HALF_ODD && (rest < -power_half || (rest == -power_half && tmp_value % 2 == 0)))
284+
) {
285+
if (max_places) {
286+
return FAILURE; // would underflow
287+
}
288+
289+
tmp_value = tmp_value * power;
290+
291+
if (tmp_value < ZEND_LONG_MIN + power) {
292+
return FAILURE; // would underflow
293+
}
294+
295+
tmp_value = tmp_value - power;
296+
} else if (max_places) {
297+
tmp_value = 0;
298+
} else {
299+
tmp_value = tmp_value * power;
300+
}
301+
}
302+
303+
*result = tmp_value;
304+
return SUCCESS;
305+
}
306+
/* }}} */
307+
308+
207309
/* {{{ Return the absolute value of the number */
208310
PHP_FUNCTION(abs)
209311
{
@@ -239,7 +341,7 @@ PHP_FUNCTION(ceil)
239341
if (Z_TYPE_P(value) == IS_DOUBLE) {
240342
RETURN_DOUBLE(ceil(Z_DVAL_P(value)));
241343
} else if (Z_TYPE_P(value) == IS_LONG) {
242-
RETURN_DOUBLE(zval_get_double(value));
344+
RETURN_ZVAL(value, 1, 0);
243345
} else {
244346
ZEND_ASSERT(0 && "Unexpected type");
245347
}
@@ -258,7 +360,7 @@ PHP_FUNCTION(floor)
258360
if (Z_TYPE_P(value) == IS_DOUBLE) {
259361
RETURN_DOUBLE(floor(Z_DVAL_P(value)));
260362
} else if (Z_TYPE_P(value) == IS_LONG) {
261-
RETURN_DOUBLE(zval_get_double(value));
363+
RETURN_ZVAL(value, 1, 0);
262364
} else {
263365
ZEND_ASSERT(0 && "Unexpected type");
264366
}
@@ -272,7 +374,8 @@ PHP_FUNCTION(round)
272374
int places = 0;
273375
zend_long precision = 0;
274376
zend_long mode = PHP_ROUND_HALF_UP;
275-
double return_val;
377+
double return_val_d;
378+
zend_long return_val_l;
276379

277380
ZEND_PARSE_PARAMETERS_START(1, 3)
278381
Z_PARAM_NUMBER(value)
@@ -291,16 +394,16 @@ PHP_FUNCTION(round)
291394

292395
switch (Z_TYPE_P(value)) {
293396
case IS_LONG:
294-
/* Simple case - long that doesn't need to be rounded. */
295-
if (places >= 0) {
296-
RETURN_DOUBLE((double) Z_LVAL_P(value));
397+
if (_php_math_round_long(Z_LVAL_P(value), places, (int)mode, &return_val_l) == SUCCESS) {
398+
RETURN_LONG(return_val_l);
297399
}
400+
298401
ZEND_FALLTHROUGH;
299402

300403
case IS_DOUBLE:
301-
return_val = (Z_TYPE_P(value) == IS_LONG) ? (double)Z_LVAL_P(value) : Z_DVAL_P(value);
302-
return_val = _php_math_round(return_val, (int)places, (int)mode);
303-
RETURN_DOUBLE(return_val);
404+
return_val_d = Z_TYPE_P(value) == IS_LONG ? (double)Z_LVAL_P(value) : Z_DVAL_P(value);
405+
return_val_d = _php_math_round(return_val_d, places, (int)mode);
406+
RETURN_DOUBLE(return_val_d);
304407
break;
305408

306409
EMPTY_SWITCH_DEFAULT_CASE()

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)