Skip to content

Add generic overloads to Math.Min and Math.Max #14510

Closed

Description

@svick

There are overloads of Math.Min and Math.Max for all the numeric types, but there aren't any for general comparable types (including framework types like DateTime and TimeSpan). It would be nice to have them.

Rationale

For integral types, Math.Min and Math.Max are not necessary, e.g. instead of Math.Min(a, b), you can write a <= b ? a : b. But they are nice, because they make the meaning clear and decrease the likelihood of error. The same argument applies to general comparable types, so they should work with Math.Min and Math.Max too.

Proposed API

public static class Math
{
    public static T Min<T>(T val1, T val2) where T : IComparable<T>;
    public static T Min<T>(T val1, T val2, IComparer<T> comparer);

    public static T Max<T>(T val1, T val2) where T : IComparable<T>;
    public static T Max<T>(T val1, T val2, IComparer<T> comparer);
}

Open questions

  • Should the constraint where T : IComparable<T> be there? Other framework methods that require comparison, like Enumerable.OrderBy, don't have it and instead use Comparer<T>.Default.
  • Should nullable value types be supported? The proposed version doesn't, because T? does not implement IComparable<T?>, even when T does implement IComparable<T>.

Activity

  1. NickCraver commented on May 14, 2016

    @NickCraver
    Member

    I would ammend this API to move off of the Math class, for 3 reasons:

    • I think Math ceases to make as much sense with many IComparable<T> types. You could be comparing swimming pools or classical music to see which is greater, these things aren't bounded to mathematical concepts.
    • The overloads would be an overload change to existing code and we could have breaks there.
    • It avoids type forwarding concerns and allows the API to be added without a desktop CLR ship.

    Instead, I'd suggest a new static class called Compare, where many such generic methods could live. Updated example:

    public static class Compare
    {
        public static T Min<T>(T val1, T val2, IComparer<T> comparer = null);
        public static T Max<T>(T val1, T val2, IComparer<T> comparer = null);
    }

    Should the constraint where T : IComparable<T> be there?

    I would remove it and opt for a single overload that optionally accepts the IComparer<T>. If it's null, Comparer<T>.Default would be used for the same effect as the other overload.

    Should nullable value types be supported?

    I don't think they should be supported - because defining this behavior is...hard to define. You could argue that IComparable<T> forces such a comparison but structs remain fun and ambiguous.

  2. benaadams commented on May 14, 2016

    @benaadams
    Member

    Should nullable value types be supported?

    No because nullable value types can and often represent database nulls and you don't want to go there... As it means closer to unknown and the comparisons and behaviours are almost philosophical.

    Is a null greater or less than a value? Or is it indeterminate depending on what the unknown value really is?

  3. Siderite commented on May 17, 2016

    @Siderite

    I would use params to calculate the max/min for any number of items. And in that vein, if you are already using a list of objects, then a LInQ extension kind of makes more sense. new[] { o1, o2, o3}.Max();

  4. NickCraver commented on May 17, 2016

    @NickCraver
    Member

    @Siderite that would require the an allocation of an array on each call. We definitely do not want that overhead. If such overhead is acceptable, then the current LINQ approach works.

    Alternatively, we could provide such an overload, but alongside built-in non-array allocation overloads for (probably) 2, 3, and 4 parameters.

  5. Siderite commented on May 17, 2016

    @Siderite

    I always thought this was an antipattern resulting from the compiler laziness. Surely if there is a cost in allocating an array, then the compiler could create the underlying 1,2,3 parameter overloads for you. As such, with something like PostSharp, this could be implemented as a rule even by the developer, I think. Aren't you tired of writing Math.Max(Math.Max(a,b),c) when you want to compare three numbers? Of course, one can always create a Linq aggregate, but it feels like overkill.

  6. NickCraver commented on May 17, 2016

    @NickCraver
    Member

    How would the compiler generate the code here? That's different code for the multiple cases - you'd spend far more time writing the necessary (and even if possible) compiler gymnastics here than the code for the few overloads :)

    The whole point here (with any multi-overload method) is efficiency; optimizing for the 95-99% case with a few overloads and the rest can allocate more. If this wasn't a goal, we'd just all use the LINQ approach available right now.

  7. danmoseley commented on Dec 27, 2016

    @danmoseley
    Contributor

    @AlexGhiondea what is next here, are we asking @svick to update his proposal? or is this ready for review?

  8. AlexGhiondea commented on Dec 28, 2016

    @AlexGhiondea
    Contributor

    @danmosemsft I believe we were still settling on the design. I will take a look when I get back next year!

  9. AlexGhiondea commented on Jan 4, 2017

    @AlexGhiondea
    Contributor

    @svick @NickCraver I am closing this since we are going to review basically the same in dotnet/corefx#11555

  10. transferred this issue fromdotnet/corefxon Jan 31, 2020
  11. added this to the 2.0.0 milestone on Jan 31, 2020
  12. ghost locked as resolved and limited conversation to collaborators on Jan 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Runtimehelp wanted[up-for-grabs] Good issue for external contributors

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions