Я хотел бы зафиксировать значение x
в диапазоне [a, b]
:
x = (x < a) ? a : ((x > b) ? b : x);
Это довольно просто. Но я не вижу в библиотеке классов функции "зажим" - по крайней мере, не в 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
Начиная с .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
:источник
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
пространстве имен нет ни одного .Существует
MathHelper
класс, в котором он доступен для игровой студии XNA, если это именно то, что вы делаете:источник
Просто поделитесь решением Ли с рассмотренными проблемами и проблемами в комментариях, где это возможно:
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
если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
), где наклон линии может увеличиваться или уменьшаться.Я знаю: код состоит из пяти супер-уродливых операторов условных выражений . Дело в том, что он работает , и приведенные ниже тесты это подтверждают. Не стесняйтесь добавлять совершенно ненужные скобки, если хотите.
Вы можете легко создавать другие перегрузки для других числовых типов и в основном копировать / вставлять тесты.
Предупреждение: сравнивать числа с плавающей запятой непросто. Этот код не обеспечивает
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);
источник