I have a short, instr
, that looks like this:
1110xxx111111111
I need to pull out bits 0-9, which I do with (instr &
* No branching required *
See http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend for a list of very useful bit hacks. Specifically, sign extending a number is as simple as:
/* generate the sign bit mask. 'b' is the extracted number of bits */
int m = 1U << (b - 1);
/* Transform a 'b' bits unsigned number 'x' into a signed number 'r' */
int r = (x ^ m) - m;
You may need to clear the uppermost bits of 'x' if they are not zero ( x = x & ((1U << b) - 1);
) before using the above procedure.
If the number of bits 'b' is known at compile time (e.g., 5 bits in your case) there's even a simpler solution (this might trigger a specific sign-extend instruction if the processor supports it and the compiler is clever enough):
struct {signed int x:5;} s;
r = s.x = x;
(instr & 0x1FF) * (1 - ((unsigned short)(instr & 0x100) >> 7))
How does it work? It selects your sign bit and shifts it to the 2's position. This is used to generate either the value 1 (if your sign bit was absent) or -1 (if your sign bit was present).
This solution is branchless and does not depend on undefined behavior.
An easier solution is this, for x
being a 5-bit 2's complement number, look:
z = (x^16)-16
I'm not sure how you're getting 13 1 bits after masking with 0x1ff
, but this should sign-extend a 9-bit number into a 16-bit short. Not pretty (or particularly efficient), but it works:
(instr & 0x1ff) | (0xfe00 * ((instr & 0x100) >> 8))
Mask out the sign bit, shift to the 1 position to get 0/1. Multiply this by the the upper bits, if the sign is 1, then the 9-bit number will be OR'ed with 0xfe
, which will set all the upper bits to 1.
Just bumped into this looking for something else, maybe a bit late, but maybe it'll be useful for someone else. AFAIAC all C programmers should start off programming assembler.
Anyway sign extending is much easier than the other 2 proposals. Just make sure you are using signed variables and then use 2 shifts.
short instr = state->mem[state->pc];
unsigned int reg = (instr >> 9) & 7; // 0b111
instr &= 0x1ff; // get lower 9 bits
instr = ((instr << 7) >> 7); // sign extend
state->regs[reg] = state->pc + instr;
If the variable is signed then the C compiler translates >> to Arithmetic Shift Right which preserved sign. This behaviour is platform independent.
So, assuming that instr starts of with 0x1ff then we have, << 7 will SL (Shift Left) the value so instr is now 0xff80, then >> 7 will ASR the value so instr is now 0xffff.
Assuming a short is 16 bits:
You can do it manually: (instr & 0x1FF) | ((instr & 0x100) ? 0xFE00 : 0)
. This tests the sign bit (the uppermost bit you are retaining, 0x100
) and sets all the bits above it if the sign bit is set. You can extend this to 5 bits by adapting the masks to 0x1F
, 0x10
and 0xFFE0
, being the lower 5 bits, the 5th bit itself and all the bits 5-16 respectively.
Or you can find some excuse to assign the bits to the upper part of a signed short and shift them down (getting a sign-extension in the process): short x = (instr & 0x1FF) << 7; x >>= 7;
The latter may actually end up being more straightforward in assembly and will not involve a branch. If instr
is signed this can be done in a single expression: (instr & 0x1FF) << 7 >> 7
. Since that already removes the upper bits it simplifies to instr << 7 >> 7
. Replace 7 with 11 for 5 bits (16-5).