在.NET中,我该在哪里找到“clamp”函数?

124

我想将一个值 x 限制在区间 [a, b] 内:

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

这很基础。但我在类库中没有看到“clamp”函数,至少不在System.Math中。

(对于不知道的人,“clamp”一个值是确保它在某些最大和最小值之间。如果它大于最大值,则替换为最大值等。)


3
@Danvil:没有所谓的“C#类库”。你指的是“.NET Framework”。 - John Saunders
2
到 C# 7.1 ,还是没有任何进展吗? - joce
2
@JohnSaunders 我不相信那是绝对正确的。https://dev59.com/NXRA5IYBdhLWcg3w1BqW - Adam Naylor
2
@Bob 有些词汇具有历史意义和明确定义。Clamp就是其中之一。https://en.wikipedia.org/wiki/Clamping_(graphics)或https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/clamp.xhtml或https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-clamp。“限制”这个译词会让人产生误解,尤其是在数学中“limit”已经有了不同的含义。 - kaalus
1
Bob,真的希望你喜欢成为程序员的这个晚期转变。祝你好运! CSS: https://css-tricks.com/snippets/sass/clamping-number C#: https://www.tutorialkart.com/c-sharp-tutorial/c-sharp-math-clamp/ Java: https://www.demo2s.com/java/java-math-clamp-int-val-int-min-int-max.html Rust: https://docs.rs/num/0.2.1/num/fn.clamp.html C++: https://www.geeksforgeeks.org/stdclamp-in-cpp-17/ Boost: https://www.geeksforgeeks.org/boostalgorithmclamp-in-c-library/ Ruby: https://jemma.dev/blog/comparable-clamp - Kaitain
显示剩余5条评论
11个回答

174

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

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
1
@Frasier 除非这是超高性能敏感的代码,否则你不太可能通过这样做获得任何有意义的性能提升。让它变成通用的可能比节省几微秒更有用。 - MgSam
6
约束使用“通用”版本的 IComparable 的好处是不会发生装箱操作。这应该运行非常快。请记住,对于 doublefloatCompareTo 方法对应于一个全序,其中 NaN 小于所有其他值,包括 NegativeInfinity。因此它与 < 运算符不等价。如果您在浮点类型中使用 <,您还必须考虑如何处理 NaN。这对于其他数字类型不相关。 - Jeppe Stig Nielsen
1
无论哪种情况,您都需要考虑如何处理 NaN。使用 <> 的版本将输出 NaN,在 minmax 中使用 NaN 将有效地产生单侧夹紧。如果 maxNaN ,则使用 CompareTo 将始终返回 NaN - Herman
1
@CodeClown - 扩展方法是静态方法,因此您可以像这样调用它:int c = Clamp(a, b, c)。但是,我不明白这个实现如何暗示类型正在被更改。如果您的意思是它暗示了this参数正在被改变,那么我不同意,因为它返回一个值。 - Lee
显示剩余8条评论

36

只需使用 Math.MinMath.Max

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

这意味着 int a0 = x > a ? x : a; return a0 < b ? a0 : b,虽然可以得到正确的结果,但并不是最理想的。 - Mr. Smith
4
如果我们知道 min <= max,当 x < min 时,使用 Math.Min(Math.Max(x, min), max) 将导致比必要多进行一次比较。 - Jim Balter
@JimBalter,理论上来说这是正确的。如果你看一下CompareTo()通常是如何实现的,那么接受的答案可能需要进行多达6次比较。但我不知道编译器是否足够聪明,能够内联CompareTo()并删除多余的比较。 - quinmars
2
这种情况下,只需要执行一次的话,为此编写一个全新的函数会感觉有些过度设计。 - feos

25

尝试:

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

10
啊!那些丑陋的冗余括号!如果你打算用双重三元运算符做一个邪恶的天才,至少也要把那些括号去掉啊! - CosmicGiant
19
那些“多余”的括号是使它易读的关键。 - Clearer
3
1)如果你追求易读性,应该避免使用双重三目运算符,而是使用 IF 代码块。 2)你不懂这个笑话,对吧?xD - CosmicGiant

13

目前并没有内置的该功能, 但制作一个不是很难。这里有一个通用的实现: 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

这个解决方案比被接受的更好。没有歧义。 - aggsol
6
这个解决方案在 value > max 时会造成不必要的比较,倒置的参数顺序容易引起(并几乎保证)错误。我不知道你认为避免了什么歧义。 - Jim Balter
为了与传统的Math.Clamp实现保持一致,建议交换min/max参数的顺序:Clamp(T value, T min, T max) - josh poley

10

现在有:Math.Clamp - aradalvand

5

System.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

4

分享一下 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;
}

差异:

限制:没有单侧夹紧。如果maxNaN,则始终返回NaN(请参见赫尔曼的评论)。


另一个限制是 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);呢? - Henrik

0

基于 @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;
    }
  }
}

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接