Hello,
I handled the feedback received on the draft RCF
https://wiki.php.net/rfc/clamp_v2
If I didn't forget anything this should be now ready for discussion. So I
updated its status.
There is an implementation proposal:
https://github.com/php/php-src/pull/19434
And some draft for documentation here:
https://github.com/php/doc-en/pull/4814
Thanks,
Hey Kyle,
Hello,
I handled the feedback received on the draft RCF
https://wiki.php.net/rfc/clamp_v2
I know this isn't very used in internals, but does it make sense to write
generics for the generated stubs of this function?
Specifically, I'd expect input, min and max to be bound to the same exact
type:
/**
* @template T
* @param T $value
* @param T $min
* @param T $max
* @return T
*/
function clamp (mixed $value, mixed $min, mixed $max): mixed {
// ...
}
Greets,
Marco Pivetta
I know this isn't very used in internals, but does it make sense to
write
generics for the generated stubs of this function?
[...]
/**
- @template T
- @param T $value
- @param T $min
- @param T $max
- @return T
*/
function clamp (mixed $value, mixed $min, mixed $max): mixed {
+1 This is an excellent suggestion. Using a template ensures type
safety and clearly communicates the intended relationship between the
arguments and return value, which is a significant improvement.
This also highlights a minor but important clarification for the
documentation. To make the behavior perfectly unambiguous, I suggest
a small edit to explicitly state the bounds are inclusive.
Current text:
clamp
takes three arguments, a$value
,$min
and$max
, then
checks if$value
is within the bounds of$min
and$max
.
Suggested edit:
clamp
takes three arguments, a$value
,$min
and$max
, then
checks if$value
is within the bounds of$min
and$max
(both
inclusive).
This is a minimal change that adds crucial clarity already in the first
sentence.
--hakre
Hi
Am 2025-08-28 08:35, schrieb Hans Krentel:
This also highlights a minor but important clarification for the
documentation. To make the behavior perfectly unambiguous, I suggest
a small edit to explicitly state the bounds are inclusive.
This is a good point and I agree that it should be spelled out
explicitly. However to me this also raises the question if the interval
boundaries should be configurable to switch between closed intervals and
(half-)open intervals. For many applications of continuous values a
right-open interval (excluding the upper boundary) is more useful, since
this makes it easy to generate non-overlapping ranges (e.g. to clamp to
a given day when passing DateTimeImmutable). This would also provide an
additional value-add over the min-max construction, where the boundaries
would need to be calculated manually, which is super complicated to do
in userland for floats.
There's precedent in ext/random with the Randomizer::getFloat() method
that takes a Random\IntervalBoundary enum to decide on the the
interpretation of the boundary values. Translated to clamp()
this
could mean:
$value = 1.0;
clamp($value, 0.0, 1.0, IntervalBoundary::ClosedOpen); //
0.999999999999999888978
Best regards
Tim Düsterhus
There's precedent in ext/random with the Randomizer::getFloat() method
that takes a Random\IntervalBoundary enum to decide on the the
interpretation of the boundary values. Translated toclamp()
this
could mean:
If it weren't living in the Random extension, that very Enum could have
been used.
Hi
There's precedent in ext/random with the Randomizer::getFloat() method
that takes a Random\IntervalBoundary enum to decide on the the
interpretation of the boundary values. Translated toclamp()
this
could mean:If it weren't living in the Random extension, that very Enum could have
been used.
Indeed. Back when introducing the enum, we actually had a discussion
about naming and “placement”. In the end, we opted to make it
ext/random-specific as a more conservative choice. I think back then it
was the first native enum.
See https://externals.io/message/118810#118910 and
https://chat.stackoverflow.com/transcript/11?m=55444127#55444127.
If a new general-purpose IntervalBoundary enum is introduced, I'm happy
to widen the type in ext/random from \Random\IntervalBoundary
to
\IntervalBoundary|\Random\IntervalBoundary
(and deprecate the latter
in one of the following versions).
Best regards
Tim Düsterhus
Hi
There's precedent in ext/random with the Randomizer::getFloat() method that takes a Random\IntervalBoundary enum to decide on the the interpretation of the boundary values. Translated to
clamp()
this could mean:If it weren't living in the Random extension, that very Enum could have been used.
Indeed. Back when introducing the enum, we actually had a discussion about naming and “placement”. In the end, we opted to make it ext/random-specific as a more conservative choice. I think back then it was the first native enum.
See https://externals.io/message/118810#118910 and https://chat.stackoverflow.com/transcript/11?m=55444127#55444127.
If a new general-purpose IntervalBoundary enum is introduced, I'm happy to widen the type in ext/random from
\Random\IntervalBoundary
to\IntervalBoundary|\Random\IntervalBoundary
(and deprecate the latter in one of the following versions).Best regards
Tim Düsterhus
Not sure if I'm too much off track here: As we can't alias classes in userland that are internal classes, internally PHP could do the oppsite and given clamp could borrow it out to/from \Random\IntervalBoundary given we have enough discipline. That also could buy some time on the widening idea and preserve a bit of the conservative approach to further on defer the details until necessary.
Widening is certainly correct in its own right, but why not keep the door open this way so one day we can applaud the first internal enum for its foresight?
-- hakre
Not sure if I'm too much off track here: As we can't alias classes in userland that are internal classes, internally PHP could do the oppsite and given clamp could borrow it out to/from \Random\IntervalBoundary given we have enough discipline. That also could buy some time on the widening idea and preserve a bit of the conservative approach to further on defer the details until necessary.
This is false: you can perfectly make a userland alias of an internal class since PHP 8.3.
PHP can also make aliases of classes internally.
Hi,
2025-08-28 at 09:37, Tim Düsterhus tim@bastelstu.be wrote :
Hi
Am 2025-08-28 08:35, schrieb Hans Krentel:
This also highlights a minor but important clarification for the
documentation. To make the behavior perfectly unambiguous, I suggest
a small edit to explicitly state the bounds are inclusive.This is a good point and I agree that it should be spelled out
explicitly. However to me this also raises the question if the interval
boundaries should be configurable to switch between closed intervals and
(half-)open intervals. For many applications of continuous values a
right-open interval (excluding the upper boundary) is more useful, since
this makes it easy to generate non-overlapping ranges (e.g. to clamp to
a given day when passing DateTimeImmutable). This would also provide an
additional value-add over the min-max construction, where the boundaries
would need to be calculated manually, which is super complicated to do
in userland for floats.There's precedent in ext/random with the Randomizer::getFloat() method
that takes a Random\IntervalBoundary enum to decide on the the
interpretation of the boundary values. Translated toclamp()
this
could mean:$value = 1.0; clamp($value, 0.0, 1.0, IntervalBoundary::ClosedOpen); //
0.999999999999999888978
Best regards
Tim Düsterhus
Just my opinion but the function aims to provide an alternative to
min(max()) that would be:
- easier to read
- faster to execute
If having many different behaviors depending on an option and so a lot of
if-else path complexity, then it will tend to defeat the second goal. I
would add it if there is a strong interest, otherwise I'd rather stick to a
minimalist implementation (like seen in most languages where clamp() is
available.
Hi,
2025-08-28 at 09:37, Tim Düsterhus tim@bastelstu.be wrote :
There's precedent in ext/random with the Randomizer::getFloat() method
that takes a Random\IntervalBoundary enum to decide on the the
interpretation of the boundary values. Translated toclamp()
this
could mean:$value = 1.0; clamp($value, 0.0, 1.0, IntervalBoundary::ClosedOpen); //
0.999999999999999888978
Best regards
Tim DüsterhusJust my opinion but the function aims to provide an alternative to
min(max()) that would be:
- easier to read
- faster to execute
If having many different behaviors depending on an option and so a lot
of if-else path complexity, then it will tend to defeat the second
goal. I would add it if there is a strong interest, otherwise I'd
rather stick to a minimalist implementation (like seen in most
languages where clamp() is available.
I would also favor the minimalist/traditional/like-everyone-else approach. I wondered at first if open/closed boundaries made sense, but on consideration, that's not what clamp() traditionally does. It's just min/max shortcutting. So let's just do min/max shortcutting and move on with life.
--Larry Garfield
Hi
Just my opinion but the function aims to provide an alternative to
min(max()) that would be:
- easier to read
- faster to execute
If having many different behaviors depending on an option and so a lot of
if-else path complexity, then it will tend to defeat the second goal. I
Adding an additional parameter to switch between 4 modes of comparison
hardly qualifies as “a lot of if-else path complexity”. That can be done
with a single switch()
statement. I doubt that you can measure a
meaningful difference in speed and if you can, then it's still possible
to optimize based on the expected path (i.e. closed intervals).
Since a big part of your argument in favor of the clamp() function is
“performance” (both in your reply here and in the introduction to the
RFC): Please add meaningful benchmarks to the RFC text that show how
much faster clamp() is actually going to be compared to alternative
solutions.
would add it if there is a strong interest, otherwise I'd rather stick to a
minimalist implementation (like seen in most languages where clamp() is
available.
Best regards
Tim Düsterhus
Since a big part of your argument in favor of the clamp() function is
“performance” (both in your reply here and in the introduction to the
RFC): Please add meaningful benchmarks to the RFC text that show how
much faster clamp() is actually going to be compared to alternative
solutions.
Another part of the argument is "complexity", as brought up in the
original proposal. I notice that the offered implementation of this
function relies entirely on zend_compare().
max/min special-case long and double arguments and compare them more
directly, leaving cases like string/string, string/double, array/array,
or boolean/null to be handled by zend_compare(). It is a lot more
complex, turning less than a dozen lines (looping over and passing args
to zend_compare()) into more than fifty, presumably for performance
reasons. The extra complication may not be worth it if there are only
two or three values being compared (long and double are the first types
zend_compare tries).
Hi
Another part of the argument is "complexity", as brought up in the
original proposal. I notice that the offered implementation of this
function relies entirely on zend_compare().
I'm not finding the word “complexity” in https://wiki.php.net/rfc/clamp
or https://externals.io/message/115050. Can you link to the message you
are referring to?
reasons. The extra complication may not be worth it if there are only
two or three values being compared (long and double are the first types
zend_compare tries).
I'm positive that the benchmarks will answer that question.
Best regards
Tim Düsterhus
Hi
Another part of the argument is "complexity", as brought up in the
original proposal. I notice that the offered implementation of this
function relies entirely on zend_compare().I'm not finding the word “complexity” in https://wiki.php.net/rfc/clamp
or https://externals.io/message/115050. Can you link to the message you
are referring to?
I'm probably misremembering https://externals.io/message/128505#128532
and #128542.
reasons. The extra complication may not be worth it if there are only
two or three values being compared (long and double are the first types
zend_compare tries).I'm positive that the benchmarks will answer that question.
Agreed. But if clamp doesn't need to special-case numeric inputs to
perform well then maybe max/min don't either (at least not any more) and
their implementations could be streamlined.
Best regards
Tim Düsterhus
Hi
Am 2025-08-27 17:45, schrieb Marco Pivetta:
I know this isn't very used in internals, but does it make sense to
write
generics for the generated stubs of this function?
The stub generator makes use of the types provided in the PHPDoc comment
to generate optimizer information. The stub generator has no support for
generic types (or things like array shapes), which makes this risky. The
information is not used, it is not tested and it might confuse the stub
generator.
See https://github.com/php/php-src/pull/19546 as an example.
If there is an interest in having generics in the stubs, the stub
generator should first be extended to handle them, even if it does not
(yet) use the information and then it should be consistently applied to
the entirety of PHP's stdlib.
Best regards
Tim Düsterhus
Hey,
Hello,
I handled the feedback received on the draft RCF
https://wiki.php.net/rfc/clamp_v2If I didn't forget anything this should be now ready for discussion.
So I updated its status.There is an implementation proposal:
https://github.com/php/php-src/pull/19434
And some draft for documentation here:
https://github.com/php/doc-en/pull/4814Thanks,
As others have noted before, what's the motivation of having the
to-be-clamped value as first parameter rather than infixed between min
and max?
E.g. from CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/clamp:
|clamp(min, val, max)|
Feels to me as the most natural order, too.
You mention it was taken from the first RFC, but that one also did not
discuss the ordering in the first place either.
Bob
Hey,
Hello,
I handled the feedback received on the draft RCF https://wiki.php.net/rfc/clamp_v2
If I didn't forget anything this should be now ready for discussion. So I updated its status.
There is an implementation proposal: https://github.com/php/php-src/pull/19434
And some draft for documentation here: https://github.com/php/doc-en/pull/4814Thanks,
As others have noted before, what's the motivation of having the
to-be-clamped value as first parameter rather than infixed between min
and max?E.g. from CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/clamp:
clamp(min, val, max)
Feels to me as the most natural order, too.
You mention it was taken from the first RFC, but that one also did not
discuss the ordering in the first place either.
Some quick data points from momentary googling:
CSS: clamp(min, val, max)
Python: clamp(val, min, max)
C++: clamp(val, min, max)
Java: clamp(val, in, max)
Javascript: clamp(val, min, max)
C#: clamp(val, min, max)
Kotlin: val.clamp(min, max) (as an extension function)
Ruby: val.clamp(min, max)
So it looks like CSS is the oddball here. We should follow the clear majority approach. (Kyle, feel free to include this in the RFC.)
--Larry Garfield
Thanks Larry!
Hey,
Hello,
I handled the feedback received on the draft RCF https://wiki.php.net/rfc/clamp_v2
If I didn't forget anything this should be now ready for discussion. So I updated its status.
There is an implementation proposal: https://github.com/php/php-src/pull/19434
And some draft for documentation here: https://github.com/php/doc-en/pull/4814Thanks,
As others have noted before, what's the motivation of having the
to-be-clamped value as first parameter rather than infixed between min
and max?E.g. from CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/clamp:
clamp(min, val, max)
Feels to me as the most natural order, too.
You mention it was taken from the first RFC, but that one also did not
discuss the ordering in the first place either.
Some quick data points from momentary googling:CSS: clamp(min, val, max)
Python: clamp(val, min, max)
C++: clamp(val, min, max)
Java: clamp(val, in, max)
Javascript: clamp(val, min, max)
C#: clamp(val, min, max)Kotlin: val.clamp(min, max) (as an extension function)
Ruby: val.clamp(min, max)So it looks like CSS is the oddball here. We should follow the clear majority approach. (Kyle, feel free to include this in the RFC.)
--Larry Garfield
Indeed, that makes sense then. I must admit that I've only ever been
using the clamp from CSS, but never from other languages. (I still think
value in the middle makes more sense, but I guess that's me then, and I
won't object.)
Then yes, please note it in the RFC :-)
Bob
On Wed, Aug 27, 2025, 12:44 PM Larry Garfield larry@garfieldtech.com
wrote:
Hey,
Hello,
I handled the feedback received on the draft RCF
https://wiki.php.net/rfc/clamp_v2If I didn't forget anything this should be now ready for discussion. So
I updated its status.There is an implementation proposal:
https://github.com/php/php-src/pull/19434
And some draft for documentation here:
https://github.com/php/doc-en/pull/4814Thanks,
As others have noted before, what's the motivation of having the
to-be-clamped value as first parameter rather than infixed between min
and max?E.g. from CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/clamp:
clamp(min, val, max)
Feels to me as the most natural order, too.
You mention it was taken from the first RFC, but that one also did not
discuss the ordering in the first place either.Some quick data points from momentary googling:
CSS: clamp(min, val, max)
Python: clamp(val, min, max)
C++: clamp(val, min, max)
Java: clamp(val, in, max)
Javascript: clamp(val, min, max)
C#: clamp(val, min, max)Kotlin: val.clamp(min, max) (as an extension function)
Ruby: val.clamp(min, max)So it looks like CSS is the oddball here. We should follow the clear
majority approach. (Kyle, feel free to include this in the RFC.)--Larry Garfield
A reference from userland, Laravel has Number::clamp($val, $min, $max) as
well.
https://github.com/laravel/framework/blob/12.x/src%2FIlluminate%2FSupport%2FNumber.php#L301
...So it looks like CSS is the oddball here. We should follow the clear
majority approach...
While the majority of languages favor clamp($value, $min, $max)
,
dismissing the CSS order without examining the "why" misses a valuable
perspective, especially for a language so intertwined with web development.
The rationale for aligning with the majority (and the current RFC) boils
down to the Principle of Least Surprise within PHP's own ecosystem.
PHP is an imperative language. Its standard library functions,
particularly in math, consistently take the value to be operated on as
the first argument: min($value1, $value2)
, max($value1, $value2)
,
round($value, $precision)
. Introducing clamp($min, $value, $max)
would break this pattern, making it the odd one out within PHP itself.
This internal inconsistency would be a greater source of errors for PHP
developers than not matching an external standard like CSS.
The solution to the CSS dissonance is not to change the function but to
document it explicitly:
Note for Web Developers: This function uses the argument order
clamp(value, min, max)
, which differs from the CSS function
clamp(min, value, max)
.
This addresses the concern head-on. The ultimate win is not the argument
order itself, but the mere existence of the function, which finally
banishes the awkward and error-prone max($min, min($max, $value))
incantation.
My 2cents,
--hakre