2

I've been working on this puzzle for awhile. I'm trying to figure out how to rotate 4 bits in a number (x) around to the left (with wrapping) by n where 0 <= n <= 31.. The code will look like:

moveNib(int x, int n){
//... some code here
}

The trick is that I can only use these operators:

~ & ^ | + << >>

and of them only a combination of 25. I also can not use If statements, loops, function calls. And I may only use type int.

An example would be moveNib(0x87654321,1) = 0x76543218.

My attempt: I have figured out how to use a mask to store the the bits and all but I can't figure out how to move by an arbitrary number. Any help would be appreciated thank you!

Shaw
  • 169
  • 1
  • 3
  • 12
  • Are you trying to pick a nibble in the number and rotate it, or are you trying to rotate the whole number by multiples of 1 nibble? – user2357112 Mar 02 '14 at 02:47
  • What do you mean by "a combination of 25"? Also, are you sure `-` isn't on the list of allowed operators? If not, it can easily be emulated via `~` and `+` assuming a two's complement architecture, but it seems odd to allow `+` but not `-`. – amaurea Mar 02 '14 at 02:47
  • Why is `moveNib(0x87654321,1)` supposed to return `0x76543218`? – user2357112 Mar 02 '14 at 02:49
  • user: That would be consistent with rotating the nibbles one step to the left. – amaurea Mar 02 '14 at 02:50
  • `-` can be emulated with a `+` and a `~`. – Sergey Kalinichenko Mar 02 '14 at 02:51
  • @amaurea: It would also be consistent with a number of other interpretations. I'd rather not guess. – user2357112 Mar 02 '14 at 03:00
  • 1
    @user2357112 It's rather obvious; no guessing needed. Your first post posed two alternatives, one of which 0x76543218 satisfies and the other of which it doesn't. It's also clear from the description and the name of the function -- rotating a nibble in place wouldn't be called "move", and it would require an additional parameter so as to specify both which nibble and how many bits to rotate by. – Jim Balter Mar 02 '14 at 03:01

2 Answers2

4

How about:

uint32_t moveNib(uint32_t x, int n) { return x<<(n<<2) | x>>((8-n)<<2); }

It uses <<2 to convert from nibbles to bits, and then shifts the bits by that much. To handle wraparound, we OR by a copy of the number which has been shifted by the opposite amount in the opposite direciton. For example, with x=0x87654321 and n=1, the left part is shifted 4 bits to the left and becomes 0x76543210, and the right part is shifted 28 bits to the right and becomes 0x00000008, and when ORed together, the result is 0x76543218, as requested.

Edit: If - really isn't allowed, then this will get the same result (assuming an architecture with two's complement integers) without using it:

uint32_t moveNib(uint32_t x, int n) { return x<<(n<<2) | x>>((9+~n)<<2); } 

Edit2: OK. Since you aren't allowed to use anything but int, how about this, then?

int moveNib(int x, int n) { return (x&0xffffffff)<<(n<<2) | (x&0xffffffff)>>((9+~n)<<2); }

The logic is the same as before, but we force the calculation to use unsigned integers by ANDing with 0xffffffff. All this assumes 32 bit integers, though. Is there anything else I have missed now?

Edit3: Here's one more version, which should be a bit more portable:

int moveNib(int x, int n) { return ((x|0u)<<((n&7)<<2) | (x|0u)>>((9+~(n&7))<<2))&0xffffffff; }

It caps n as suggested by chux, and uses |0u to convert to unsigned in order to avoid the sign bit duplication you get with signed integers. This works because (from the standard):

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Since int and 0u have the same rank, but 0u is unsigned, then the result is unsigned, even though ORing with 0 otherwise would be a null operation.

It then truncates the result to the range of a 32-bit int so that the function will still work if ints have more bits than this (though the rotation will still be performed on the lowest 32 bits in that case. A 64-bit version would replace 7 by 15, 9 by 17 and truncate using 0xffffffffffffffff).

This solution uses 12 operators (11 if you skip the truncation, 10 if you store n&7 in a variable).

To see what happens in detail here, let's go through it for the example you gave: x=0x87654321, n=1. x|0u results in a the unsigned number 0x87654321u. (n&7)<<2=4, so we will shift 4 bits to the left, while ((9+~(n&7))<<2=28, so we will shift 28 bits to the right. So putting this together, we will compute 0x87654321u<<4 | 0x87654321u >> 28. For 32-bit integers, this is 0x76543210|0x8=0x76543218. But for 64-bit integers it is 0x876543210|0x8=0x876543218, so in that case we need to truncate to 32 bits, which is what the final &0xffffffff does. If the integers are shorter than 32 bits, then this won't work, but your example in the question had 32 bits, so I assume the integer types are at least that long.

As a small side-note: If you allow one operator which is not on the list, the sizeof operator, then we can make a version that works with all the bits of a longer int automatically. Inspired by Aki, we get (using 16 operators (remember, sizeof is an operator in C)):

int moveNib(int x, int n) {
    int nbit = (n&((sizeof(int)<<1)+~0u))<<2;
    return (x|0u)<<nbit | (x|0u)>>((sizeof(int)<<3)+1u+~nbit);
}
amaurea
  • 4,950
  • 26
  • 35
  • `8-n` will produce a negative result when `n > 8`. OP explicitly allowed that. I don't think this is enough reason for a downvote, though. – Sergey Kalinichenko Mar 02 '14 at 02:53
  • That's why I used unsigned integers rather than signed ones. I've tested the function, and it works for numbers larger than 8 too (as well as negative values of n). I would also like to know the reason for the downvote. – amaurea Mar 02 '14 at 02:55
  • 2
    "And I may only use type int." – Nikita Kouevda Mar 02 '14 at 02:56
  • Ok, I missed that. I have edited my response to take it into account. It's not pretty, but it should fulfill all requirements now. – amaurea Mar 02 '14 at 03:32
  • 1
    Assuming 32-bit `int`, shifts greater than 31 are not well defined even if they work as desired on your machine. Suggest rather than `n`, use `(n&7)`. – chux - Reinstate Monica Mar 02 '14 at 04:43
  • Minor: `(x&0xffffffff)` does not force an `unsigned` should `INT_MAX >= 0xffffffff` as with 64- bit `int`. `(x&0xffffffffu)` will do. +1 as this answer is really close enough anyways. – chux - Reinstate Monica Mar 02 '14 at 04:47
  • @amaurea I tried out both 2 and 3 of your solutions (i normally like to switch up the code but for testing purposes i copied your lines exactly). When testing, I used the number x = 0x80000000 and n = 1 but the result is still 0x80000000 when the answer should be 0x8 (found this to be the case for both 2 and 3). For number 3 I do not really understand what the 1u is or what its purpose is. And it (the 1u) might be outside the scope of the question. Thank you so much for your help though! – Shaw Mar 03 '14 at 02:22
  • actually I want to ask something else. In Edit:2 you used x & 0xffffffff. wouldnt that just return x every time? It seems like a waste of an opp. – Shaw Mar 03 '14 at 03:35
  • @Shaw There was a bug in #3. It should have been `0u`, not `1u`. #2 should work as given, though. I just tried it (and the fixed #3) with `x=0x80000000` and `n=1`, and I got the result `8`, just as I should. The purpose of `&0xffffffff` and `|0u` *is* to have no effect except to turn the result into an unsigned, to avoid the sign bit duplication problem. Even if that didn't work, I still don't see why you should get the same result as the input. Are you sure you copied the function properly? – amaurea Mar 03 '14 at 10:01
  • 1
    @Shaw I have edited my answer with more details now. I still don't understand why it doesn't work for you. I have both tested it on my own computer and checked the standard to see that it should work on any computer two's complement integers. I would be very interested in hearing what you get for each of the steps in the detailed example I gave. – amaurea Mar 03 '14 at 10:53
  • The good news is that your Edit: 3 works! The bad news is that I can't use u0. I tried Edit:2 but I got a result of 0xfffffff8. I'll write up a run through of whats happening. – Shaw Mar 03 '14 at 16:09
  • Why can't you use `0u`? Is it considered breaking the rule of "you can only use `int` by smuggling in unsigned numbers? – amaurea Mar 03 '14 at 16:17
  • pretty much. I am going to write out step by step what I am getting with edit 2. It might help. – Shaw Mar 03 '14 at 16:26
  • I am going to break it down into seperate parts for this: – Shaw Mar 03 '14 at 16:29
  • @amaurea Yikes, I cant figure out how to make this readable. Sorry. I am going to break it down into seperate parts for this: int hold = x & 0xffffffff; //this returns 80000000----- int hold2 = n<<2; // this returns 4----- hold2 = hold << hold2; // this returns 0----- int hold3 = 9 + ~n; //this returns 7----- hold3 = hold3 << 2; //this returns 1c----- hold3 = hold >> hold3; // this returns fffffff8----- hold = hold2 | hold3; // this returns fffffff8-----. I think I got everything but let me know if something was missed. – Shaw Mar 03 '14 at 16:41
  • The `|0u` and `x&0xffffffff` will not work if you store the result in `int` variables, since that will turn them back into signed integers. So if you actually created that `hold` variable, then it's no wonder it didn't work. You should try entering it exactly as I wrote it (without the extra variables). – amaurea Mar 03 '14 at 16:52
  • I did. I just used hold to demonstrate. While your edit 3 does work it it outside the scope. Thank you for all the help though. I was looking around and ended up figuring out another solution. – Shaw Mar 03 '14 at 16:58
  • Ok. If it isn't too much work, you should consider posting your final solution as an answer to your own question, so it might benefit others too. I'm also curious to see it. I also think Aki's approach should work. – amaurea Mar 03 '14 at 17:32
3

Without the additional restrictions, the typical rotate_left operation (by 0 < n < 32) is trivial.

uint32_t X = (x << 4*n) | (x >> 4*(8-n));

Since we are talking about rotations, n < 0 is not a problem. Rotation right by 1 is the same as rotation left by 7 units. Ie. nn=n & 7; and we are through.

int nn = (n & 7) << 2;                         // Remove the multiplication
uint32_t X = (x << nn) | (x >> (32-nn));

When nn == 0, x would be shifted by 32, which is undefined. This can be replaced simply with x >> 0, i.e. no rotation at all. (x << 0) | (x >> 0) == x.

Replacing the subtraction with addition: a - b = a + (~b+1) and simplifying:

int nn = (n & 7) << 2;
int mm = (33 + ~nn) & 31;
uint32_t X = (x << nn) | (x >> mm);    // when nn=0, also mm=0

Now the only problem is in shifting a signed int x right, which would duplicate the sign bit. That should be cured by a mask: (x << nn) - 1

int nn = (n & 7) << 2;
int mm = (33 + ~nn) & 31;
int result = (x << nn) | ((x >> mm) & ((1 << nn) + ~0));

At this point we have used just 12 of the allowed operations -- next we can start to dig into the problem of sizeof(int)...

int nn = (n & (sizeof(int)-1)) << 2; // etc.
Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57
  • No, it's not. OTOH it's not necessary to calculate that, since the problem description defines `sizeof int == 4`. – Aki Suihkonen Mar 03 '14 at 11:06
  • Or rather, the examples given in the problem show that the rotation should happen on only the least significant 4 bytes of the number, which isn't quite the same (it's why I truncate to 32 bits in the end). – amaurea Mar 03 '14 at 11:22