Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Clamp method to System.Math #13994

Closed
sgtfrankieboy opened this issue Jan 18, 2015 · 20 comments
Closed

Add a Clamp method to System.Math #13994

sgtfrankieboy opened this issue Jan 18, 2015 · 20 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Milestone

Comments

@sgtfrankieboy
Copy link
Contributor

I know the System.Math library hasn't been pushed to the .Net Core library but since discussing api additions could take some time I thought it was a good idea to propose it now.

Rationale and Usage

Math.Clamp is an easy to implement method and makes it easier for developers. Currently developers would have to implement their own methods in an extension class or use Math.Max and Math.Min together. The proposed API will shorten the amount of code needed and increase readability.

Two examples on how we currently can clamp in .NET:

int result = Math.Max(Math.Min(value, 100), 0);
public static class Ext {
    // Source: http://stackoverflow.com/a/2683487/1455541
    public static T Clamp<T>(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;
    }
}
// Somewhere in the codebase
int result = Ext.Clamp(value, 0, 100);

The proposed API would turn that into:

int result = Math.Clamp(value, 0, 100);

Proposed API 1 - Specialized

public static class Math {

    public static Byte Clamp(Byte value, Byte min, Byte max);
    public static Decimal Clamp(Decimal value, Decimal min, Decimal max);
    public static Double Clamp(Double value, Double min, Double max);
    public static Int16 Clamp(Int16 value, Int16 min, Int16 max);
    public static Int32 Clamp(Int32 value, Int32 min, Int32 max);
    public static Int64 Clamp(Int64 value, Int64 min, Int64 max);
    public static SByte Clamp(SByte value, SByte min, SByte max);
    public static Single Clamp(Single value, Single min, Single max);
    public static UInt16 Clamp(UInt16 value, UInt16 min, UInt16 max);
    public static UInt32 Clamp(UInt32 value, UInt32 min, UInt32 max);
    public static UInt64 Clamp(UInt64 value, UInt64 min, UInt64 max);

}

This proposed version mimics the way current System.Math methods do it.

Proposed API 2 - IComparable

Because this is a new API addition it can also be possible to have smaller implementation using generics that should cover all the cases.

public static class Math {

    public static T Clamp<T>(T value, T min, T max)  where T : System.IComparable<T>;

}

Open Questions

  • If the api addition is going to be implemented should it be implemented as Proposed API 1 or 2?
@terrajobst
Copy link
Member

This seems quite reasonable to me. Tagging a few more folks to take a look.

@mellinoe @KrzysztofCwalina @ellismg

@akoeplinger
Copy link
Member

XNA and Unity added it as well.

@ellismg
Copy link
Contributor

ellismg commented Jan 19, 2015

I like the idea. I think we should consider both explicit specializations for the core numeric types as well as the generic version. I can imagine the code-gen might be better for the non-generic versions, but we should validate that assumption.

@stephentoub
Copy link
Member

I was about to write almost the exact same thing as @ellismg.

@mikedn
Copy link
Contributor

mikedn commented Jan 19, 2015

You'll likely need specializations for at least float and double. That's partly due to double.CompareTo/float.CompareTo being poor inlining candidates (too large), partly to make sure that NaN are properly dealt with.

For example, the min/max version produces NaN if the input value is NaN while the IComparable version produces 0 (the min value). It's more common for math functions to propagate NaNs so the IComparable result seems undesirable.

@colombod
Copy link
Member

You need to pay attention to epsilon when it comes to double and floats.

Dr Diego Colombo PhD

On 19 Jan 2015, at 08:48, mikedn [email protected] wrote:

You'll likely need specializations for at least float and double. That's partly due to double.CompareTo/float.CompareTo being poor inlining candidates (too large), partly to make sure that NaN are properly dealt with.

For example, the min/max version produces NaN if the input value is NaN while the IComparable version produces 0 (the min value). It's more common for math functions to propagate NaNs so the IComparable result seems undesirable.


Reply to this email directly or view it on GitHub.

@mikedn
Copy link
Contributor

mikedn commented Jan 19, 2015

Epsilon is normally associated with floating point equality tests, it has nothing to do with clamping.

@colombod
Copy link
Member

In video games code you tend to give epsilon options (can be overridden ) to tweak the clamp function behaviour.

Dr Diego Colombo PhD

On 19 Jan 2015, at 09:25, mikedn [email protected] wrote:

Epsilon is normally associated with floating point equality tests, it has nothing to do with clamping.


Reply to this email directly or view it on GitHub.

@sgtfrankieboy
Copy link
Contributor Author

Neither Clamp methods in XNA and Unity have an option to change the epsilon.

Unity however has a read-only value.

@mikedn
Copy link
Contributor

mikedn commented Jan 19, 2015

I've done graphics programming and I've never seen or need a clamp function with any epsilon option (or any other optional behaviors). In particular, neither HLSL nor GLSL offer any epsilon option for their clamp functions. You're probably confusing clamping with something else.

@mellinoe
Copy link
Contributor

This looks like a good addition to me.

Regarding a generic Clamp method vs. individual specializations: right now, all of the methods in the Math class operate only on primitive numeric types, and there are no generic methods. It might be a bit odd having a method that can be used on any IComparable object on the "Math" class, but then again it's not that strange considering what the method does. Seeing something like Math.Clamp(DateTime.Now, min, max) feels a bit odd at first, just because I am used to seeing primitive numeric arguments to Math methods. I don't feel very strongly about that, though, and I think a generic overload would be very useful in a lot of situations.

Regarding the epsilon discussion above: Could you clarify what exactly you are referring to, @colombod? I would second that I haven't seen any epsilon arguments in any clamp functions. I'm also not sure what the semantics of such a parameter would be, as, like @mikedn said, epsilon generally refers to an acceptable margin for equality comparisons. I'm not sure how that would translate to greater-than/less-than/equal-to comparisons.

I do think this is a good candidate for addition, whatever we decide about the above point. Thanks for the suggestion, @sgtfrankieboy.

@colombod
Copy link
Member

Some game engine allows you to define custom precision (like float 16) and therefore you might want to tweak the "epsilon". That way is like reducing the resolution of steps for greater and lesser checks.

Dr Diego Colombo PhD

On 19 Jan 2015, at 21:32, Eric Mellino [email protected] wrote:

This looks like a good addition to me.

Regarding a generic Clamp method vs. individual specializations: right now, all of the methods in the Math class operate only on primitive numeric types, and there are no generic methods. It might be a bit odd having a method that can be used on any IComparable object on the "Math" class, but then again it's not that strange considering what the method does. Seeing something like Math.Clamp(DateTime.Now, min, max) feels a bit odd at first, just because I am used to seeing primitive numeric arguments to Math methods. I don't feel very strongly about that, though, and I think a generic overload would be very useful in a lot of situations.

Regarding the epsilon discussion above: Could you clarify what exactly you are referring to, @colombod ? I would second that I haven't seen any epsilon arguments in any clamp functions. I'm also not sure what the semantics of such a parameter would be, as, like @mikedn said, epsilon generally refers to an acceptable margin for equality comparisons. I'm not sure how that would translate to greater-than/less-than/equal-to comparisons.

I do think this is a good candidate for addition, whatever we decide about the above point. Thanks for the suggestion, @sgtfrankieboy.


Reply to this email directly or view it on GitHub.

@mikedn
Copy link
Contributor

mikedn commented Jan 20, 2015

Again, epsilon is not used in a clamp implementation because clamp doesn't do any equality checks. And there's no support for FP16 format in .NET, its smallest floating point format has 32 bit. In general, FP16 is something you see on GPUs, CPUs barely support it. This whole talk about epsilon is a red herring in this particular context.

@colombod
Copy link
Member

I agree

On 20 Jan 2015, at 04:35, mikedn [email protected] wrote:

Again, epsilon is not used in a clamp implementation because clamp doesn't do any equality checks. And there's no support for FP16 format in .NET, its smallest floating point format has 32 bit. In general, FP16 is something you see on GPUs, CPUs barely support it. This whole talk about epsilon is a red herring in this particular context.


Reply to this email directly or view it on GitHub.

@Thynix
Copy link

Thynix commented Jan 25, 2015

This seems like a good idea to me as well. Tangential nitpick: the example

int result = Math.Max(Math.Min(value, 0), 100);

always returns 100. It should be

int result = Math.Max(Math.Min(value, 100), 0);

@sgtfrankieboy
Copy link
Contributor Author

I haven't tested example code, it was more to show the current methods. Now that you mentioned it, it also shows that the current methods can have easily overlooked mistakes which could be hard to track down when actually using it. I've updated the example.

@ebickle
Copy link

ebickle commented Feb 2, 2015

Seems like a sensible idea to me.

My preference is option E1 - include the overloads for the built-in numeric data types and omit the generic IComparable variant unless the same is done for there rest of the Math class' API surface area.

There's a potential for some very weird patterns using the generic variant - consider Clamp, for example.

@sgtfrankieboy
Copy link
Contributor Author

I see that System.Math is now available in mscorlib, will it be moved over from CoreCLR to CoreFX? Because the documentation said that new API additions should happen in CoreFX unless specified.

@joshfree joshfree assigned mellinoe and unassigned terrajobst Dec 2, 2015
@weshaggard
Copy link
Member

We've reviewed the API and we believe we should go with proposal 1 and use the specialized overloads.

@jkotas
Copy link
Member

jkotas commented Sep 25, 2016

Make sure to add tests with NaNs for the floating point overloads.

@ghost ghost locked as resolved and limited conversation to collaborators Jan 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Projects
None yet
Development

No branches or pull requests