6

Lets say that I have an array of 4 32-bit integers which I use to store the 128-bit number

How can I perform left and right shift on this 128-bit number?

Thanks!

artichoke101
  • 73
  • 1
  • 3

5 Answers5

6

Working with uint128? If you can, use the x86 SSE instructions, which were designed for exactly that. (Then, when you've bitshifted your value, you're ready to do other 128-bit operations...)

SSE2 bit shifts take ~4 instructions on average, with one branch (a case statement). No issues with shifting more than 32 bits, either. The full code for doing this is, using gcc intrinsics rather than raw assembler, is in sseutil.c (github: "Unusual uses of SSE2") -- and it's a bit bigger than makes sense to paste here.

The hurdle for many people in using SSE2 is that shift ops take immediate (constant) shift counts. You can solve that with a bit of C preprocessor twiddling (wordpress: C preprocessor tricks). After that, you have op sequences like:

LeftShift(uint128 x, int n) = _mm_slli_epi64(_mm_slli_si128(x, n/8), n%8)

for n = 65..71, 73..79, … 121..127 ... doing the whole shift in two instructions.

phuclv
  • 27,258
  • 11
  • 104
  • 360
Mischa
  • 2,090
  • 20
  • 15
  • You should show the relevant code rather than linking offsite. I tired going through your macro implementation, but it probably needs to be unrolled to make it clearer for visitors. – jww Apr 04 '17 at 08:04
4
void shiftl128 (
    unsigned int& a,
    unsigned int& b,
    unsigned int& c,
    unsigned int& d,
    size_t k)
{
    assert (k <= 128);
    if (k >= 32) // shifting a 32-bit integer by more than 31 bits is "undefined"
    {
        a=b;
        b=c;
        c=d;
        d=0;
        shiftl128(a,b,c,d,k-32);
    }
    else
    {
        a = (a << k) | (b >> (32-k));
        b = (b << k) | (c >> (32-k));
        c = (c << k) | (d >> (32-k));
        d = (d << k);
    }
}

void shiftr128 (
    unsigned int& a,
    unsigned int& b,
    unsigned int& c,
    unsigned int& d,
    size_t k)
{
    assert (k <= 128);
    if (k >= 32) // shifting a 32-bit integer by more than 31 bits is "undefined"
    {
        d=c;
        c=b;
        b=a;
        a=0;
        shiftr128(a,b,c,d,k-32);
    }
    else
    {
        d = (c << (32-k)) | (d >> k); \
        c = (b << (32-k)) | (c >> k); \
        b = (a << (32-k)) | (b >> k); \
        a = (a >> k);
    }
}
Henry
  • 367
  • 4
  • 14
Remus Rusanu
  • 273,340
  • 38
  • 408
  • 539
  • Fails for k > 32. You don't think a function would be better than a macro? – Martin York May 13 '11 at 19:34
  • @MArtin: you're right, haven't though at that aspect or shifting more than 32. – Remus Rusanu May 13 '11 at 19:37
  • @Remus Rusanu and MArtin: Thanks a lot! – artichoke101 May 13 '11 at 20:06
  • In addition to the shift larger than 32 problem mentioned, the macro versions have a number of common macro problems: the parameters used are not fully parenthesized, you may get undesired behavior if the parameters are not 32-bit types or are signed types, the macros are not 'statement safe' (they should be wrapped in a `do/while(0)` or similar). Also, the macros appear to perform shift operations, but the names suggest rotation - I'd find that confusing if I came across code using them. Anyone considering using them should take care to fix the problems. Or use the function versions. – Michael Burr May 14 '11 at 00:53
  • @Michael Burr: correct in every point. In fact I removed the macros, someone may find this post by google months from now and use them macros... – Remus Rusanu May 14 '11 at 01:14
  • @Remus: I probably should have simply suggested that instead of mini-ranting. I'll delete the comment (and this) in several minutes. – Michael Burr May 14 '11 at 01:19
  • BTW, even the function version will not work if the 128bit integer stored in the 4 individual 32bit ints is 'signed'. Shiftr128 would have to fill with 1s, not 0s, if leftmost bit of 'a' is 1 and carry over the 1s across the 32bit chunks boundary... – Remus Rusanu May 14 '11 at 01:19
  • @Michael Burr: I don't take that as a rant at all :) I've been burned by exactly those problems you mention many times, so I stand to shame for making the same mistakes myself. As for why I used ROT instead of SHIFT, was probably no later than yesterday I searched a very large code base for a macro/function to do a rot32 :), and I found the CRT was offering it already http://msdn.microsoft.com/en-us/library/5cc576c4%28v=vs.80%29.aspx... – Remus Rusanu May 14 '11 at 01:22
  • 1
    @Remus Rusanu: There is still a bug in this code. The condition for recursing should be `if (k >= 32)`. – Henry Oct 17 '12 at 18:33
  • Try compiling with GCC 4.8 and above or Clang 3.2 and above ***using*** `-fsanitize=undefined`. UBsan is such a tattle tale .... – jww Jul 19 '15 at 02:44
2

Instead of using a 128 bit number why not use a bitset? Using a bitset, you can adjust how big you want it to be. Plus you can perform quite a few operations on it.

You can find more information on these here:

http://www.cppreference.com/wiki/utility/bitset/start?do=backlink

C Johnson
  • 14,441
  • 9
  • 56
  • 70
  • 1
    @C Johnson I agree a bitset can be used, but I am looking at it from more of a logical standpoint and having those 4 32-bit ints as a constraint – artichoke101 May 13 '11 at 19:10
1

First, if you're shifting by n bits and n is greater than or equal to 32, divide by 32 and shift whole integers. This should be trivial. Now you're left with a remaining shift count from 0 to 31. If it's zero, return early, you're done.

For each integer you'll need to shift by the remaining n, then shift the adjacent integer by the same amount and combine the valid bits from each.

Mark Ransom
  • 271,357
  • 39
  • 345
  • 578
0

Since you mentioned you're storing your 128-bit value in an array of 4 integers, you could do the following:

void left_shift(unsigned int* array)
{   
    for (int i=3; i >= 0; i--)
    {
        array[i] = array[i] << 1;

        if (i > 0)
        {
            unsigned int top_bit = (array[i-1] >> 31) & 0x1;
            array[i] = array[i] | top_bit;
        }
    }
}

void right_shift(unsigned int* array)
{   
    for (int i=0; i < 4; i++)
    {
        array[i] = array[i] >> 1;

        if (i < 3)
        {
            unsigned int bottom_bit = (array[i+1] & 0x1) << 31;
            array[i] = array[i] | bottom_bit;
        }
    }
}
Jason
  • 30,174
  • 7
  • 55
  • 73