# [RFC] [Discussion] Add clamp function

- Thread: https://externals.io/message/128570
- Original archive: https://news-web.php.net/php.internals/128570
- Started by Kyle Katarn on 2025-08-27T13:34:53+00:00
- Messages: 22
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #128570 — Kyle Katarn — 2025-08-27T13:34:53+00:00

URL: https://externals.io/message/128570#128570

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,

---

## #128572 — Marco Pivetta — 2025-08-27T15:45:59+00:00

URL: https://externals.io/message/128570#128572 · In reply to: #128570

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:

```php
/**
 * @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

https://mastodon.social/@ocramius

https://ocramius.github.io/

---

## #128573 — Bob Weinand — 2025-08-27T16:06:44+00:00

URL: https://externals.io/message/128570#128573 · In reply to: #128570

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/4814
>
> Thanks,


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

---

## #128574 — Larry Garfield — 2025-08-27T16:40:51+00:00

URL: https://externals.io/message/128570#128574 · In reply to: #128573

> Hey,
>
> 
> > […]
>
>
> 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

---

## #128575 — Bob Weinand — 2025-08-27T16:48:16+00:00

URL: https://externals.io/message/128570#128575 · In reply to: #128574

Thanks Larry!


> 
> > […]
> 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

---

## #128576 — Yitzchok Willroth — 2025-08-27T16:56:47+00:00

URL: https://externals.io/message/128570#128576 · In reply to: #128574

On Wed, Aug 27, 2025, 12:44 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> 
> > […]
> https://wiki.php.net/rfc/clamp_v2
> > […]
> I updated its status.
> > […]
> https://github.com/php/php-src/pull/19434
> > […]
> https://github.com/php/doc-en/pull/4814
> > […]
>
> 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

---

## #128577 — Hans Krentel — 2025-08-28T06:35:08+00:00

URL: https://externals.io/message/128570#128577 · In reply to: #128572

> 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

---

## #128578 — Hans Krentel — 2025-08-28T06:36:52+00:00

URL: https://externals.io/message/128570#128578 · In reply to: #128574

> ...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

---

## #128579 — tim@bastelstu.be — 2025-08-28T07:29:58+00:00

URL: https://externals.io/message/128570#128579 · In reply to: #128572

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

---

## #128580 — tim@bastelstu.be — 2025-08-28T07:37:43+00:00

URL: https://externals.io/message/128570#128580 · In reply to: #128577

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

---

## #128581 — Morgan — 2025-08-28T10:59:27+00:00

URL: https://externals.io/message/128570#128581 · In reply to: #128580

> 
> 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.

---

## #128582 — tim@bastelstu.be — 2025-08-28T11:28:43+00:00

URL: https://externals.io/message/128570#128582 · In reply to: #128581

Hi

> > […]
> 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

---

## #128586 — Hans Krentel — 2025-08-28T16:54:54+00:00

URL: https://externals.io/message/128570#128586 · In reply to: #128582

> Hi
> 
> > […]
> 
> 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

---

## #128587 — Niels Dossche — 2025-08-28T18:23:55+00:00

URL: https://externals.io/message/128570#128587 · In reply to: #128586

> 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.

---

## #128588 — Kyle Katarn — 2025-08-28T18:26:08+00:00

URL: https://externals.io/message/128570#128588 · In reply to: #128580

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 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
>

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.

---

## #128590 — Larry Garfield — 2025-08-28T19:48:17+00:00

URL: https://externals.io/message/128570#128590 · In reply to: #128588

> Hi,
>
> 2025-08-28 at 09:37, Tim Düsterhus <tim@bastelstu.be> wrote :

> > […]
>
> 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.

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

---

## #128591 — tim@bastelstu.be — 2025-08-28T20:36:50+00:00

URL: https://externals.io/message/128570#128591 · In reply to: #128588

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

---

## #128592 — Morgan — 2025-08-29T02:42:23+00:00

URL: https://externals.io/message/128570#128592 · In reply to: #128591

> 
> 
> 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).

---

## #128593 — tim@bastelstu.be — 2025-08-29T13:10:11+00:00

URL: https://externals.io/message/128570#128593 · In reply to: #128592

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

---

## #128595 — Morgan — 2025-08-29T21:11:39+00:00

URL: https://externals.io/message/128570#128595 · In reply to: #128593

> Hi
> 
> 
> > […]
> 
> 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.

> > […]
> 
> 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

---

## #128651 — Kyle Katarn — 2025-09-08T11:46:30+00:00

URL: https://externals.io/message/128570#128651 · In reply to: #128593

Hello

2025-08-29 at 15:12, Tim Düsterhus <tim@bastelstu.be> wrote:

> Hi
>
> 
> > […]
>
> 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 positive that the benchmarks will answer that question.
>
> Best regards
> Tim Düsterhus
>

I back-ported "not cost-effective when called multiple times" from the
clamp v1, I think its author meant "compared to user-land implementation
using min(max())"

So I just did this benchmark:
https://github.com/kylekatarnls/clamp-perf-test/actions/runs/17546601109/job/49829393835
(following Tim's advice to use hyperfine for it).

Summary is:
php clamp.php ran
1.06 ± 0.02 times faster than php ternary.php
1.42 ± 0.03 times faster than php min-max.php

Which means if everything is correct, then the current native
implementation proposal is barely faster than a user-land function using
ternary implementation.

Using a user-land function and min(max()) is 42% slower.

(See the files at the root of this repo:
https://github.com/kylekatarnls/clamp-perf-test)

Those numbers might have been better in the first implementation (which
supported only int and float and it had no special case handling for NAN,
so I guess it was even a bit faster this way).

Anyway, I think the speed is probably not the main reason why we should add
it. Maybe I would rather phrase the above result this way:
- User-land implementation for clamp using ternary is notably faster than
min(max()) user-land implementation
  -> but still the difference shouldn't be a concern for most people.
- With the native implementation, you get (for no extra speed cost):
  -> `NAN` handling
  -> verification than min < max
  -> a standard way (framework/application can drop their own
implementations and converge to the one propose by the language which will
also be pretty similar with most implementations in other languages)

So I guess I should arrange the note about cost-effectiveness, or maybe
drop it all together.

---

## #128716 — Kyle Katarn — 2025-09-21T16:47:23+00:00

URL: https://externals.io/message/128570#128716 · In reply to: #128570

Hi,

2025-08-27 at 15:34, Kyle Katarn <kylekatarnls@gmail.com> wrote:

> 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,
>

If there is nothing left to discuss, I'd like to open the votes for this
function adding.
