在哪里可以找到.NET中的“ clamp”功能?


92

我想将值限制x在一个范围内[a, b]

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

这是很基本的。但是我没有在类库中看到函数“ clamp”-至少没有System.Math

(对于不知道“钳位”的值是要确保它介于某个最大值和最小值之间。如果该值大于最大值,则将其替换为最大值,等等。)


2
@Danvil:没有“ C#类库”。您的意思是“ .NET Framework”。
约翰·桑德斯

1
到C#7.1还是什么都没有?
joce

1
@JohnSaunders我不相信这是完全正确stackoverflow.com/questions/807880/...
亚当勒

如果我问如何“限制”一个值,那么世界上每个讲英语的程序员都会立即知道我的意思。每个程序员最有可能知道。在从事业务30多年后,我不得不找出“钳位”今天的含义。与“依赖注入”相似,“参数化”是如此明显,没人能写过这本书。
鲍勃

@Bob有些单词具有历史意义,定义明确。夹钳就是其中之一。en.wikipedia.org/wiki/Clamping_(graphics)khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtmldocs.microsoft.com/en-us/windows/win32/direct3dhlsl/… “限制”会产生误导,尤其是“极限”在数学上已经具有不同的含义。
kaalus

Answers:


135

您可以编写一个扩展方法:

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);

1
我在哪里放置它,并且在调用CompareTo时比与<比较(对于整数类型)要慢?
Danvil 2010年

1
在静态类和.NET框架(不确定单声道,紧凑等)中,应针对类型重新编译泛型,并内联CompareTo,因此不会影响性能。
罗伯特·弗雷泽

1
@Frasier除非这是对性能非常敏感的代码,否则您不可能通过这种方式获得有意义的性能提升。具有通用性可能比节省几微秒有用。
MgSam 2013年

4
约束为通用版本的好处IComparable是不会发生装箱。这应该运行得非常快。请记住,使用double和时float,该CompareTo方法所对应的总阶次NaN小于所有其他值,包括NegativeInfinity。因此它不等同于<运算符。如果使用<浮点类型,则必须考虑如何处理NaN。这与其他数字类型无关。
2013年

1
您需要考虑NaN两种情况下的治疗方法。带有<和的版本>可以输出NaN和使用NaNmin或者max可以有效地制作一个单面夹。有了CompareTo它总是返回NaN如果maxIS NaN
Herman 2014年

29

只需使用Math.MinMath.Max

x = Math.Min(Math.Max(x, a), b);

这就意味着int a0 = x > a ? x : a; return a0 < b ? a0 : b哪一个(尽管给出正确的结果)并不完全理想。
Smith先生

12
那为什么呢?
d7samurai 2014年

4
@ d7samurai如果我们知道min <= max,Math.Min(Math.Max(x, min), max)则如果x <min,则比必要的结果多一个。
Jim Balter 2015年

@JimBalter,理论上是正确的。如果您看一下CompareTo()的典型实现方式,那么可接受的答案最多可以进行6次比较。我不知道,但是编译器是否足够聪明并且内联CompareTo()并删除多余的比较。
quinmars

1
这对于只需要执行一次的情况很有用,然后使用该功能的全新功能似乎有点过头了。
feos

26

尝试:

public static int Clamp(int value, int min, int max)  
{  
    return (value < min) ? min : (value > max) ? max : value;  
}

6
啊! 那些难看的多余的括号!如果您将成为双三元运算符的邪恶天才,至少要正确地做到这一点,并摆脱它们!😂–
XenoRo

8
@XenoRo那些“多余的”括号使它易于阅读。
清晰的

2
@Cleaner-1)如果您出于可读性考虑,将避免使用双三元,而是使用IF块。2)你不开玩笑,是吗?xD
XenoRo

13

没有一个,但是制作一个并不是很难。我在这里找到一个:

它是:

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

此解决方案优于公认的解决方案。没有歧义。
aggsol

6
@CodeClown当值> max时,此解决方案会导致不必要的比较,并且参数的倒序会引发(并实际上保证)错误。我不知道您认为避免了什么歧义。
吉姆·巴尔特

为了与传统Math.Clamp实现保持一致,建议切换最小/最大参数的顺序:Clamp(T value, T min, T max)
josh poley


4

仅在可能的情况下,将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;
}

差异:

局限性:无单侧夹钳。如果max为is NaN,则始终返回NaN(请参阅Herman的注释)。


另一个限制nameof不适用于C#5或更低版本。
RoLYroLLs

0

使用前面的答案,我将其压缩为以下代码以满足我的需求。这也将允许您仅按数字的最小值或最大值来钳位一个数字。

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);呢?
亨里克

0

以下代码支持以任何顺序(即bound1 <= bound2bound2 <= 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);
    }
}

0

如果要验证[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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.