« Citizen Mo | Main | Performance of System.IO.Ports versus Un... »

Flip the endian-ness of a long in C#

Posted 2006-08-24 10:48 PM in Programming.

Programming challenge:

Write me a function with this signature in C#:

public (unsafe?) long Reverse(long i, int bits)

...to flip the endian-ness (LSB/MSB) of a long, but just the # of significant bits specified.

Example, if the input is 376, with bits=11, the output is 244 (decimal, base 10).

376 = 00000101111000
244 = 00000011110100

Example, if the input is 900, with bits=11, the output is 270.

900 = 00001110000100
270 = 00000100001110

Example, if the input is 900, with bits=12, the output is 540.

900 = 00001110000100
540 = 00001000011100

Example, if the input is 154, with bits=4, the output is 5.

154 = 00000010011010
5   = 00000000000101

And make it FAST...;)



Thursday, August 24, 2006 10:55:06 PM (Pacific Standard Time, UTC-08:00)
I'd try something like this as the first guess:

public long Reverse(long i, int bits) {
i = ((i >> 1) & 0x5555555555555555) | ((i & 0x5555555555555555) << 1);
i = ((i >> 2) & 0x3333333333333333) | ((i & 0x3333333333333333) << 2);
i = ((i >> 4) & 0x0F0F0F0F0F0F0F0F) | ((i & 0x0F0F0F0F0F0F0F0F) << 4);
i = ((i >> 8) & 0x00FF00FF00FF00FF) | ((i & 0x00FF00FF00FF00FF) << 8);
i = ((i >> 16) & 0x0000FFFF0000FFFF) | ((i & 0x0000FFFF0000FFFF) << 16);
i = ( i >> 32 ) | ( i << 32);
return i >> (64 - bits);
}
Thursday, August 24, 2006 11:03:58 PM (Pacific Standard Time, UTC-08:00)
I haven't tried this out, but here's my entry (divide and conquer, constant-time):

public long Reverse(long word, int bits)
{
ulong n = (ulong)word {{ (64-bits);
n = n }} 32 | n {{ 32;
n = n }} 0xf & 0x0000ffff | n {{ 0xf & 0xffff0000;
n = n }} 0x8 & 0x00ff00ff | n {{ 0x8 & 0xff00ff00;
n = n }} 0x4 & 0x0f0f0f0f | n {{ 0x4 & 0xf0f0f0f0;
n = n }} 0x2 & 0x33333333 | n {{ 0x2 & 0xcccccccc;
n = n }} 0x1 & 0x55555555 | n {{ 0x1 & 0xaaaaaaaa;
return (long) n | word & (-1 {{ bits);
}
Thursday, August 24, 2006 11:05:06 PM (Pacific Standard Time, UTC-08:00)
Nicely done, Dr. Nick.

I tried this:

long reverse(long data,int bits)
{
int i;
long l;
l = 0;
for(i = 0; i &lt; bits; i++)
{
l |= (long) (((data & (((long) 1)&lt;&lt;i)) ? 1:0)) &lt;&lt; (bits - 1 - i);
}
return l;
}

But got stuck in middle with the left shift not working the same in C# as it does in C++.

Then I headed in this general direction but got lost along the way:

public unsafe long Reverse(long i)
{
byte b1, b2, b3, b4;

b1 = (byte)(i & 255);
b2 = (byte)((i &gt;&gt; 8) & 255);
b3 = (byte)((i &gt;&gt; 16) & 255);
b4 = (byte)((i &gt;&gt; 24) & 255);

return (long)(((int)b1 &lt;&lt; 24) + ((int)b2 &lt;&lt; 16) + ((int)b3 &lt;&lt; 8) + b4);
}

You just whip yours out, or did you have it lying around?
Scott Hanselman
Thursday, August 24, 2006 11:06:36 PM (Pacific Standard Time, UTC-08:00)
Clever Wes, I'll test it and time it as well!
Scott Hanselman
Thursday, August 24, 2006 11:07:14 PM (Pacific Standard Time, UTC-08:00)
In the previous function, extend each of the constants to 64 bit length...
Thursday, August 24, 2006 11:10:38 PM (Pacific Standard Time, UTC-08:00)
The left shift operator should work the same in C# as in C++.
The right shift has two different behavior (arithmetic versus logical shift) depending on whether the type is signed or not.

(ulong) >>
(long) >>
Thursday, August 24, 2006 11:18:30 PM (Pacific Standard Time, UTC-08:00)
That reverse idea was classic on 16-bit micros. I just extended the pattern and fit it to your problem. I think Wes has the right idea with operating on ulong instead of long. I don't normally do a lot of bit flicking with C#.
Friday, August 25, 2006 3:50:22 AM (Pacific Standard Time, UTC-08:00)
So, which one was the fastest? :)
Diego
Friday, August 25, 2006 7:34:30 AM (Pacific Standard Time, UTC-08:00)
The fastest way is going to use precomputation and table lookups.

http://jasonf-blog.blogspot.com/2006/08/hanselmans-endianess-converter.html

Friday, August 25, 2006 8:24:34 AM (Pacific Standard Time, UTC-08:00)
Scott! Do your own homework young man!
Friday, August 25, 2006 10:32:34 AM (Pacific Standard Time, UTC-08:00)

http://aggregate.org/MAGIC/#Bit%20Reversal
Dilip
Friday, August 25, 2006 1:05:37 PM (Pacific Standard Time, UTC-08:00)
Not the most optimal solution, but another one none the less...

public long Reverse(long i, int bits)
{
long reversed = 0;
int j = 0;
for (j = 0; (j < (bits / 2)) && (j < 32); j++)
{
// Swap elements j, bits-j
reversed |= (((i >> (bits - j - 1)) & 0x1) << j);
reversed |= (((i >> j) & 0x1) << (bits - j - 1));
}
if (0 != (bits % 2))
{
// for an odd bits value, assign the middle value
reversed |= (((i >> (bits - j - 1)) & 0x1) << j);
}

return reversed;
}

I'd be interested in seeing the timing results...

-Joe
Friday, August 25, 2006 1:57:47 PM (Pacific Standard Time, UTC-08:00)
I went for the straight-forward

public static long Reverse(int i, int bits)
{
int rev = 0;
while (bits-- > 0)
{
rev <<= 1;
rev = rev + (i & 1);
i >>= 1;
}
return rev;
}

Yes, mine is O(N), but N is the value of bits, which is <= 32, while all the so-called O(1) solutions here are really O(ln N) where N is the number of bits in i. I would expect an insignificant speed difference among them.

Friday, August 25, 2006 2:36:17 PM (Pacific Standard Time, UTC-08:00)
BTW, this has nothing to do with Endian-ness, which is always byte-by-byte, not bit-by-bit.
Friday, August 25, 2006 2:40:25 PM (Pacific Standard Time, UTC-08:00)
James - heh! you're right. I'm just asking to flip the MSB and LSBs...the whole structure on it's ass.
Scott Hanselman
Friday, August 25, 2006 3:08:40 PM (Pacific Standard Time, UTC-08:00)
public static long Lusix(long l, int bits)
{

Mask m = new Mask(bits);
long result;
long rval;
long calculatedRight=0;
long lMaskLeft;

// Get the block of bits
result = l & m.Right;
for (int i = 0; i < bits; i++)
{
// result = 543
rval = result >> (bits - (i + 1)); // if bits is 3 then rval=5

// Add to build
calculatedRight += (rval << i);

// Calculate new result
result = result - (rval << bits - (i + 1));

}

// Zero out the right part... and keep just the left part.
lMaskLeft = l & m.Left;

// Return the left part and the new calculated right part.
return lMaskLeft + calculatedRight;
}

public class Mask
{
private long _left;
private long _right;

public Mask(int bits)
{
this._left = (long.MaxValue) << bits;
this._right = long.MaxValue >> (64 - (1 + bits));
}

public long Left
{
get { return _left; }
}

public long Right
{
get { return _right; }
}

}
Ivan Terrence
Friday, August 25, 2006 4:46:49 PM (Pacific Standard Time, UTC-08:00)
The looping solution works fine with shift operators, and this is the approach I tried first. I generally find that it's easy to become confused on complex operations, so I prefer to write my first approach with each step on individual lines for easier debugging (and to keep my mind straight). Once I prove the general solution works, I use ReSharper to inline the variables one at a time and add parantheses as necessary at each step.

public unsafe static long Reverse(long i, int bits)
{
long x = 0;

for(int bit = 0; bit < bits; bit++)
{
x |= ((i & (1 << bit)) == 0) ? 0 : (1 << (bits - bit - 1));
}

return x;
}

I was curious about the difference between this solution and Nicholas' solution, so I called each method 4,000 times in a loop. According to Performance Explorer using instrumented profiling, the looping solution requires 0.822978 seconds whereas Nicholas' solution requires 0.418179 seconds (roughly twice as fast).
Lothan
Friday, August 25, 2006 5:24:30 PM (Pacific Standard Time, UTC-08:00)
I smell a Hanselminutes topic: ways to instrument code and/or measure performance...

I tried timing all of the variations using the StopWatch class (starting it before the function call, and stopping it after, repeating for x iterations, and then averaging the ElapsedTicks. The results usually put Nicholas's solution first, but not consistently. Ivan's approach was consistently always last, though. :(

But, then out of curiosity, I commented out the Reverse() function calls, and didn't see any changes in my timings!!! That suggests some sort of Heisenberg phenomenon with my method.
Saturday, August 26, 2006 4:49:59 AM (Pacific Standard Time, UTC-08:00)
JasonF, table lookup is not always the fastest solution. The CPU can often perform computations faster than looking up memory.
Saturday, August 26, 2006 8:45:40 AM (Pacific Standard Time, UTC-08:00)
Wesner: I came to realize that same conclusion as part of exploring this topic. Nicholas's algorithm above is consistently fast. It wasn't until I used a 16-bit lookup table that my algorithm would consistently beat his algorithm, but only at about 15-20% improvement in execution time at its best (this is, of course, assuming that the way I ended up capturing times turns out to be accurate).

An additional point of consideration: using 16-bit tables increased the memory requirements by a factor of 512 over the 8-bit table. So for the cost of 128+ KB of memory, you get a 15% improvement over Nicholas's algorithm that has no memory requirements. Memory is cheap, but in this case, would it make Scott's HDTV change channels any faster? Probably not.

The C# code from my trials is at the link that I posted above.
JasonF
Comments are closed.

Contact

Sponsors

Hosting By

Hot Topics

Tags

Calendar

<November 2009>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

Archives

November, 2009 (5)
October, 2009 (19)
September, 2009 (11)
August, 2009 (12)
July, 2009 (21)
June, 2009 (26)
May, 2009 (16)
April, 2009 (13)
March, 2009 (17)
February, 2009 (17)
January, 2009 (18)
December, 2008 (32)
November, 2008 (17)
October, 2008 (22)
September, 2008 (16)
August, 2008 (14)
July, 2008 (25)
June, 2008 (19)
May, 2008 (17)
April, 2008 (17)
March, 2008 (26)
February, 2008 (21)
January, 2008 (28)
December, 2007 (19)
November, 2007 (17)
October, 2007 (31)
September, 2007 (39)
August, 2007 (37)
July, 2007 (43)
June, 2007 (37)
May, 2007 (32)
April, 2007 (38)
March, 2007 (29)
February, 2007 (46)
January, 2007 (31)
December, 2006 (27)
November, 2006 (31)
October, 2006 (32)
September, 2006 (39)
August, 2006 (34)
July, 2006 (40)
June, 2006 (18)
May, 2006 (31)
April, 2006 (34)
March, 2006 (30)
February, 2006 (38)
January, 2006 (44)
December, 2005 (19)
November, 2005 (34)
October, 2005 (24)
September, 2005 (37)
August, 2005 (20)
July, 2005 (24)
June, 2005 (33)
May, 2005 (16)
April, 2005 (22)
March, 2005 (34)
February, 2005 (15)
January, 2005 (37)
December, 2004 (28)
November, 2004 (30)
October, 2004 (34)
September, 2004 (22)
August, 2004 (34)
July, 2004 (18)
June, 2004 (64)
May, 2004 (49)
April, 2004 (21)
March, 2004 (29)
February, 2004 (29)
January, 2004 (36)
December, 2003 (25)
November, 2003 (24)
October, 2003 (59)
September, 2003 (42)
August, 2003 (24)
July, 2003 (44)
June, 2003 (29)
May, 2003 (21)
April, 2003 (30)
March, 2003 (27)
February, 2003 (47)
January, 2003 (50)
December, 2002 (31)
November, 2002 (38)
October, 2002 (44)
September, 2002 (15)
May, 2002 (2)
April, 2002 (4)

Google Ads