Sign extension from 16 to 32 bits in C

43,051

Solution 1

Try:

int signExtension(int instr) {
    int value = (0x0000FFFF & instr);
    int mask = 0x00008000;
    if (mask & instr) {
        value += 0xFFFF0000;
    }
    return value;
}

Solution 2

Why is wrong with:

int16_t s = -890;
int32_t i = s;  //this does the job, doesn't it?

Solution 3

what's wrong in using the builtin types?

int32_t signExtension(int32_t instr) {
    int16_t value = (int16_t)instr;
    return (int32_t)value;
}

or better yet (this might generate a warning if passed a int32_t)

int32_t signExtension(int16_t instr) {
    return (int32_t)instr;
}

or, for all that matters, replace signExtension(value) with ((int32_t)(int16_t)value)

you obviously need to include <stdint.h> for the int16_t and int32_t data types.

Solution 4

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 proposals. Just make sure you are using signed variables and then use 2 shifts.

long value;   // 32 bit storage
value=0xffff; // 16 bit 2's complement -1, value is now 0x0000ffff
value = ((value << 16) >> 16); // value is now 0xffffffff

If the variable is signed then the C compiler translates >> to Arithmetic Shift Right which preserves sign. This behaviour is platform independent.

So, assuming that value starts of with 0x1ff then we have, << 16 will SL (Shift Left) the value so instr is now 0xff80, then >> 16 will ASR the value so instr is now 0xffff.

If you really want to have fun with macros then try something like this (syntax works in GCC haven't tried in MSVC).

#include <stdio.h>

#define INT8 signed char
#define INT16 signed short
#define INT32 signed long
#define INT64 signed long long
#define SIGN_EXTEND(to, from, value) ((INT##to)((INT##to)(((INT##to)value) << (to - from)) >> (to - from)))

int main(int argc, char *argv[], char *envp[])
{
    INT16 value16 = 0x10f;
    INT32 value32 = 0x10f;
    printf("SIGN_EXTEND(8,3,6)=%i\n", SIGN_EXTEND(8,3,6));
    printf("LITERAL         SIGN_EXTEND(16,9,0x10f)=%i\n", SIGN_EXTEND(16,9,0x10f));
    printf("16 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=%i\n", SIGN_EXTEND(16,9,value16));
    printf("32 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=%i\n", SIGN_EXTEND(16,9,value32));

    return 0;
}

This produces the following output:

SIGN_EXTEND(8,3,6)=-2
LITERAL         SIGN_EXTEND(16,9,0x10f)=-241
16 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=-241
32 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=-241

Solution 5

People pointed out casting and a left shift followed by an arithmetic right shift. Another way that requires no branching:

(0xffff & n ^ 0x8000) - 0x8000

If the upper 16 bits are already zeroes:

(n ^ 0x8000) - 0x8000

• Community wiki as it's an idea from "The Aggregate Magic Algorithms, Sign Extension"

Share:
43,051
Sorin Cioban
Author by

Sorin Cioban

Updated on August 22, 2020

Comments

  • Sorin Cioban
    Sorin Cioban over 3 years

    I have to do a sign extension for a 16-bit integer and for some reason, it seems not to be working properly. Could anyone please tell me where the bug is in the code? I've been working on it for hours.

    int signExtension(int instr) {
        int value = (0x0000FFFF & instr);
        int mask = 0x00008000;
        int sign = (mask & instr) >> 15;
        if (sign == 1)
            value += 0xFFFF0000;
        return value;
    }
    

    The instruction (instr) is 32 bits and inside it I have a 16bit number.

  • JeremyP
    JeremyP almost 13 years
    Given that the question explicitly asks for extension from 16 to 32 bits, consider using int16_t and int32_t instead of short and int
  • JeremyP
    JeremyP almost 13 years
    See my comment on Nawaz's answer
  • qbert220
    qbert220 almost 13 years
    @Nawaz: Your solution is not portable. Type sizes can vary across compilers.
  • Nawaz
    Nawaz almost 13 years
    @qbert220: You mean int16_t is not necessarily 16 bit signed integer?
  • qbert220
    qbert220 almost 13 years
    @Sorin: You've updated the question so it now behaves like my answer. Both do correct sign extension of a 16 bit value to 32 bit value.
  • JeremyP
    JeremyP almost 13 years
    @Nawaz: I think he was commenting on your original answer.
  • DipSwitch
    DipSwitch almost 13 years
    You could also use a macro instead of the function #define signExtension(x) ((int32_t)((int16_t)x)) because this would be way faster than doing it via a function call :)
  • Steve Jessop
    Steve Jessop almost 13 years
    @DipSwitch: my compiler's better than yours, it can inline such function calls and so the macro is no faster :-)
  • Steve Jessop
    Steve Jessop almost 13 years
    @Nawaz: but edits to posts aren't pushed out to people who've already loaded the page, so the time it takes to read the answers above yours is how out-of-date people's comments on your answer will be.
  • Nawaz
    Nawaz almost 13 years
    @Steve: Hmmm.. makes sense. I didn't think that could be the case.
  • Steve Jessop
    Steve Jessop almost 13 years
    @CAFxX: I'd expect the "or better yet" to provoke compiler warnings among the paranoid, since calling it with a computed value in a type bigger than 16 bits discards information if the resulting value is out of range. Perhaps the value passed in shouldn't be out of range, perhaps discarding the top bits is deliberate, but the compiler doesn't know whether you know that. So some compilers/options will force the caller to use an explicit cast, which may or may not be desirable depending why this 16 bit instr is in a 32 bit type in the first place.
  • qbert220
    qbert220 almost 13 years
    @Nawaz: I was indeed commenting on your original answer which used short and int types. int16_t will always be 16 bits.
  • CAFxX
    CAFxX almost 13 years
    @Steve you're right, I just wasn't sure if the OP had a 16 bit value inside a int32_t or inside a int16_t.
  • qbert220
    qbert220 almost 13 years
    Nawaz's revised answer is a better solution than mine!
  • Ben Voigt
    Ben Voigt almost 13 years
    Don't use + for bit manipulation, the correct operator here is |.
  • Ben Voigt
    Ben Voigt almost 13 years
    @Steve: The first version also invokes implementation-defined behavior, if the argument can't be expressed by int16_t.
  • DipSwitch
    DipSwitch almost 13 years
    @Steve sure your's is better, but most of the time you don't make an inline function for such small bit manipulations. And since we don't know the input he is giving it is cleaner to make it an macro because now we won't get any compiler warnings. If we declare the input int16_t you get a compiler warning because your a chopping of some bits if you pass it along. Also gcc is better in optimizing macro's last time i checked #define ROL(x,y) ((x << y) | (x >> (sizeof(x) - y))) was optimized to the rol machine instruction and the inline variant not...
  • R.. GitHub STOP HELPING ICE
    R.. GitHub STOP HELPING ICE almost 13 years
    This answer invokes UB, assuming int is only 32 bits, due to signed overflow. If you're going to implement your own sign extension, you need to use unsigned types, but you'd be better off just letting the compiler do it.
  • R.. GitHub STOP HELPING ICE
    R.. GitHub STOP HELPING ICE almost 13 years
    The inline variant will be optimized just like the macro. And your macro could really use some work - it's not even properly protecting its arguments...
  • thenickdude
    thenickdude over 9 years
    This suffers from the problem that shifting into the sign bit is undefined behaviour for signed integers: blog.regehr.org/archives/738
  • Holger Bille
    Holger Bille about 7 years
    This is the superior answer - goes for any bit length - thanks, just what I needed :-)
  • CAFxX
    CAFxX almost 6 years
    long is not guaranteed to be 32 bit (it's only guaranteed to be at least as big as int, and int itself is not required to be 32 bit) -- so the claim that the approach here is "platform independent" is laughable. Also see @thenickdude's comment about UB.