Is there any benefit to C#'s Math.Max() implementation vs doing it mathemathicaly?

Viewed 210

In C# Math.Max() is implemented as:

   [NonVersionable]
   public static int Max(int val1, int val2)
   {
       return (val1 >= val2) ? val1 : val2;
   }

While I believe that it can also be implemented as:

enter image description here

Does C#'s implementation have any benefit such as efficiency over implementering it mathemathicaly?

(a + b + Math.Abs(a-b)) /2

4 Answers

For one, the mathematical implementation is incorrect as it doesn't handle overflow/underflow.

If you do:

// suppose Max is implemented the "mathematical" way
Max(int.MaxValue, int.MaxValue - 1)

it would give -1.

Assuming your Math is correct and you could implement it like you proposed. It would still have zero to none benifit, because the time complexity would be O(1) in both cases. But I would argue that the implementation in C# is more readable. Moreover the Math.Abs function is porbably implemented like return x > 0 ? x : -x; which would be the same amount of conditionals as in the current implementation.

Another reason would be dependent on your CPU. A CISC CPU is very like to have a max math operator. MAXSS in x64 for example.

A compiler might be able to figure out what you are trying to do. But with the workarounds necessary that seems highly unlikely. Allowing your intention to be pushed down the stack gives the best chance that your code resolves to a single CPU operation. I'm actually surprised by @Luuk's benchmark results. But it goes to show, optimize for readability, but when you care about performance make sure you measure thoroughly. In fact I try to always leave the performance tests behind in unit tests, b/c things change as compilers improve.

There was a comment about benchmarking, and a comment about overflow/underflow.

This code shows that the mathematical approach is quicker, even when doing a simplistic workaround for the overflow

    int a = 1;
    int b = Int32.MaxValue - 1;
    int count = 100000;
    DateTime start;
    Console.WriteLine($"a:{a} b:{b}");

    Console.WriteLine("Method1");
    start = DateTime.Now;
    for (int i=1; i< count; i++)
    {
        int c = Math.Max(a, b);
        //if (i == 1) Console.Write($"{c} ");
    }
    Console.WriteLine($"time: { (DateTime.Now - start).TotalMilliseconds }");


    Console.WriteLine("Method2");
    start = DateTime.Now;
    for (int i = 1; i < count; i++)
    {
        int c = Convert.ToInt32((double)(((double)a + b + Math.Abs(a - b)) / 2.0));
        //if(i == 1) Console.Write($"{c} ");
    }
    Console.WriteLine($"time: { (DateTime.Now - start).TotalMilliseconds }");

output:

a:1 b:2147483646
Method1
time: 3,0267
Method2
time: 0,4752
Press any key to continue . . .

EDIT: after suggestion to use benchmark.net

private readonly int a = 1;
private readonly int b = Int32.MaxValue - 1;

public Max()
{
    a = new Random(42).Next();
    b = new Random(24).Next();
}

[Benchmark]
public  int Method1() => Math.Max(a, b);

[Benchmark]
public int Method2() => Convert.ToInt32((double)(((double)a + b + Math.Abs(a - b)) / 2.0));

[Benchmark]
public int Method3() => ((a + b + Math.Abs(a - b) / 2));

[Benchmark]
public int Method4() => ((a >= b) ? a :b);

results:

// * Summary *

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8700K CPU 3.70GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.102
  [Host]     : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
  DefaultJob : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT


|  Method |      Mean |     Error |    StdDev |
|-------- |----------:|----------:|----------:|
| Method1 | 0.2288 ns | 0.0058 ns | 0.0054 ns |
| Method2 | 2.7820 ns | 0.0157 ns | 0.0123 ns |
| Method3 | 0.4558 ns | 0.0094 ns | 0.0088 ns |
| Method4 | 0.2645 ns | 0.0133 ns | 0.0124 ns |

// * Hints *
Outliers
  Max.Method2: Default -> 3 outliers were removed (4.21 ns..4.23 ns)

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ns   : 1 Nanosecond (0.000000001 sec)

// ***** BenchmarkRunner: End *****
// ** Remained 0 benchmark(s) to run **
Run time: 00:02:02 (122.05 sec), executed benchmarks: 4

Global total time: 00:02:05 (125.55 sec), executed benchmarks: 4
Related