3

I have an enumeration MyEnum (Neg -1; None 0; Pos 1).

I use the result of CompareTo() to initialize a object of that enum.

What is the most performant way in .NET to do it (Negative to -1, 0 to 0, Positive to 1)?

NB.
CompareTo() returns an Integer value..

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
serhio
  • 28,010
  • 62
  • 221
  • 374
  • 2
    @knittl : do you think the *division* is the most performant? – serhio Jul 06 '10 at 14:12
  • 9
    @knittl does .NET map `0/0` to `0`? – Jeriko Jul 06 '10 at 14:13
  • If the enum already has the -1/0/1 values assigned, CompareTo() will always return -1, 0 or 1 (never any other value). So if I understand correctly, you can simply do the following to get a MyEnum type value: `MyEnum myval = (MyEnum)x.CompareTo(y);` – manixrock Jul 06 '10 at 14:24
  • 2
    @manixrock: I am not sure about "always". MSDN tells about: "less than 0": http://msdn.microsoft.com/en-us/library/system.icomparable.compareto.aspx – serhio Jul 06 '10 at 14:27
  • 3
    If you want "the most performant" method, you need to test the various options. Take a look at the System.Diagnostics.Stopwatch class to measure each. – Hans Kesting Jul 06 '10 at 14:30

9 Answers9

13

Have a look at Math.Sign

Henrik
  • 23,186
  • 6
  • 42
  • 92
11

there's a builtin method called Math.Sign in the .net framework

knittl
  • 246,190
  • 53
  • 318
  • 364
10

As @Henrik and @knittl have said you should use Math.Sign. But if you're interested as to what the .Net framework is doing behind the scenes the following code has come out of Reflector.

public static int Sign(int value)
{
  if (value < 0)
  {
    return -1;
  }
  if (value > 0)
  {
    return 1;
  }
  return 0;
}
stevehipwell
  • 56,138
  • 6
  • 44
  • 61
4
Math.Sign(value)
mbeckish
  • 10,485
  • 5
  • 30
  • 55
3

Result of CompareTo is negative, zero, or positive. If you look at the other answers, Math.Sign uses 2 if statements to return an int.

Just re-code Math.Sign to return your enum.

(If this was not homework I would give a code example there, but you are supposed to learn from it).

After that, test it, to see what is the most performant.

GvS
  • 52,015
  • 16
  • 101
  • 139
  • 1
    In the time you spend waiting for an answer, you could have created 10 code examples yourself, done 20 things wrong, and so learned 20 things that are not in any code example ;-) – GvS Jul 06 '10 at 15:22
  • A custom sign function is faster than Math.Sign. – s_hewitt Jul 06 '10 at 16:57
3

Test Results (Dual core, x86):

''''''''''''''''''''' DEBUG MODE '''
= 1 =
Division took    00:00:06.2482408 ms
BuiltInSign took 00:00:05.0293383 ms <<<
BitTestSign took 00:00:05.2092181 ms
CustomSign took  00:00:05.2512802 ms

= 2 =
Division took    00:00:06.2477787 ms
BuiltInSign took 00:00:05.0330921 ms <<<
BitTestSign took 00:00:05.2114098 ms
CustomSign took  00:00:05.2556966 ms

= 3 =
Division took    00:00:06.2506690 ms
BuiltInSign took 00:00:05.0388615 ms <<<
BitTestSign took 00:00:05.2306954 ms
CustomSign took  00:00:05.2512391 ms


''''''''''''''''''' RELEASE MODE '''
= 1 =
Division took    00:00:01.0974078 ms
BuiltInSign took 00:00:00.3195232 ms
BitTestSign took 00:00:00.6392142 ms
CustomSign took  00:00:00.3194230 ms <<<

= 2 =
Division took    00:00:01.1007138 ms
BuiltInSign took 00:00:00.3197784 ms <<<
BitTestSign took 00:00:00.6395294 ms
CustomSign took  00:00:00.3202774 ms

= 3 =
Division took    00:00:01.0977087 ms
BuiltInSign took 00:00:00.3194622 ms <<<
BitTestSign took 00:00:00.6394220 ms
CustomSign took  00:00:00.3201607 ms

Code:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Stopwatch sw = new Stopwatch();
        MyEnum myEnum = MyEnum.None;

        const int max = 100000000;

        sw.Start();
        for (int i = -max; i < max; i++)
        {
            myEnum = Division(i);
        }
        sw.Stop();
        Console.WriteLine("Division took {0} ms", sw.Elapsed);
        sw.Reset();

        sw.Start();
        for (int i = -max; i < max; i++)
        {
            myEnum = BuiltInSign(i);
        }
        sw.Stop();
        Console.WriteLine("BuiltInSign took {0} ms", sw.Elapsed);
        sw.Reset();

        sw.Start();
        for (int i = -max; i < max; i++)
        {
            myEnum = BitTestSign(i);
        }
        sw.Stop();
        Console.WriteLine("BitTestSign took {0} ms", sw.Elapsed);
        sw.Reset();

        sw.Start();
        for (int i = -max; i < max; i++)
        {
            myEnum = CustomSign(i);
        }
        sw.Stop();
        Console.WriteLine("CustomSign took {0} ms", sw.Elapsed);
    }

    private MyEnum Division(int value)
    {
        return value == 0 ? 0 : (MyEnum)(value / Math.Abs(value));
    }

    private MyEnum BuiltInSign(int value)
    {
        return (MyEnum)Math.Sign(value);
    }

    private MyEnum CustomSign(int value)
    {
        if (value < 0)
            return MyEnum.Neg;

        if (value > 0)
            return MyEnum.Pos;

        return MyEnum.None;
    }

    MyEnum BitTestSign(int value)
    {
        // Shifts the variable over 31 places, 
        // if the MSB is 1, the statement is true
        if ((value >> 31) == 1)
        {
            return MyEnum.Neg;
        }
        else
        {
            if (value == 0)
            {
                return MyEnum.None;
            }
            else
            {
                return MyEnum.Pos;
            }
        }
    }

    private enum MyEnum
    {
        Pos = 1,
        None = 0,
        Neg = -1
    }
}
serhio
  • 28,010
  • 62
  • 221
  • 374
  • In the CustomSign, why do you `return (MyEnum)(-1)` instead of `return MyEnum.Pos`? Why is `MyEnum.Pos` a negative number, and the opposite from your original question? – GvS Jul 06 '10 at 15:50
  • Shouldn't your test set also have negative numbers? Now, you do not measure the perf for negatives, it should be different. – GvS Jul 06 '10 at 15:56
  • 1
    On my PC, if I run from -max to max the CustomSign is the fastest. – GvS Jul 06 '10 at 16:13
  • CustomSign is the fastest on my machine too. For -max to max where max = 1000000000. Div = 15s, BuiltInSign = 10s, BitTestSign = 10s, CustomSign = 8s. – s_hewitt Jul 06 '10 at 16:56
  • I also modified your test to include as many zeros as negative / positive numbers. After for(-max to max) I added for(0 to max){Division(0);}. Similar results, Division slowest, CustomSign fastest. – s_hewitt Jul 06 '10 at 17:01
  • 2
    @serhio - are you running your tests in Debug mode instead of Release? I get results like yours in Debug mode. You should try running them in Release mode. – s_hewitt Jul 06 '10 at 17:18
  • @s_hewitt: I run in Debug. Is the difference relevant for the subject? – serhio Jul 06 '10 at 21:48
  • @serhio - I believe it is relevant. See http://stackoverflow.com/questions/2446027/c-debug-vs-release-performance and the accepted answer's link to Eric Lippert's blog. – s_hewitt Jul 06 '10 at 22:13
  • @s_hewitt: Updated the result tests with release mode. Almost no changes. – serhio Jul 07 '10 at 12:22
  • And if you add the zeros so there are as many zeros as Negative and Positive numbers? – s_hewitt Jul 07 '10 at 14:50
1

.NET internally stores ints as two's complement. So, if you want to try something else, check and see if the most significant bit is set. This may or may not be faster, but it should fit into your testing framework easily.

Pseudocode:

if(num == 0)
  return 0;

if(num has msb set)
  return -1;

return 1;
Donnie
  • 45,732
  • 10
  • 64
  • 86
0

If I understand that correctly, you want to return -1 for negative values, 0 for zero values and 1 for positive values.

I'd go with:

public static MyEnum GetSign(int value)
{
    return value == 0 ? 0 : (MyEnum)(value / Math.Abs(value));
}

Or did I get something wrong?

Turing Complete
  • 929
  • 2
  • 12
  • 19
0

You could check the most significant bit of the variable (in two's complement, if the most significant bit is 1, you have a negative number). If your most significant bit is 0, check if the value is 0. Return a value accordingly.

int Sign(int value){
    if (value >> 31) { //Shifts the variable over 31 places, if the MSB is 1, the statement is true
         return -1;
    } else {
        if (value == 0){
            return 0;
        } else {
            return 1;
        }
    }  }  

(edited for example)

Jan Gorzny
  • 1,732
  • 1
  • 16
  • 25
  • @GvS: thinking like this I should search the answer in books, and not asking in forum. Now if I don't know how to transform a negative to -1, do you think I am able to check the most significant bit in a integer? :) – serhio Jul 06 '10 at 14:36
  • @Joren thanks, I changed it; it should now compile as c++ code (that was my intention, but it came out looking like Java). – Jan Gorzny Jul 06 '10 at 15:03
  • Jan, you don't need "else", cause you do return... now after the number of operation does not Stevo3000 solution be more optimal?.. – serhio Jul 06 '10 at 15:09
  • True, the 'else' statements are unnecessary. As for which is more optimal, I'm not entirely sure--I'm just offering an alternative. You can test which is quicker or allow someone else to comment on the performance difference. I think it comes down to whether '<' is faster/slower than '<<'. – Jan Gorzny Jul 06 '10 at 15:17
  • 2
    @Jan: I was thinking more of `if (value >> 31 == 1)` to make it valid C# (a more likely .NET language) but sure, whatever. :) – Joren Jul 06 '10 at 15:33