diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 2d4798a14db78..c49291d1d74ad 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -3700,6 +3700,16 @@ function is_iterable(mixed $value): bool {} */ function is_countable(mixed $value): bool {} +/** + * @compile-time-eval + */ +function is_representable_as_float(mixed $value): bool {} + +/** + * @compile-time-eval + */ +function is_representable_as_int(mixed $value): bool {} + /* uniqid.c */ #ifdef HAVE_GETTIMEOFDAY diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 49ad188a32275..423777816f7ab 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: dcc4e6865dac52b23534b6ef61c0d6be766af6b9 */ + * Stub hash: 78c28b4b7387d823c189068117d517b292bfc2c1 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -2069,6 +2069,10 @@ ZEND_END_ARG_INFO() #define arginfo_is_countable arginfo_boolval +#define arginfo_is_representable_as_float arginfo_boolval + +#define arginfo_is_representable_as_int arginfo_boolval + #if defined(HAVE_GETTIMEOFDAY) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_uniqid, 0, 0, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, prefix, IS_STRING, 0, "\"\"") @@ -2845,6 +2849,8 @@ ZEND_FUNCTION(is_scalar); ZEND_FUNCTION(is_callable); ZEND_FUNCTION(is_iterable); ZEND_FUNCTION(is_countable); +ZEND_FUNCTION(is_representable_as_float); +ZEND_FUNCTION(is_representable_as_int); #if defined(HAVE_GETTIMEOFDAY) ZEND_FUNCTION(uniqid); #endif @@ -3457,6 +3463,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(is_callable, arginfo_is_callable) ZEND_RAW_FENTRY("is_iterable", zif_is_iterable, arginfo_is_iterable, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("is_countable", zif_is_countable, arginfo_is_countable, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("is_representable_as_float", zif_is_representable_as_float, arginfo_is_representable_as_float, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("is_representable_as_int", zif_is_representable_as_int, arginfo_is_representable_as_int, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) #if defined(HAVE_GETTIMEOFDAY) ZEND_FE(uniqid, arginfo_uniqid) #endif diff --git a/ext/standard/tests/general_functions/is_representable_as_float.phpt b/ext/standard/tests/general_functions/is_representable_as_float.phpt new file mode 100644 index 0000000000000..b7872c752ee40 --- /dev/null +++ b/ext/standard/tests/general_functions/is_representable_as_float.phpt @@ -0,0 +1,245 @@ +--TEST-- +Test is_representable_as_float() function +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(false) diff --git a/ext/standard/tests/general_functions/is_representable_as_float_32bit.phpt b/ext/standard/tests/general_functions/is_representable_as_float_32bit.phpt new file mode 100644 index 0000000000000..de986ab7cbbeb --- /dev/null +++ b/ext/standard/tests/general_functions/is_representable_as_float_32bit.phpt @@ -0,0 +1,155 @@ +--TEST-- +Test is_representable_as_float() function (32-bit) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(false) \ No newline at end of file diff --git a/ext/standard/tests/general_functions/is_representable_as_int.phpt b/ext/standard/tests/general_functions/is_representable_as_int.phpt new file mode 100644 index 0000000000000..e05ca39344b8b --- /dev/null +++ b/ext/standard/tests/general_functions/is_representable_as_int.phpt @@ -0,0 +1,194 @@ +--TEST-- +Test is_representable_as_int() function +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(false) +bool(false) diff --git a/ext/standard/type.c b/ext/standard/type.c index 4557014ff5a33..16bbfd1b30f4c 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -465,3 +465,118 @@ PHP_FUNCTION(is_countable) RETURN_BOOL(zend_is_countable(var)); } /* }}} */ + +static bool can_int_be_exact_float(zend_long lval) { +#if SIZEOF_ZEND_LONG == 4 + return true; +#else + return (zend_long) (double) lval == lval; +#endif +} + +static bool check_numeric_string_representable_as_float(zend_string *str) { + zend_long lval; + double dval; + int oflow_info; + + uint8_t type = _is_numeric_string_ex(ZSTR_VAL(str), ZSTR_LEN(str), + &lval, &dval, false, &oflow_info, NULL); + + if (type == IS_LONG) { + return oflow_info != 0 || can_int_be_exact_float(lval); + } + + return type == IS_DOUBLE && zend_finite(dval); +} + +static bool check_numeric_string_representable_as_int(zend_string *str) { + zend_long lval; + double dval; + int oflow_info; + + uint8_t type = _is_numeric_string_ex(ZSTR_VAL(str), ZSTR_LEN(str), + &lval, &dval, false, &oflow_info, NULL); + + if (type == IS_LONG) { + return oflow_info == 0; + } + + return type == IS_DOUBLE && dval == floor(dval) && ZEND_DOUBLE_FITS_LONG(dval); +} + +PHP_FUNCTION(is_representable_as_float) +{ + zval *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(arg) + ZEND_PARSE_PARAMETERS_END(); + + switch (Z_TYPE_P(arg)) { + case IS_LONG: + RETURN_BOOL(can_int_be_exact_float(Z_LVAL_P(arg))); + + case IS_DOUBLE: + RETURN_TRUE; + + case IS_STRING: + RETURN_BOOL(check_numeric_string_representable_as_float(Z_STR_P(arg))); + + case IS_OBJECT: { + zval tmp; + if (!Z_OBJ_HT_P(arg)->cast_object || + Z_OBJ_HT_P(arg)->cast_object(Z_OBJ_P(arg), &tmp, IS_STRING) != SUCCESS) { + RETURN_FALSE; + } + + bool result = check_numeric_string_representable_as_float(Z_STR(tmp)); + zval_ptr_dtor(&tmp); + RETURN_BOOL(result); + } + + default: + RETURN_FALSE; + } +} + +PHP_FUNCTION(is_representable_as_int) +{ + zval *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(arg) + ZEND_PARSE_PARAMETERS_END(); + + switch (Z_TYPE_P(arg)) { + case IS_LONG: + RETURN_TRUE; + + case IS_DOUBLE: { + double dval = Z_DVAL_P(arg); + + if (!zend_finite(dval) || zend_isnan(dval)) { + RETURN_FALSE; + } + + RETURN_BOOL(dval == floor(dval) && ZEND_DOUBLE_FITS_LONG(dval)); + } + + case IS_STRING: + RETURN_BOOL(check_numeric_string_representable_as_int(Z_STR_P(arg))); + + case IS_OBJECT: { + zval tmp; + if (!Z_OBJ_HT_P(arg)->cast_object || + Z_OBJ_HT_P(arg)->cast_object(Z_OBJ_P(arg), &tmp, IS_STRING) != SUCCESS) { + RETURN_FALSE; + } + + bool result = check_numeric_string_representable_as_int(Z_STR(tmp)); + zval_ptr_dtor(&tmp); + RETURN_BOOL(result); + } + + default: + RETURN_FALSE; + } +}