oliverleo
oliverleo

Reputation: 77

Convert signed int of variable bit size

I have a number of bits (the number of bits can change) in an unsigned int (uint32_t). For example (12 bits in the example):

uint32_t a = 0xF9C;

The bits represent a signed int of that length. In this case the number in decimal should be -100. I want to store the variable in a signed variable and gets is actual value. If I just use:

int32_t b = (int32_t)a;

it will be just the value 3996, since it gets casted to (0x00000F9C) but it actually needs to be (0xFFFFFF9C)

I know one way to do it:

union test
{
    signed temp :12;
}; 
union test x;
x.temp = a;
int32_t result = (int32_t) x.temp;

now i get the correct value -100

But is there a better way to do it? My solution is not very flexbile, as I mentioned the number of bits can vary (anything between 1-64bits).

Upvotes: 2

Views: 667

Answers (5)

Doug Currie
Doug Currie

Reputation: 41170

A branch free way to sign extend a bitfield (Henry S. Warren Jr., CACM v20 n6 June 1977) is this:

// value i of bit-length len is a bitfield to sign extend
// i is right aligned and zero-filled to the left
sext = 1 << (len - 1);
i = (i ^ sext) - sext;

UPDATE based on @Lundin's comment

Here's tested code (prints -100):

#include <stdio.h>
#include <stdint.h>

int32_t sign_extend (uint32_t x, int32_t len)
{
    int32_t i = (x & ((1u << len) - 1)); // or just x if you know there are no extraneous bits
    int32_t sext = 1 << (len - 1);
    return (i ^ sext) - sext;
}

int main(void)
{
    printf("%d\n", sign_extend(0xF9C, 12));
    return 0;
}

Upvotes: 1

Lundin
Lundin

Reputation: 213318

The sane, portable and effective way to do this is simply to mask out the data part, then fill up everything else with 0xFF... to get proper 2's complement representation. You need to know is how many bits that are the data part.

  • We can mask out the data with (1u << data_length) - 1.
  • In this case with data_length = 8, the data mask becomes 0xFF. Lets call this data_mask.
  • Thus the data part of the number is a & data_mask.
  • The rest of the number needs to be filled with zeroes. That is, everything not part of the data mask. Simply do ~data_mask to achieve that.
  • C code: a = (a & data_mask) | ~data_mask. Now a is proper 32 bit 2's complement.

Example:

#include <stdio.h>
#include <inttypes.h>

int main(void) 
{
  const uint32_t data_length = 8;
  const uint32_t data_mask = (1u << data_length) - 1;

  uint32_t a = 0xF9C;
  a = (a & data_mask) | ~data_mask;

  printf("%"PRIX32 "\t%"PRIi32, a, (int32_t)a);
}

Output:

FFFFFF9C        -100

This relies on int being 32 bits 2's complement but is otherwise fully portable.

Upvotes: -1

4386427
4386427

Reputation: 44264

But is there a better way to do it?

Well, depends on what you mean by "better". The example below shows a more flexible way of doing it as the size of the bit field isn't fixed. If your use case requires different bit sizes, you could consider it a "better" way.

unsigned sign_extend(unsigned x, unsigned num_bits)
{
    unsigned f = ~((1 << (num_bits-1)) - 1);
    if (x & f)  x = x | f;
    return x;
}


int main(void)
{
    int x = sign_extend(0xf9c, 12);
    printf("%d\n", x);

    int y = sign_extend(0x79c, 12);
    printf("%d\n", y);
}

Output:

-100
1948

Upvotes: 3

Ameen
Ameen

Reputation: 1857

This is a solution to your problem:

int32_t sign_extend(uint32_t x, uint32_t bit_size)
{
    // The expression (0xffffffff << bit_size) will fill the upper bits to sign extend the number.
    // The expression (-(x >> (bit_size-1))) is a mask that will zero the previous expression in case the number was positive (to avoid having an if statemet).
    return (0xffffffff << bit_size) & (-(x >> (bit_size-1))) | x;
}
int main()
{

    printf("%d\n", sign_extend(0xf9c, 12)); // -100
    printf("%d\n", sign_extend(0x7ff, 12)); // 2047

    return 0;
}

Upvotes: 0

Eugene Sh.
Eugene Sh.

Reputation: 18299

This relies on the implementation defined behavior of sign extension when right-shifting signed negative integers. First you shift your unsigned integer all the way left until the sign bit is becoming MSB, then you cast it to signed integer and shift back:

#include <stdio.h>
#include <stdint.h>

#define NUMBER_OF_BITS 12

int main(void) {
    uint32_t x = 0xF9C;
    int32_t y = (int32_t)(x << (32-NUMBER_OF_BITS)) >> (32-NUMBER_OF_BITS);

    printf("%d\n", y);

    return 0;
}

Upvotes: 0

Related Questions