値x
を範囲にクランプしたい[a, b]
:
x = (x < a) ? a : ((x > b) ? b : x);
これは非常に基本的です。しかし、クラスライブラリに「clamp」という関数はありませんSystem.Math
。少なくともにはありません。
(知らないうちに値を「クランプ」することは、いくつかの最大値と最小値の間にあることを確認することです。それが最大値より大きい場合、最大値などに置き換えられます。)
値x
を範囲にクランプしたい[a, b]
:
x = (x < a) ? a : ((x > b) ? b : x);
これは非常に基本的です。しかし、クラスライブラリに「clamp」という関数はありませんSystem.Math
。少なくともにはありません。
(知らないうちに値を「クランプ」することは、いくつかの最大値と最小値の間にあることを確認することです。それが最大値より大きい場合、最大値などに置き換えられます。)
回答:
あなたは拡張メソッドを書くことができます:
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以降System.Math
、Clamp
代わりに使用できるメソッドが追加されました。
using System;
int i = Math.Clamp(4, 1, 3);
IComparable
は、ボクシングが発生しないことです。これは非常に高速に実行する必要があります。とを使用するdouble
とfloat
、このCompareTo
メソッドはをNaN
含む他のすべての値よりも小さい合計注文に対応しますNegativeInfinity
。したがって、<
演算子と同等ではありません。<
浮動小数点型を使用する場合は、処理方法NaN
も考慮する必要があります。これは他の数値型には関係ありません。
NaN
どちらの場合でも、治療方法を検討する必要があります。およびを使用するバージョンでは<
、>
出力としてNaN
使用さNaN
れ、min
またはmax
片側クランプが効果的に作成されます。CompareTo
それは常に返すNaN
場合max
ですNaN
。
ただ使用Math.Min
してMath.Max
:
x = Math.Min(Math.Max(x, a), b);
int a0 = x > a ? x : a; return a0 < b ? a0 : b
は、正しい結果が得られますが、正確ではないということです。
Math.Min(Math.Max(x, min), max)
場合、x <minの場合、必要以上の比較が行われます。
試してください:
public static int Clamp(int value, int min, int max)
{
return (value < min) ? min : (value > max) ? max : value;
}
ありませんが、作るのはそれほど難しくありません。ここで見つけました:クランプ
それは:
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
Clamp(T value, T min, T max)
System.Math名前空間にはありません
http://msdn.microsoft.com/en-us/library/system.math_members.aspx
MathHelperクラスがあり、XNAゲームスタジオで利用できる場合は、それがたまたまそうなのです。
http://msdn.microsoft.com/en-us/library/bb197892(v=XNAGameStudio.31).aspx
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;
}
違い:
ed
)を使用して(さらに)値がインプレースでクランプされておらず、代わりに新しい値が返されることを示します(@JimBalterのコメントを参照)。null check
すべての入力に適切です(@JeppeStigNielsenのコメントを参照)。min
とmax
if min > max
(@JeppeStigNielsenのコメントを参照)。制限:片側クランプはありません。場合max
でNaN
、常に返しますNaN
(参照ハーマンさんのコメント)。
nameof
、C#5以下では機能しません。
以前の回答を使用して、必要に応じて以下のコードに要約しました。これにより、最小値または最大値のみで数値をクランプすることもできます。
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);
?
以下のコードは、任意の順序(つまり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);
}
}
引数の範囲を[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);