.NET에서 "클램프"기능은 어디에서 찾을 수 있습니까?


95

x을 범위 로 고정하고 싶습니다 [a, b].

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

이것은 아주 기본적인 것입니다. 하지만 클래스 라이브러리에서 "클램프"함수를 볼 수 없습니다 System.Math. 적어도 .

( "클램핑"하는 것을 모르는 사람에게는 값이 최대 값과 최소값 사이에 있는지 확인하는 것입니다. 최대 값보다 크면 최대 값 등으로 대체됩니다.)


2
@Danvil : "C # 클래스 라이브러리"가 없습니다. ".NET Framework"를 의미합니다.
John Saunders

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.xhtml 또는 docs.microsoft.com/en-us/windows/win32/direct3dhlsl/… "제한 "는 오해의 소지가 있습니다. 특히"한계 "는 이미 수학에서 다른 의미를 가지고 있습니다.
kaalus

답변:


137

확장 메서드를 작성할 수 있습니다.

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
정적 클래스 및 .NET 프레임 워크 (mono, compact 등에 대해 확실하지 않음)에서 제네릭은 형식에 대해 다시 컴파일되고 CompareTo 인라인되므로 성능 저하가 없습니다.
Robert Fraser

1
@Frasier 이것이 성능에 매우 민감한 코드가 아니라면 그렇게함으로써 의미있는 성능 향상을 얻을 가능성은 거의 없습니다. 일반적으로 사용하는 것이 몇 마이크로 초를 절약하는 것보다 더 유용 할 것입니다.
MgSam 2013-06-06

4
제네릭 버전으로 제한하는 좋은 점은 IComparable권투가 발생하지 않는다는 것입니다. 이것은 매우 빠르게 실행되어야합니다. 그와 함께 기억 double하고 float상기 CompareTo전체 순서에있어서의 대응 NaN을 포함하여 다른 모든 값 미만이다 NegativeInfinity. 따라서 <연산자 와 동일하지 않습니다 . <부동 소수점 유형 을 사용 하는 경우 처리 방법 NaN도 고려해야합니다 . 다른 숫자 유형과는 관련이 없습니다.
Jeppe Stig Nielsen

1
NaN두 경우 모두 치료 방법을 고려해야합니다 . <and >would가 있는 버전은 for 또는 이를 NaN사용하여 효과적으로 단면 클램프를 만듭니다. 로 그것을 항상 반환 하는 경우 IS . NaNminmaxCompareToNaNmaxNaN
Herman

30

사용 Math.Min하고 Math.Max:

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

그것은 int a0 = x > a ? x : a; return a0 < b ? a0 : b(정확한 결과를 제공하지만) 정확히 이상적이지 않은 것으로 해석됩니다 .
스미스 씨

12
왜 그런데?
d7samurai 2014

4
@ d7samurai min <= max라는 것을 안다면 Math.Min(Math.Max(x, min), max)x <min이면 필요한 것보다 더 많은 비교 결과를 얻습니다 .
짐 Balter

@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

이 솔루션은 허용되는 솔루션보다 낫습니다. 모호함이 없습니다.
아그 솔

6
@CodeClown이 솔루션은 value> max 일 때 불필요한 비교를 생성하고 반전 된 인수 순서가 버그를 초대 (그리고 사실상 보장)합니다. 피할 수 있다고 생각하는 모호함이 무엇인지 모르겠습니다.
Jim Balter 2015 년

기존 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이며 NaN, 항상 반환 NaN(참조 허먼의 주석 ).


또 다른 제한은 nameofC # 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

아래 코드는 임의의 순서 (예 bound1 <= bound2: 또는 bound2 <= bound1)로 경계 지정을 지원합니다 . y=mx+b선의 기울기가 증가하거나 감소 할 수있는 선형 방정식 ( )에서 계산 된 값을 클램핑하는 데 유용함을 발견했습니다 .

알아요 : 코드는 5 개의 매우 못생긴 조건식 연산자로 구성 됩니다. 문제는 작동합니다 하며 아래 테스트가이를 증명합니다. 원하는 경우 엄격하게 불필요한 괄호를 자유롭게 추가하십시오.

다른 숫자 유형에 대해 다른 오버로드를 쉽게 만들고 기본적으로 테스트를 복사 / 붙여 넣기 할 수 있습니다.

경고 : 부동 소수점 수를 비교하는 것은 간단하지 않습니다. 이 코드는 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);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.