.NETの「クランプ」機能はどこにありますか?


92

xを範囲にクランプしたい[a, b]

x = (x < a) ? a : ((x > b) ? b : x);

これは非常に基本的です。しかし、クラスライブラリに「clamp」という関数はありませんSystem.Math。少なくともにはありません。

(知らないうちに値を「クランプ」することは、いくつかの最大値と最小値の間にあることを確認することです。それが最大値より大きい場合、最大値などに置き換えられます。)


2
@Danvil:「C#クラスライブラリ」はありません。「.NET Framework」を意味します。
John Saunders

1
C#7.1の時点ではまだ何もありませんか?
2017年

1
@JohnSaunders私はそれは厳密に本当だとは思わないstackoverflow.com/questions/807880/...
アダム・ネイラー

値を「制限」する方法を尋ねると、世界中のすべての英語を話すプログラマーが私が何を意味するかすぐにわかります。ほとんどのプログラマは知っているでしょう。30年以上のビジネスを経て、今日は「クランプ」が何を意味するのかを調べなければなりませんでした。「依存性注入」に似ています-「パラメータ化」は、誰もそれについて本を書いたことがないような明白なことです。
Bob

@ボブ一部の単語には、歴史的で明確な意味があります。クランプもそのひとつです。en.wikipedia.org/wiki/Clamping_(graphics)またはkhronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtmlまたはdocs.microsoft.com/en-us/windows/win32/direct3dhlsl/… "制限"は誤解を招く可能性があります。特に、"制限 "はすでに数学で別の意味を持っています。
kaalus

回答:


135

あなたは拡張メソッドを書くことができます:

public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
{
    if (val.CompareTo(min) < 0) return min;
    else if(val.CompareTo(max) > 0) return max;
    else return val;
}

編集:拡張メソッドは静的クラスに入ります-これは非常に低レベルの関数であるため、おそらくプロジェクトのコア名前空間に入るはずです。次に、名前空間のusingディレクティブを含む任意のコードファイルでメソッドを使用できます。

using Core.ExtensionMethods

int i = 4.Clamp(1, 3);

.NET Core 2.0

.NET Core 2.0以降System.MathClamp代わりに使用できるメソッドが追加されました。

using System;

int i = Math.Clamp(4, 1, 3);

1
これをどこに配置し、<と比較するよりもCompareToの呼び出しが遅くなりますか(整数型の場合)?
Danvil

1
静的クラスおよび.NETフレームワーク(mono、compactなどについては不明)では、ジェネリックを型用に再コンパイルし、CompareToをインライン化する必要があるため、パフォーマンスが低下することはありません。
Robert Fraser

1
@Frasierこれが超パフォーマンスの影響を受けやすいコードでない限り、そうすることによって意味のあるパフォーマンスの向上を実現することはほとんどありません。汎用的にする方が、数マイクロ秒を節約するよりもおそらく便利です。
MgSam 2013

4
ジェネリックバージョンに制約することの良い点IComparableは、ボクシングが発生しないことです。これは非常に高速に実行する必要があります。とを使用するdoublefloat、このCompareToメソッドはをNaN含む他のすべての値よりも小さい合計注文に対応しますNegativeInfinity。したがって、<演算子と同等ではありません。<浮動小数点型を使用する場合は、処理方法NaNも考慮する必要があります。これは他の数値型には関係ありません。
Jeppe Stig Nielsen

1
NaNどちらの場合でも、治療方法を検討する必要があります。およびを使用するバージョンでは<>出力としてNaN使用さNaNれ、minまたはmax片側クランプが効果的に作成されます。CompareToそれは常に返すNaN場合maxですNaN
ハーマン

29

ただ使用Math.MinしてMath.Max

x = Math.Min(Math.Max(x, a), b);

これint a0 = x > a ? x : a; return a0 < b ? a0 : bは、正しい結果が得られますが、正確ではないということです。
スミス氏

12
なんで?
d7samurai 2014

4
@ d7samurai min <= maxであることがわかっているMath.Min(Math.Max(x, min), max)場合、x <minの場合、必要以上の比較が行われます。
ジムバルター、2015

@JimBalter、理論的にはこれは真実です。CompareTo()が通常どのように実装されているかを見ると、受け入れられた回答は最大6つの比較を取ることができます。しかし、コンパイラが十分にスマートでCompareTo()をインライン化し、余分な比較を削除するかどうかはわかりません。
quinmars 2016

1
これは、一度だけ実行する必要がある場合に適しています。その場合、まったく新しい機能が過剰に感じられます。
feos

26

試してください:

public static int Clamp(int value, int min, int max)  
{  
    return (value < min) ? min : (value > max) ? max : value;  
}

6
うわっ!これらの醜い冗長な括弧!あなたが二重の三項演算子で邪悪な天才になるつもりなら、少なくともそれを正しく行い、それらも取り除く!😂
XenoRo

8
@XenoRoそれらの「冗長な」括弧は、それを読みやすくするものです。
2018年

2
@Cleaner-1)読みやすさを優先する場合は、二重の3値を避け、代わりにIFブロックを使用します。2)冗談がわからないよね?xD
XenoRo 2018年

13

ありませんが、作るのはそれほど難しくありません。ここで見つけました:クランプ

それは:

public static T Clamp<T>(T value, T max, T min)
    where T : System.IComparable<T> {
        T result = value;
        if (value.CompareTo(max) > 0)
            result = max;
        if (value.CompareTo(min) < 0)
            result = min;
        return result;
    }

そしてそれは次のように使用できます:

int i = Clamp(12, 10, 0); -> i == 10
double d = Clamp(4.5, 10.0, 0.0); -> d == 4.5

このソリューションは、受け入れられているソリューションよりも優れています。あいまいさはありません。
aggsol 2015年

6
@CodeClownこのソリューションでは、値> maxの場合に不要な比較が行われ、引数の順序を逆にするとバグが発生します(事実上保証されます)。私はあなたがどんな曖昧さを避けていると思っているのか分かりません。
ジムバルター、2015

レガシーMath.Clamp実装と一貫性を保つため、最小/最大パラメータの順序を切り替えるお勧めします:Clamp(T value, T min, T max)
ジョシュpoleyを


4

Leeのソリューションを、可能な場合は対処されたコメントの問題や懸念と共有するだけです。

public static T Clamped<T>(this T value, T min, T max) where T : IComparable<T> {
    if (value == null) throw new ArgumentNullException(nameof(value), "is null.");
    if (min == null) throw new ArgumentNullException(nameof(min), "is null.");
    if (max == null) throw new ArgumentNullException(nameof(max), "is null.");
    //If min <= max, clamp
    if (min.CompareTo(max) <= 0) return value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value;
    //If min > max, clamp on swapped min and max
    return value.CompareTo(max) < 0 ? max : value.CompareTo(min) > 0 ? min : value;
}

違い:

制限:片側クランプはありません。場合maxNaN、常に返しますNaN(参照ハーマンさんのコメント)。


別の制限はnameof、C#5以下では機能しません。
RoLYroLL、

0

以前の回答を使用して、必要に応じて以下のコードに要約しました。これにより、最小値または最大値のみで数値をクランプすることもできます。

public static class IComparableExtensions
{
    public static T Clamped<T>(this T value, T min, T max) 
        where T : IComparable<T>
    {
        return value.CompareTo(min) < 0 ? min : value.ClampedMaximum(max);
    }

    public static T ClampedMinimum<T>(this T value, T min)
        where T : IComparable<T>
    {
        return value.CompareTo(min) < 0 ? min : value;
    }

    public static T ClampedMaximum<T>(this T value, T max)
        where T : IComparable<T>
    {
        return value.CompareTo(max) > 0 ? max : value;
    }
}

なんでreturn value.ClampedMinimum(min).ClampedMaximum(max);
Henrik

0

以下のコードは、任意の順序(つまりbound1 <= bound2、またはbound2 <= bound1)で境界を指定することをサポートしています。これy=mx+bは、線の傾きが増加または減少する可能性がある線形方程式()から計算された値をクランプするのに役立ちます。

私が知っている:コードは5つの非常に醜い条件式演算子で構成されています。問題は、それが機能し、以下のテストがそれを証明していることです。必要に応じて、厳密に不要な括弧を追加してください。

他の数値型の他のオーバーロードを簡単に作成し、基本的にテストをコピー/貼り付けできます。

警告:浮動小数点数の比較は簡単ではありません。このコードはdouble比較を堅牢に実装していません。浮動小数点比較ライブラリを使用して、比較演算子の使用を置き換えます。

public static class MathExtensions
{
    public static double Clamp(this double value, double bound1, double bound2)
    {
        return bound1 <= bound2 ? value <= bound1 ? bound1 : value >= bound2 ? bound2 : value : value <= bound2 ? bound2 : value >= bound1 ? bound1 : value;
    }
}

xUnit / FluentAssertionsテスト:

public class MathExtensionsTests
{
    [Theory]
    [InlineData(0, 0, 0, 0)]
    [InlineData(0, 0, 2, 0)]
    [InlineData(-1, 0, 2, 0)]
    [InlineData(1, 0, 2, 1)]
    [InlineData(2, 0, 2, 2)]
    [InlineData(3, 0, 2, 2)]
    [InlineData(0, 2, 0, 0)]
    [InlineData(-1, 2, 0, 0)]
    [InlineData(1, 2, 0, 1)]
    [InlineData(2, 2, 0, 2)]
    [InlineData(3, 2, 0, 2)]
    public void MustClamp(double value, double bound1, double bound2, double expectedValue)
    {
        value.Clamp(bound1, bound2).Should().Be(expectedValue);
    }
}

0

引数の範囲を[min、max]で検証したい場合は、次の便利なクラスを使用します。

public class RangeLimit<T> where T : IComparable<T>
{
    public T Min { get; }
    public T Max { get; }
    public RangeLimit(T min, T max)
    {
        if (min.CompareTo(max) > 0)
            throw new InvalidOperationException("invalid range");
        Min = min;
        Max = max;
    }

    public void Validate(T param)
    {
        if (param.CompareTo(Min) < 0 || param.CompareTo(Max) > 0)
            throw new InvalidOperationException("invalid argument");
    }

    public T Clamp(T param) => param.CompareTo(Min) < 0 ? Min : param.CompareTo(Max) > 0 ? Max : param;
}

クラスは、すべてのオブジェクトに対して機能しますIComparable。特定の範囲でインスタンスを作成します。

RangeLimit<int> range = new RangeLimit<int>(0, 100);

私は引数を検証します

range.Validate(value);

または、引数を範囲に固定します。

var v = range.Validate(value);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.