Sign extend a nine-bit number in C

前端 未结 7 2036
情话喂你
情话喂你 2020-12-15 05:18

I have a short, instr, that looks like this:

1110xxx111111111

I need to pull out bits 0-9, which I do with (instr &

相关标签:
7条回答
  • 2020-12-15 05:44

    * 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;
    
    0 讨论(0)
  • 2020-12-15 05:49
    (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.

    0 讨论(0)
  • 2020-12-15 05:49

    An easier solution is this, for x being a 5-bit 2's complement number, look:

    z = (x^16)-16
    
    0 讨论(0)
  • 2020-12-15 05:51

    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.

    0 讨论(0)
  • 2020-12-15 05:53

    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.

    0 讨论(0)
  • 2020-12-15 05:54

    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).

    0 讨论(0)
提交回复
热议问题