Где я могу найти функцию "зажима" в.NET?
Я хотел бы зафиксировать значение x
в диапазоне [a, b]
:
x = (x < a) ? a : ((x > b) ? b : x);
Это довольно просто. Но я не вижу функцию "зажим" в библиотеке классов - по крайней мере, не в System.Math
,
(Для того, чтобы не "зажать" значение, необходимо убедиться, что оно лежит между некоторыми максимальными и минимальными значениями. Если оно больше максимального значения, оно заменяется максимальным и т. Д.)
11 ответов
Вы можете написать метод расширения:
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);
Просто используйте Math.Min
а также Math.Max
:
x = Math.Min(Math.Max(x, a), b);
Пытаться:
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
В пространстве имен 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
System.Math.Clamp
это то, что вам нужно, если вы используете .NET 5, .NET Core 2.x, 3.x...
Просто поделитесь решением Ли с обсуждаемыми проблемами и проблемами в комментариях, где это возможно:
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
(См . Комментарий Германа).
Если я хочу проверить диапазон аргумента в [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);
Приведенный ниже код поддерживает указание границ в любом порядке (т.е. 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);
}
}
Используя предыдущие ответы, я сократил его до приведенного ниже кода для своих нужд. Это также позволит вам фиксировать число только его мин или макс.
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;
}
}
На основе ответа @JeremyB с предложенными исправлениями.
namespace App
{
/// <summary>
/// Miscellaneous utilities.
/// </summary>
public static class Util
{
/// <summary>
/// Clamp a value to the inclusive range [min, max].
/// </summary>
/// <remarks>
/// In newer versions of the .NET Framework, there is a System.Math.Clamp() method.
/// </remarks>
/// <typeparam name="T">The type of value.</typeparam>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static T clamp<T>( T value, T min, T max ) where T : System.IComparable<T>
{
if ( value.CompareTo( max ) > 0 )
{
return max;
}
if ( value.CompareTo( min ) < 0 )
{
return min;
}
return value;
}
}
}