我想将一个值 x
限制在区间 [a, b]
内:
x = (x < a) ? a : ((x > b) ? b : x);
这很基础。但我在类库中没有看到“clamp”函数,至少不在System.Math
中。
(对于不知道的人,“clamp”一个值是确保它在某些最大和最小值之间。如果它大于最大值,则替换为最大值等。)
我想将一个值 x
限制在区间 [a, b]
内:
x = (x < a) ? a : ((x > b) ? b : x);
这很基础。但我在类库中没有看到“clamp”函数,至少不在System.Math
中。
(对于不知道的人,“clamp”一个值是确保它在某些最大和最小值之间。如果它大于最大值,则替换为最大值等。)
你可以编写一个扩展方法:
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开始,System.Math
现在拥有了一个可以代替的Clamp
方法:
using System;
int i = Math.Clamp(4, 1, 3);
IComparable
的好处是不会发生装箱操作。这应该运行非常快。请记住,对于 double
和 float
,CompareTo
方法对应于一个全序,其中 NaN
小于所有其他值,包括 NegativeInfinity
。因此它与 <
运算符不等价。如果您在浮点类型中使用 <
,您还必须考虑如何处理 NaN
。这对于其他数字类型不相关。 - Jeppe Stig NielsenNaN
。使用 <
和 >
的版本将输出 NaN
,在 min
或 max
中使用 NaN
将有效地产生单侧夹紧。如果 max
是 NaN
,则使用 CompareTo
将始终返回 NaN
。 - Hermanint c = Clamp(a, b, c)
。但是,我不明白这个实现如何暗示类型正在被更改。如果您的意思是它暗示了this
参数正在被改变,那么我不同意,因为它返回一个值。 - Lee只需使用 Math.Min
和 Math.Max
:
x = Math.Min(Math.Max(x, a), b);
int a0 = x > a ? x : a; return a0 < b ? a0 : b
,虽然可以得到正确的结果,但并不是最理想的。 - Mr. SmithMath.Min(Math.Max(x, min), max)
将导致比必要多进行一次比较。 - Jim Balter尝试:
public static int Clamp(int value, int min, int max)
{
return (value < min) ? min : (value > max) ? max : value;
}
目前并没有内置的该功能, 但制作一个不是很难。这里有一个通用的实现: clamp
实现方法如下:
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)
。 - josh poleySystem.Math.Clamp
是您在.NET 5+、.NET Core 3.x或.NET Core 2.x上想要使用的方法。
var a = Math.Clamp(5, 1, 10); // = 5
var b = Math.Clamp(-99, 1, 10); // = 1
var c = Math.Clamp(99, 1, 10); // = 10
分享一下 Lee's solution,并尽可能解决评论中的问题和疑虑:
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检查
(请参见@JeppeStigNielsen的评论)。min>max
,则交换min
和max
(请参见@JeppeStigNielsen的评论)。限制:没有单侧夹紧。如果max
是NaN
,则始终返回NaN
(请参见赫尔曼的评论)。
nameof
不适用于 C# 5 或更低版本。 - RoLYroLLspublic 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);
呢? - Henrik基于 @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;
}
}
}
以下代码支持以任意顺序指定界限(即 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);
}
}