我在哪里可以找到“钳子”在.NET 的功能?

我想夹一个值 x到一个范围 [a, b]:

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

This is quite basic. But I do not see a function "clamp" in the class library - at least not in System.Math.

(对于不知道“钳制”一个值是为了确保它在一些最大值和最小值之间。如果它大于最大值,那么它就被最大值替换,等等。)

87896 次浏览

System.Math名称空间里没有。

有一个 MathHelper类,它是可用的 XNA 游戏工作室,如果这正好是你正在做的:

没有,但是做一个并不难。我在这里找到了一个: 钳子

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

你可以写一个扩展方法:

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

试试:

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

只需使用 Math.MinMath.Max:

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

只是分享 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;
}

区别:

限制: 没有单面夹子。如果 maxNaN,始终返回 NaN(见 赫尔曼的评论)。

Using the previous answers, I condensed it down to the below code for my needs. This will also allow you to clamp a number only by its min or max.

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

下面的代码支持以任意顺序(即 bound1 <= bound2bound2 <= bound1)指定界限。我发现这对于从线性方程(y=mx+b)计算出的夹紧值很有用,因为线的斜率可以增加也可以减少。

I know: The code consists of five super-ugly conditional expression operators. The thing is, 很管用, and the tests below prove it. Feel free to add strictly unnecessary parentheses if you so desire.

您可以轻松地为其他数值类型创建其他重载,并基本上复制/粘贴测试。

警告: 比较浮点数并不简单。这段代码不能稳健地实现 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);

I an either validate an argument

range.Validate(value);

或者将争论限定在一个范围内:

var v = range.Validate(value);

如果你使用.NET 5 + 、 .NET Core 3.x 或.NET Core 2.x,那么 System.Math.Clamp 就是你想要的方法。

var a = Math.Clamp(5, 1, 10); // = 5
var b = Math.Clamp(-99, 1, 10); // = 1
var c = Math.Clamp(99, 1, 10); // = 10

根据@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;
}
}
}