Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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
========================================
Expand Down
6 changes: 6 additions & 0 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
16 changes: 15 additions & 1 deletion ext/standard/basic_functions_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions ext/standard/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Comment thread
kylekatarnls marked this conversation as resolved.

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)
{
Expand Down
103 changes: 103 additions & 0 deletions ext/standard/tests/math/clamp.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
--TEST--
clamp() tests
--INI--
precision=14
date.timezone=UTC
Comment thread
kylekatarnls marked this conversation as resolved.
--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)));
Comment thread
kylekatarnls marked this conversation as resolved.

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)