11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PHPAdvent Calendar 2022

Day 12

【PHP Next】PHPにclamp関数がほしい

Posted at

CCさくらとは何の関係もありません。

clampは何かというと、CSSには既に実装されているのでそれを見るとわかりやすいですが、引数の数値を範囲内に納めてくれるというものです。
たとえばclamp(min=100, max=200)に150を渡すと150が返ってくる、0を渡すと100が返ってくる、300を渡すと200が返ってくる、という感じです。

ということでPHPにもclampがほしいというRFCが提出されました。
以下は該当のRFC、PHP RFC: clampの紹介です。

PHP RFC: clamp

Introduction

clampは、値が範囲内にあるかをチェックします。
範囲内であればその値を返し、範囲外であれば境界値を返します。

ユーザランドの実装は幾つか存在しますが、minmaxを使っているものも存在し、これらはコストがかかるため頻繁に呼ばれると遅くなります。
ユーザランド実装は呼び出しコストを考慮されていないものが多いので、言語での実装が望まれています。

Proposal

このRFCでは、新しい関数clampを提案します。

clamp ( int|float $num, int|float $min, int|float $max ) : int|float

3引数$num$min$maxを取ります。
$num$min$maxの間にあるかを調べ、範囲内であれば$numを返します。
範囲外であれば、$min$maxのうち近い方を返します。
すなわち、$num > $maxであれば$maxを返し、$num < $minであれば$minを返します。

もし$max < $minであれば、無効な値としてValueErrorを投げます。
何れかの値にNANが入ってきたときもValueErrorになります。

例としては以下のようになります。

clamp(num: 1, min: 0, max: 3); // 1 範囲内
clamp(num: 1, min: 2, max: 5); // 2 小さい
clamp(num: 4, min: 1, max: 3); // 3 大きい

clamp(num: 0, min: 2, max: 1); // clamp(): Argument #2 ($min) cannot be greater than Argument #3 ($max)

Backward Incompatible Changes

PHP自身の後方互換性のない変更はありません。
clamp()は関数名として使用できなくなります。

Proposed PHP Version(s)

PHP8.x

Open Issues

NANの取り扱いについて。
#115085 / #115167

Implementation

プルリク

References

類似の実装。
C++
CRAN
CSS

感想

clampはIEEE754-2019で規格が決められているそうです。
見れないからわかりませんが、こんなものも定義されているんですね。

この関数は、PHPでも容易に実装できます。

function clamp(int|float $num, int|float $min, int|float $max):int|float {
    if($max < $min){
        throw new \ValueError('clamp(): Argument #2 ($min) cannot be greater than Argument #3 ($max)');
    }
    if($max < $num){
        return $max;
    }
    if($min > $num){
        return $min;
    }
    return $num;
}

プルリクでのCによる実装とほぼ同じです。

	if (zend_compare(zmin, zmax) > 0) {
		zend_argument_value_error(2, "must be smaller than or equal to argument #3 ($max)");
		RETURN_THROWS();
	}
	if (zend_compare(zmax, zvalue) == -1) {
		RETURN_COPY_VALUE(zmax);
	} else if (zend_compare(zvalue, zmin) == -1) {
		RETURN_COPY_VALUE(zmin);
	} else {
		RETURN_COPY_VALUE(zvalue);
	}

従って、どうしても必要な関数というわけではなく、str_contains同様なくてもどうにかなるけどあれば便利なお助け関数という立ち位置ですね。

このRFC、2021年6月に提出され、その後一か月ほどでコードの実装は完成しました。
しかしその後ぱったりと消息が途絶え、進捗が停滞してしまいました。
作者は他のプロジェクトには様々にコミットしているので、飽きたか面倒臭くなったかでしょうか。

しかしその後も、これ欲しいよーというコメントがプルリクにちらほら追加されているので、もしかしたら今後レスキューされるかもしれません。
されないかもしれません。

ちなみに、以下がおそらくもっとも簡単な実装ですが、同時にRFCで言うところの遅い実装でしょう。

function clamp(int|float $num, int|float $min, int|float $max):int|float {
    return max($min, min($max, $num));
}

ベンチ取ったわけじゃないからわかりませんが、見るからに遅そうですね。
しかし、この程度で有意差が出るほどclampを大量に呼び出すのっていったいどういうユースケースなんだろうか。

11
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?