Где я могу найти функцию зажима в .NET?

Я хотел бы ограничить значение x диапазоном [a, b]:

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

Это довольно просто. Но я не вижу функции "зажим" в библиотеке классов - по крайней мере, в System.Math.

(Для неосведомленных «зажимать» значение - это убедиться, что оно находится между некоторыми максимальными и минимальными значениями. Если оно больше максимального значения, оно заменяется максимальным и т. Д.)


person Danvil    schedule 21.04.2010    source источник
comment
@Danvil: библиотеки классов C # нет. Вы имеете в виду .NET Framework.   -  person John Saunders    schedule 21.04.2010
comment
По-прежнему ничего по сравнению с C # 7.1?   -  person joce    schedule 03.05.2017
comment
@JohnSaunders Я не верю, что это строго правда stackoverflow.com/questions/807880/   -  person Adam Naylor    schedule 19.06.2017
comment
Если бы я спросил, как ограничить значение, каждый англоговорящий программист в мире сразу понял бы, что я имел в виду. Скорее всего, это знает каждый программист. После 30 с лишним лет в бизнесе мне пришлось выяснить, что сегодня означает зажим. Подобно внедрению зависимостей, параметризация - такая очевидная вещь, о которой никто никогда не писал книги.   -  person Bob    schedule 05.03.2019
comment
@Bob Некоторые слова имеют историческое, четко определенное значение. Зажим - один из них. en.wikipedia.org/wiki/Clamping_(graphics) или khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtml или docs.microsoft.com/ en-us / windows / win32 / direct3dhlsl / Ограничение может ввести в заблуждение, особенно это ограничение уже имеет другое значение в математике.   -  person kaalus    schedule 18.05.2020
comment
@lkaalus. Чтобы зажать что-либо, нужно удерживать это в одной точке. Ограничить что-либо - значит ограничить это диапазоном. Математический предел - это предел функции, поскольку связанная переменная стремится к значению - fnc имеет диапазон, зависящий от диапазона переменной. Зажим здесь используется для обозначения того, что определяется пределом. Тот, кто определил это как таковое (ur ссылки), был неправ в то время, и любой, кто понимает их значение, просто усугубляет ситуацию - поскольку большая часть более шумного / модного элемента ИТ-индустрии обычно не связана практически со всем, что говорит любой идейный лидер.   -  person Bob    schedule 18.05.2020
comment
Для функции, ограничивающей значение диапазоном, ограничение, ограничение, ограничение, регулирование, ограничение, управление и ограничение было бы разумным и интуитивно понятным выбором. Я уверен, что есть еще кое-что. Clamp - очень плохой выбор: он не только не описывает то, что делает fnc, но и описывает то, что fnc не делает (за исключением особого случая), если, как здесь, вы не согласились, что отныне он будет что-то значить. никогда раньше не делал: это похоже на решение, что, когда мы используем слово Aardvark в контексте нашего конкретного предмета, мы на самом деле будем иметь в виду слона.   -  person Bob    schedule 20.05.2020
comment
Теперь он добавлен в .NET 5.0.   -  person Zimano    schedule 10.12.2020


Ответы (10)


Вы можете написать метод расширения:

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);
person Lee    schedule 21.04.2010
comment
Куда бы я поместил это и вызывает CompareTo медленнее, чем сравнение с ‹(для целочисленных типов)? - person Danvil; 21.04.2010
comment
В статическом классе и в платформе .NET (не уверен в моно, компактности и т. Д.) Общий тип должен быть перекомпилирован для типа, а CompareTo встроен, чтобы не снижать производительность. - person Robert Fraser; 21.04.2010
comment
@Frasier Если это не особо чувствительный к производительности код, вы вряд ли добьетесь значительного прироста производительности, сделав это. Иметь общий вид, вероятно, полезнее, чем сэкономить несколько микросекунд. - person MgSam; 06.06.2013
comment
Хорошая вещь в ограничении общей версией IComparable заключается в том, что упаковки не происходит. Это должно работать очень быстро. Помните, что с double и float метод CompareTo соответствует общему порядку, где NaN меньше всех других значений, включая NegativeInfinity. Таким образом, он не эквивалентен оператору <. Если вы использовали < с типом с плавающей запятой, вам также следует подумать, как обращаться с NaN. Это не актуально для других числовых типов. - person Jeppe Stig Nielsen; 08.08.2013
comment
Вам нужно будет подумать, как обращаться с NaN в любом случае. Версия с < и > выдала бы NaN, а использование NaN вместо min или max фактически сделало бы односторонний зажим. С CompareTo он всегда будет возвращать NaN, если max равно NaN. - person Herman; 18.03.2014
comment
Хорошее решение Ли. Не менее полезный метод расширения для некоторых вещей в том же духе: общедоступный статический T TrapInvalid ‹T› (это T val, T min, T max, T valueIfInvalid), где T: IComparable ‹T› {if (val.CompareTo (min ) ‹0) return valueIfInvalid; иначе, если (val.CompareTo (max) ›0) return valueIfInvalid; else return val; } - person radsdau; 15.05.2014
comment
Статическая функция была бы лучше, поскольку расширение предполагает, что метод сам меняет тип. Но это не так! - person aggsol; 16.01.2015
comment
@CodeClown - методы расширения - это статические методы, поэтому вы можете называть их int c = Clamp(a, b, c), если хотите. Однако я не понимаю, как эта реализация предполагает изменение типа. Если вы имеете в виду, что это подразумевает, что параметр this изменяется, я не согласен, поскольку он возвращает значение. - person Lee; 16.01.2015
comment
@Lee - См. Пример Отсутствие расширения предотвратит ошибку, поскольку сам foo никогда не фиксируется. Было бы очевидно, если бы у расширения было более подробное имя, например CalcClampedVal. Но теперь, когда я думаю об этом больше, мою проблему можно считать незначительной. - person aggsol; 16.01.2015
comment
@CodeClown - я вижу проблему, хотя думаю, что ваш пример приведет к предупреждению об игнорируемом возвращаемом значении. Преимущество использования метода расширения заключается в том, что он делает фиксированный аргумент явным. - person Lee; 16.01.2015
comment
Я бы добавил ограничение 'struct' на этот общий тип T, чтобы мы случайно не использовали его для ссылочных типов и не рисковали исключить null, если, конечно, вы не хотите использовать его для ссылочных типов. Эти два оператора else также излишни. - person angularsen; 19.04.2015
comment
Для методов вполне нормально возвращать преобразованную версию аргумента; они обязательно видоизменяют объект. Распространенным соглашением является использование подходящего глагольного времени, например, list.Sort для сортировки по месту и list.Sorted для возврата отсортированной копии списка. По такому соглашению это следует называть Clamped, а не Clamp. - person Jim Balter; 15.09.2015
comment
Еще два замечания: (1) Вы не ограничиваетесь типами значений, поэтому, если T является ссылочным типом, а val - это null, мы получаем NullReferenceException. Обратите внимание, что Comparer<T>.Default.Compare(val, min) будет считать null меньше ненулевого, поэтому вы можете использовать это, если хотите. Или проверьте значение null. Или ограничьтесь where T : struct. (2) Если какой-нибудь идиот однажды передаст min и max, которые находятся в неправильном относительном порядке, тогда (ненамеренно) становится важным, чтобы вы сначала проверили с min. (Поскольку val может удовлетворять обоим условиям.) В этом случае мы должны выбросить исключение. - person Jeppe Stig Nielsen; 18.09.2017

Просто используйте Math.Min и Math.Max:

x = Math.Min(Math.Max(x, a), b);
person d7samurai    schedule 07.12.2013
comment
Это означает int a0 = x > a ? x : a; return a0 < b ? a0 : b, который (хотя и дает правильные результаты) не совсем идеален. - person Mr. Smith; 24.04.2014
comment
@ d7samurai Если мы знаем, что min ‹= max, Math.Min(Math.Max(x, min), max) дает на одно сравнение больше, чем необходимо, если x‹ min. - person Jim Balter; 15.09.2015
comment
@JimBalter, теоретически это правда. Если вы посмотрите, как обычно реализуется CompareTo (), принятый ответ может потребовать до 6 сравнений. Я не знаю, однако, достаточно ли умен компилятор, встроит ли CompareTo () и удалит ли лишние сравнения. - person quinmars; 07.09.2016
comment
Это хорошо для случаев, когда вам нужно сделать это только один раз, и тогда совершенно новая функция для этого кажется излишней. - person feos; 30.11.2017

Пытаться:

public static int Clamp(int value, int min, int max)  
{  
    return (value < min) ? min : (value > max) ? max : value;  
}
person Clit    schedule 14.06.2010
comment
Фу! Эти уродливые повторяющиеся скобки! Если вы собираетесь стать злым гением с двойными тернарными операторами, по крайней мере, сделайте это правильно и избавьтесь от них! ???? - person XenoRo; 01.10.2017
comment
@XenoRo Эти лишние скобки делают его читабельным. - person Clearer; 11.05.2018
comment
@Cleaner - 1) Если вы стремитесь к удобочитаемости, следует избегать двойных троек и вместо них использовать блоки IF. 2) Вы не поняли анекдота? xD - person XenoRo; 11.05.2018

Его нет, но сделать его не так уж и сложно. Я нашел здесь: зажим

It is:

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
person Jeremy B.    schedule 21.04.2010
comment
Это решение лучше принятого. Никакой двусмысленности. - person aggsol; 16.01.2015
comment
@CodeClown Это решение приводит к ненужному сравнению, когда значение ›max и инвертированный порядок аргументов вызывают (и фактически гарантируют) ошибки. Я не знаю, какой двусмысленности, по вашему мнению, можно избежать. - person Jim Balter; 15.09.2015
comment
Для согласованности с устаревшей реализацией Math.Clamp рекомендуется изменить порядок параметров min / max: Clamp(T value, T min, T max) - person josh poley; 15.02.2020

Его нет в System.Math пространстве имен.

Существует класс MathHelper, где он доступен для XNA game studio, если это именно то, что вы делаете:

person kemiller2002    schedule 21.04.2010
comment
Теперь есть: _1 _ - person Arad; 21.12.2020

Просто поделитесь решением Ли с указанными в комментариях проблемами и проблемами, где это возможно:

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's комментарий).
  • Поменяйте местами min и max, если min > max (см. @ JeppeStigNielsen's комментарий).

Ограничения: Нет односторонних зажимов. Если max равно NaN, всегда возвращает NaN (см. Комментарий Германа).

person XenoRo    schedule 02.10.2017
comment
Еще одно ограничение: nameof не работает для C # 5 или ниже. - person RoLYroLLs; 24.07.2018

Используя предыдущие ответы, я сократил его до приведенного ниже кода для своих нужд. Это также позволит вам ограничивать число только его минимальным или максимальным значением.

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;
    }
}
person Bobby Speirs    schedule 11.12.2017
comment
Почему не return value.ClampedMinimum(min).ClampedMaximum(max);? - person Henrik; 16.01.2019

Приведенный ниже код поддерживает указание границ в любом порядке (например, 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);
    }
}
person NathanAldenSr    schedule 28.03.2019

Если я хочу проверить диапазон аргумента в [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);
person Rabbid76    schedule 24.02.2020

System.Math.Clamp

это то, что вы ищете, если используете .NET 5, .NET Core 2.x, 3.x ...

person juFo    schedule 29.01.2021