Chciałbym ograniczyć wartość x
do zakresu [a, b]
:
x = (x < a) ? a : ((x > b) ? b : x);
To jest dość podstawowe. Ale nie widzę funkcji „clamp” w bibliotece klas - przynajmniej nie w System.Math
.
(Dla nieświadomych „ograniczenia” wartości jest upewnienie się, że mieści się między pewnymi wartościami maksymalnymi i minimalnymi. Jeśli jest większa niż wartość maksymalna, jest zastępowana przez wartość maksymalną itp.)
Odpowiedzi:
Możesz napisać metodę rozszerzenia:
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; }
Metody rozszerzające znajdują się w klasach statycznych - ponieważ jest to funkcja niskiego poziomu, prawdopodobnie powinna znajdować się w jakiejś podstawowej przestrzeni nazw w Twoim projekcie. Możesz następnie użyć metody w dowolnym pliku kodu, który zawiera dyrektywę using dla przestrzeni nazw, np
using Core.ExtensionMethods int i = 4.Clamp(1, 3);
.NET Core 2.0
Począwszy od .NET Core 2.0
System.Math
ma terazClamp
metodę, której można użyć zamiast tego:using System; int i = Math.Clamp(4, 1, 3);
źródło
IComparable
jest to, że nie występuje boks. To powinno przebiegać bardzo szybko. Pamiętaj, że zdouble
ifloat
,CompareTo
metoda odpowiada całkowitej kolejności, gdzieNaN
jest mniejsza niż wszystkie inne wartości, w tymNegativeInfinity
. Więc to nie jest równoważne z<
operatorem. Jeśli używasz<
z typem zmiennoprzecinkowym, musisz rozważyć, jakNaN
również traktować . Nie dotyczy to innych typów liczbowych.NaN
w obu przypadkach. Wersja z<
i>
będzie wyjściaNaN
i używaniaNaN
namin
lubmax
skutecznie dokonać jednostronnego zacisku. DziękiCompareTo
temu zawsze wróci,NaN
jeślimax
jestNaN
.Po prostu użyj
Math.Min
iMath.Max
:źródło
int a0 = x > a ? x : a; return a0 < b ? a0 : b
(chociaż daje prawidłowe wyniki) nie jest do końca idealne.Math.Min(Math.Max(x, min), max)
daje o jedno porównanie więcej niż to konieczne, jeśli x <min.Próbować:
public static int Clamp(int value, int min, int max) { return (value < min) ? min : (value > max) ? max : value; }
źródło
Nie ma jednego, ale nie jest trudno go stworzyć. Znalazłem jeden tutaj: zacisk
To jest:
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; }
I może być używany jak:
int i = Clamp(12, 10, 0); -> i == 10 double d = Clamp(4.5, 10.0, 0.0); -> d == 4.5
źródło
Clamp(T value, T min, T max)
Nie ma takiego w
System.Math
przestrzeni nazw .Jest
MathHelper
klasa, w której jest dostępna dla studia gier XNA, jeśli tak się dzieje:źródło
Po prostu podzielenie się rozwiązaniem Lee z omówionymi w komentarzach problemami i wątpliwościami, jeśli to możliwe:
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; }
Różnice:
ed
), aby (dalej) wskazać, że wartość nie jest blokowana w miejscu i że zamiast tego zwracana jest nowa wartość (patrz komentarz @ JimBalter ).null check
na wszystkich wejściach (patrz komentarz @ JeppeStigNielsen ).min
imax
jeślimin > max
(patrz komentarz @ JeppeStigNielsen ).Ograniczenia: brak zacisków jednostronnych. Jeśli
max
takNaN
, zawsze zwracaNaN
(zobacz komentarz Hermana ).źródło
nameof
nie działa w języku C # 5 lub starszym.Korzystając z poprzednich odpowiedzi, skondensowałem je do poniższego kodu dla moich potrzeb. Pozwoli to również na ograniczenie liczby tylko przez jej min. Lub maks.
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; } }
źródło
return value.ClampedMinimum(min).ClampedMaximum(max);
?Poniższy kod obsługuje określanie granic w dowolnej kolejności (tj.
bound1 <= bound2
Lubbound2 <= bound1
). Zauważyłem, że jest to przydatne do mocowania wartości obliczonych z równań liniowych (y=mx+b
), w których nachylenie linii może rosnąć lub maleć.Wiem: kod składa się z pięciu bardzo brzydkich operatorów wyrażeń warunkowych . Rzecz w tym, że działa , a poniższe testy to potwierdzają. Jeśli sobie tego życzysz, możesz dodać niepotrzebne nawiasy.
Możesz łatwo utworzyć inne przeciążenia dla innych typów liczbowych i po prostu skopiować / wkleić testy.
Ostrzeżenie: porównywanie liczb zmiennoprzecinkowych nie jest proste. Ten kod nie implementuje
double
solidnie porównań. Użyj biblioteki porównawczej zmiennoprzecinkowej, aby zastąpić użycie operatorów porównania.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; } }
Testy 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); } }
źródło
Jeśli chcę sprawdzić zakres argumentu w [min, max], używam następującej przydatnej klasy:
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; }
Klasa działa dla wszystkich obiektów, które są
IComparable
. Tworzę instancję o określonym zakresie:RangeLimit<int> range = new RangeLimit<int>(0, 100);
Albo potwierdzam argument
range.Validate(value);
lub ogranicz argument do zakresu:
var v = range.Validate(value);
źródło