Question

Why does .Net implement the Math.Min(float, float) function like so:

public static float Min(float val1, float val2)
{
  if ((double) val1 < (double) val2 || float.IsNaN(val1))
    return val1;
  else
    return val2;
}

While I can see the use of IsNaN I don't understand why they convert to double when comparing the values. Isn't that slower than simply writing val < val 2? Especially if I wanted to use it to clamp a value to numbers without much precision like 0f or 1f.

Should I just go ahead and implement a custom math library which additionally also requires to have non NaN values to get the best performance or is that a waste of time?

public static float Min(float a, float b)
{
    return a < b ? a : b;
}
Was it helpful?

Solution

Yes, I used Resharper which does exactly that

And that's the problem, it fumbles the code. The source code is available from the Reference Source, it looks like this:

  [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
  public static float Min(float val1, float val2) {
    if (val1 < val2)
        return val1;

    if (Single.IsNaN(val1))
        return val1;

    return val2;
  }

Hard to guess why Resharper does this, I'd readily assume its just a bug. Favor the source code available from Microsoft, not just for accuracy but the comments are nice too.

OTHER TIPS

I can't tell you why they chose to cast float to double for the comparison, but I doubt it's going to cause any noticeable or significant speed impact on your program. I'd just use the built-in function.

There's no such thing as Math.Min(float, float). .NET only contains a definition for Min(double, double). Check the MSDN: http://msdn.microsoft.com/en-us/library/system.math.min(v=vs.100).aspx

EDIT: there is, thanks @gideon http://msdn.microsoft.com/en-us/library/070xee48.aspx

In general you can't assume that Single performs faster than Double. The floating point registers on the Intel CPU's are 80 bits and floating point operations on the CPU are performed using that precision. On that platform the JIT may generate floating point instructions that load the arguments into the native 80 bit registers and only difference is if the registers are loaded from 32 bits or 64 bits memory locations. Perhaps the implementers of Math.Min based their implementation on intricate knowledge of how the JIT compiler generates floating point code.

From the accepted answer it can be seen that the question is based on the wrong assumption (that there is a cast from Single to Double). To investigate if the cast actually does make difference I looked at the assembler generated by a Min function with and without a cast.

This is the .NET framework Math.Min(Single, Single) as seen in my debugger:

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  fld         dword ptr [ebp+0Ch] 
00000006  fld         dword ptr [ebp+8] 
00000009  fxch        st(1) 
0000000b  fcomi       st,st(1) 
0000000d  jp          00000015 
0000000f  jae         00000015 
00000011  fstp        st(1) 
00000013  jmp         00000022 
00000015  fcomi       st,st(0) 
00000017  jp          0000001B 
00000019  je          00000026 
0000001b  mov         eax,1 
00000020  jmp         00000028 
00000022  pop         ebp 
00000023  ret         8 
00000026  xor         eax,eax 
00000028  test        eax,eax 
0000002a  je          00000030 
0000002c  fstp        st(1) 
0000002e  jmp         00000036 
00000030  fstp        st(0) 
00000032  pop         ebp 
00000033  ret         8 
00000036  pop         ebp 
00000037  ret         8 

Here is the the assembly for a function using a cast to Double just like in the question:

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  fld         dword ptr [ebp+0Ch] 
00000009  fld         dword ptr [ebp+8] 
0000000c  fld         st(1) 
0000000e  fstp        qword ptr [ebp-8] 
00000011  fld         qword ptr [ebp-8] 
00000014  fld         st(1) 
00000016  fstp        qword ptr [ebp-8] 
00000019  fld         qword ptr [ebp-8] 
0000001c  fcomip      st,st(1) 
0000001e  fstp        st(0) 
00000020  jp          00000028 
00000022  jbe         00000028 
00000024  fstp        st(0) 
00000026  jmp         00000043 
00000028  fxch        st(1) 
0000002a  fcomi       st,st(0) 
0000002c  jp          00000030 
0000002e  je          00000037 
00000030  mov         eax,1 
00000035  jmp         00000039 
00000037  xor         eax,eax 
00000039  test        eax,eax 
0000003b  jne         00000041 
0000003d  fstp        st(0) 
0000003f  jmp         00000049 
00000041  fstp        st(1) 
00000043  mov         esp,ebp 
00000045  pop         ebp 
00000046  ret         8 
00000049  mov         esp,ebp 
0000004b  pop         ebp 
0000004c  ret         8 

There are a few more instructions and these will probably decrease the performance of the function slightly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top