mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
[RFC] Add clamp function (#19434)
* Implement clamp function Co-authored-by: thinkverse <hallberg.kim@gmail.com> * - Use a common function for normal and frameless implementations - Add tests for null and not-comparable cases - Fix object support for frameless clamp function - Improve NAN handling * Create tests triggering both frameless and dynamic variants * Add changelog * [Review] rephrase error messages to use "must not" * Enable assert() --------- Co-authored-by: thinkverse <hallberg.kim@gmail.com>
This commit is contained in:
1
NEWS
1
NEWS
@@ -7,6 +7,7 @@ PHP NEWS
|
||||
request. (ilutov)
|
||||
. It is now possible to use reference assign on WeakMap without the key
|
||||
needing to be present beforehand. (ndossche)
|
||||
. Added `clamp()`. (kylekatarnls, thinkverse)
|
||||
|
||||
- Hash:
|
||||
. Upgrade xxHash to 0.8.2. (timwolla)
|
||||
|
||||
@@ -71,6 +71,10 @@ PHP 8.6 UPGRADE NOTES
|
||||
6. New Functions
|
||||
========================================
|
||||
|
||||
- Standard:
|
||||
. `clamp()` returns the given value if in range, else return the nearest bound.
|
||||
RFC: https://wiki.php.net/rfc/clamp_v2
|
||||
|
||||
========================================
|
||||
7. New Classes and Interfaces
|
||||
========================================
|
||||
|
||||
@@ -1606,6 +1606,12 @@ function min(mixed $value, mixed ...$values): mixed {}
|
||||
*/
|
||||
function max(mixed $value, mixed ...$values): mixed {}
|
||||
|
||||
/**
|
||||
* @compile-time-eval
|
||||
* @frameless-function {"arity": 3}
|
||||
*/
|
||||
function clamp(mixed $value, mixed $min, mixed $max): mixed {}
|
||||
|
||||
function array_walk(array|object &$array, callable $callback, mixed $arg = UNKNOWN): true {}
|
||||
|
||||
function array_walk_recursive(array|object &$array, callable $callback, mixed $arg = UNKNOWN): true {}
|
||||
|
||||
16
ext/standard/basic_functions_arginfo.h
generated
16
ext/standard/basic_functions_arginfo.h
generated
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: f6bf6cdd07080c01d3a0cb08d71409d05b1084f9 */
|
||||
* Stub hash: 1a1667a5c59111f096a758d5bb4aa7cf3ec09cfe */
|
||||
|
||||
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)
|
||||
@@ -138,6 +138,12 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_max arginfo_min
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_clamp, 0, 3, IS_MIXED, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, min, IS_MIXED, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, max, IS_MIXED, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_walk, 0, 2, IS_TRUE, 0)
|
||||
ZEND_ARG_TYPE_MASK(1, array, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL)
|
||||
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
|
||||
@@ -2197,6 +2203,12 @@ static const zend_frameless_function_info frameless_function_infos_max[] = {
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
ZEND_FRAMELESS_FUNCTION(clamp, 3);
|
||||
static const zend_frameless_function_info frameless_function_infos_clamp[] = {
|
||||
{ ZEND_FRAMELESS_FUNCTION_NAME(clamp, 3), 3 },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
ZEND_FRAMELESS_FUNCTION(in_array, 2);
|
||||
ZEND_FRAMELESS_FUNCTION(in_array, 3);
|
||||
static const zend_frameless_function_info frameless_function_infos_in_array[] = {
|
||||
@@ -2332,6 +2344,7 @@ ZEND_FUNCTION(current);
|
||||
ZEND_FUNCTION(key);
|
||||
ZEND_FUNCTION(min);
|
||||
ZEND_FUNCTION(max);
|
||||
ZEND_FUNCTION(clamp);
|
||||
ZEND_FUNCTION(array_walk);
|
||||
ZEND_FUNCTION(array_walk_recursive);
|
||||
ZEND_FUNCTION(in_array);
|
||||
@@ -2925,6 +2938,7 @@ static const zend_function_entry ext_functions[] = {
|
||||
ZEND_FE(key, arginfo_key)
|
||||
ZEND_RAW_FENTRY("min", zif_min, arginfo_min, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_min, NULL)
|
||||
ZEND_RAW_FENTRY("max", zif_max, arginfo_max, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_max, NULL)
|
||||
ZEND_RAW_FENTRY("clamp", zif_clamp, arginfo_clamp, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_clamp, NULL)
|
||||
ZEND_FE(array_walk, arginfo_array_walk)
|
||||
ZEND_FE(array_walk_recursive, arginfo_array_walk_recursive)
|
||||
ZEND_RAW_FENTRY("in_array", zif_in_array, arginfo_in_array, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_in_array, NULL)
|
||||
|
||||
@@ -389,6 +389,62 @@ PHP_FUNCTION(round)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* Return the given value if in range of min and max */
|
||||
static void php_math_clamp(zval *return_value, zval *value, zval *min, zval *max)
|
||||
{
|
||||
if (Z_TYPE_P(min) == IS_DOUBLE && UNEXPECTED(zend_isnan(Z_DVAL_P(min)))) {
|
||||
zend_argument_value_error(2, "must not be NAN");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (Z_TYPE_P(max) == IS_DOUBLE && UNEXPECTED(zend_isnan(Z_DVAL_P(max)))) {
|
||||
zend_argument_value_error(3, "must not be NAN");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (zend_compare(max, min) == -1) {
|
||||
zend_argument_value_error(2, "must be smaller than or equal to argument #3 ($max)");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (zend_compare(max, value) == -1) {
|
||||
RETURN_COPY(max);
|
||||
}
|
||||
|
||||
if (zend_compare(value, min) == -1) {
|
||||
RETURN_COPY(min);
|
||||
}
|
||||
|
||||
RETURN_COPY(value);
|
||||
}
|
||||
|
||||
/* {{{ Return the given value if in range of min and max */
|
||||
PHP_FUNCTION(clamp)
|
||||
{
|
||||
zval *zvalue, *zmin, *zmax;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(3, 3)
|
||||
Z_PARAM_ZVAL(zvalue)
|
||||
Z_PARAM_ZVAL(zmin)
|
||||
Z_PARAM_ZVAL(zmax)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
php_math_clamp(return_value, zvalue, zmin, zmax);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Return the given value if in range of min and max */
|
||||
ZEND_FRAMELESS_FUNCTION(clamp, 3)
|
||||
{
|
||||
zval *zvalue, *zmin, *zmax;
|
||||
Z_FLF_PARAM_ZVAL(1, zvalue);
|
||||
Z_FLF_PARAM_ZVAL(2, zmin);
|
||||
Z_FLF_PARAM_ZVAL(3, zmax);
|
||||
|
||||
php_math_clamp(return_value, zvalue, zmin, zmax);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Returns the sine of the number in radians */
|
||||
PHP_FUNCTION(sin)
|
||||
{
|
||||
|
||||
104
ext/standard/tests/math/clamp.phpt
Normal file
104
ext/standard/tests/math/clamp.phpt
Normal file
@@ -0,0 +1,104 @@
|
||||
--TEST--
|
||||
clamp() tests
|
||||
--INI--
|
||||
precision=14
|
||||
date.timezone=UTC
|
||||
zend.assertions=1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function make_clamp_fcc() {
|
||||
return clamp(...);
|
||||
}
|
||||
|
||||
function check_clamp_result($value, $min, $max) {
|
||||
$flf = clamp($value, $min, $max);
|
||||
$dyn = make_clamp_fcc()($value, $min, $max);
|
||||
assert($flf === $dyn || (is_nan($flf) && is_nan($dyn)));
|
||||
|
||||
return $flf;
|
||||
}
|
||||
|
||||
function check_clamp_exception($value, $min, $max) {
|
||||
try {
|
||||
var_dump(clamp($value, $min, $max));
|
||||
} catch (ValueError $error) {
|
||||
echo $error->getMessage(), "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(make_clamp_fcc()($value, $min, $max));
|
||||
} catch (ValueError $error) {
|
||||
echo $error->getMessage(), "\n";
|
||||
}
|
||||
}
|
||||
|
||||
var_dump(check_clamp_result(2, 1, 3));
|
||||
var_dump(check_clamp_result(0, 1, 3));
|
||||
var_dump(check_clamp_result(6, 1, 3));
|
||||
var_dump(check_clamp_result(2, 1.3, 3.4));
|
||||
var_dump(check_clamp_result(2.5, 1, 3));
|
||||
var_dump(check_clamp_result(2.5, 1.3, 3.4));
|
||||
var_dump(check_clamp_result(0, 1.3, 3.4));
|
||||
var_dump(check_clamp_result(M_PI, -INF, INF));
|
||||
var_dump(check_clamp_result(NAN, 4, 6));
|
||||
var_dump(check_clamp_result("a", "c", "g"));
|
||||
var_dump(check_clamp_result("d", "c", "g"));
|
||||
echo check_clamp_result('2025-08-01', '2025-08-15', '2025-09-15'), "\n";
|
||||
echo check_clamp_result('2025-08-20', '2025-08-15', '2025-09-15'), "\n";
|
||||
echo check_clamp_result(new \DateTimeImmutable('2025-08-01'), new \DateTimeImmutable('2025-08-15'), new \DateTimeImmutable('2025-09-15'))->format('Y-m-d'), "\n";
|
||||
echo check_clamp_result(new \DateTimeImmutable('2025-08-20'), new \DateTimeImmutable('2025-08-15'), new \DateTimeImmutable('2025-09-15'))->format('Y-m-d'), "\n";
|
||||
var_dump(check_clamp_result(null, -1, 1));
|
||||
var_dump(check_clamp_result(null, 1, 3));
|
||||
var_dump(check_clamp_result(null, -3, -1));
|
||||
var_dump(check_clamp_result(-9999, null, 10));
|
||||
var_dump(check_clamp_result(12, null, 10));
|
||||
|
||||
$a = new \InvalidArgumentException('a');
|
||||
$b = new \RuntimeException('b');
|
||||
$c = new \LogicException('c');
|
||||
echo check_clamp_result($a, $b, $c)::class, "\n";
|
||||
echo check_clamp_result($b, $a, $c)::class, "\n";
|
||||
echo check_clamp_result($c, $a, $b)::class, "\n";
|
||||
|
||||
check_clamp_exception(4, NAN, 6);
|
||||
check_clamp_exception(7, 6, NAN);
|
||||
check_clamp_exception(1, 3, 2);
|
||||
check_clamp_exception(-9999, 5, null);
|
||||
check_clamp_exception(12, -5, null);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(2)
|
||||
int(1)
|
||||
int(3)
|
||||
int(2)
|
||||
float(2.5)
|
||||
float(2.5)
|
||||
float(1.3)
|
||||
float(3.141592653589793)
|
||||
float(NAN)
|
||||
string(1) "c"
|
||||
string(1) "d"
|
||||
2025-08-15
|
||||
2025-08-20
|
||||
2025-08-15
|
||||
2025-08-20
|
||||
int(-1)
|
||||
int(1)
|
||||
int(-3)
|
||||
int(-9999)
|
||||
int(10)
|
||||
InvalidArgumentException
|
||||
RuntimeException
|
||||
LogicException
|
||||
clamp(): Argument #2 ($min) must not be NAN
|
||||
clamp(): Argument #2 ($min) must not be NAN
|
||||
clamp(): Argument #3 ($max) must not be NAN
|
||||
clamp(): Argument #3 ($max) must not be NAN
|
||||
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)
|
||||
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)
|
||||
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)
|
||||
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)
|
||||
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)
|
||||
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)
|
||||
Reference in New Issue
Block a user