简体   繁体   中英

How to sign extend a varying sized number to 16 bits in C?

The varying sized number could either be 10-bits or 12-bits or 15-bits. I want to sign extend the immediate bits so that the 16-bit integer has the same value as the 10, 12, or 15-bit value.

EDIT

I'm sorry I didn't post the code I wrote, that isn't working. Here it is:

switch (instr):
{
    case IMMI_10:
    int bits_to_cover = 16 - 10;
    signed short amount = (instr & 15);
    if (amount & (1 << bits_to_cover >> 1))
    {
        amount -= 1 << bits_to_cover;
    }
    break;


    case IMMI_12:
    int bits_to_cover = 16 - 12;
    signed short amount = (instr & 15);
    if (amount & (1 << bits_to_cover >> 1))
    {
        amount -= 1 << bits_to_cover;
    }
    break;


    case IMMI_15:
    int bits_to_cover = 16 - 15;
    signed short amount = (instr & 15);
    if (amount & (1 << bits_to_cover >> 1))
    {
        amount -= 1 << bits_to_cover;
    }
    break;
}

This is running on a special machine built for my CSE class in school. It is not running the x86 architecture. It is called the CSE 410 machine. Documentation: https://courses.cs.washington.edu/courses/cse410/18sp/410machine/isaManual.html

I'm working on the basis that if, for example, you have a 10-bit number to be sign-extended to 16 bits, then you have two cases to consider:

zzzzzz1x xxxxxxxx
zzzzzz0x xxxxxxxx

The x's are "don't care — must copy" bits in the result. The leading z's are "don't care — will overwrite" bits. And the bit that switches between 0 and 1 in the two examples is the sign bit which must be copied over what are shown as leading z's. I'm also assuming that the bits are numbered from 0 (the least significant bit or LSB) to 15 (the most significant bit or MSB). If you need to number the bits from 1 to 16, then you have some adjustments to make.

Given a function signature:

uint16_t sign_extend(uint16_t value, uint16_t bits)

we can determine the sign bit with:

uint16_t sign = (1 << (bits - 1)) & value;

We can 0-extend a value with a positive (0) sign bit by and'ing the value with the bit mask:

00000001 11111111

We can 1-extend a value with a negative (1) sign bit by or'ing the value with the bit mask:

11111110 00000000

In the code below, I generate the second mask with:

uint16_t mask = ((~0U) >> (bits - 1)) << (bits - 1);

and use the bit-wise inversion to generate the other.

The code avoids making assumptions about what happens when you right-shift a negative value. (See the comment by samgak .) The C standard says this is implementation-defined behaviour, and the usual cases are 'copy the MSB (sign) bit into the vacated bit' (aka arithmetic shift right) or 'set vacated bits to zero' (aka logical shift right). Both are permitted, but a given compiler must use one or the other. This code will work regardless of what the compiler does because it avoids right-shifting signed quantities. (To make up for that, it assumes you can assign signed integers to the corresponding unsigned integer type, and vice versa, even if the signed value is negative. Formally, the standard only requires that to work for the common subset of values — from 0 to <signed-type>_MAX , but I've not heard of systems where this is a problem, whereas I've heard of systems where shifting is handled differently.)

Putting it all together, here's the function I'd use, in a test harness:

#include <assert.h>
#include <stdint.h>

extern uint16_t sign_extend(uint16_t value, uint16_t bits);

uint16_t sign_extend(uint16_t value, uint16_t bits)
{
    assert(bits > 0 && bits < 16);
    uint16_t sign = (1 << (bits - 1)) & value;
    uint16_t mask = ((~0U) >> (bits - 1)) << (bits - 1);
    if (sign != 0)
        value |= mask;
    else
        value &= ~mask;
    return value;
}

#ifdef TEST

#include <stdio.h>

struct TestSignExtend
{
    uint16_t    value;
    uint16_t    bits;
    uint16_t    result;
};

static int test_sign_extend(const struct TestSignExtend *test)
{
    uint16_t result = sign_extend(test->value, test->bits);
    const char *pass = (result == test->result) ? "** PASS **" : "== FAIL ==";
    printf("%s: value = 0x%.4X, bits = %2d, wanted = 0x%.4X, actual = 0x%.4X\n",
            pass, test->value, test->bits, test->result, result);
    return(result == test->result);
}

int main(void)
{
    struct TestSignExtend tests[] =
    {
        { 0x0123, 10, 0x0123 },
        { 0x0323, 10, 0xFF23 },
        { 0x0323, 11, 0x0323 },
        { 0x0723, 11, 0xFF23 },
        { 0x0323, 12, 0x0323 },
        { 0x0C23, 12, 0xFC23 },
        { 0x0323, 13, 0x0323 },
        { 0x1723, 13, 0xF723 },
        { 0x1323, 14, 0x1323 },
        { 0x3723, 14, 0xF723 },
        { 0x0323, 15, 0x0323 },
        { 0xC723, 15, 0xC723 },
        { 0x0123,  9, 0xFF23 },
        { 0x0223,  9, 0x0023 },
        { 0x0129,  8, 0x0029 },
        { 0x03E9,  8, 0xFFE9 },
    };
    enum { NUM_TESTS = sizeof(tests) / sizeof(tests[0]) };
    int pass = 0;

    for (int i = 0; i < NUM_TESTS; i++)
        pass += test_sign_extend(&tests[i]);
    if (pass == NUM_TESTS)
        printf("PASS - All %d tests passed\n", NUM_TESTS);
    else
        printf("FAIL - %d tests failed out of %d run\n", NUM_TESTS - pass, NUM_TESTS);

    return(pass != NUM_TESTS);  /* Process logic is inverted! */
}

#endif /* TEST */

Sample output:

** PASS **: value = 0x0123, bits = 10, wanted = 0x0123, actual = 0x0123
** PASS **: value = 0x0323, bits = 10, wanted = 0xFF23, actual = 0xFF23
** PASS **: value = 0x0323, bits = 11, wanted = 0x0323, actual = 0x0323
** PASS **: value = 0x0723, bits = 11, wanted = 0xFF23, actual = 0xFF23
** PASS **: value = 0x0323, bits = 12, wanted = 0x0323, actual = 0x0323
** PASS **: value = 0x0C23, bits = 12, wanted = 0xFC23, actual = 0xFC23
** PASS **: value = 0x0323, bits = 13, wanted = 0x0323, actual = 0x0323
** PASS **: value = 0x1723, bits = 13, wanted = 0xF723, actual = 0xF723
** PASS **: value = 0x1323, bits = 14, wanted = 0x1323, actual = 0x1323
** PASS **: value = 0x3723, bits = 14, wanted = 0xF723, actual = 0xF723
** PASS **: value = 0x0323, bits = 15, wanted = 0x0323, actual = 0x0323
** PASS **: value = 0xC723, bits = 15, wanted = 0xC723, actual = 0xC723
** PASS **: value = 0x0123, bits =  9, wanted = 0xFF23, actual = 0xFF23
** PASS **: value = 0x0223, bits =  9, wanted = 0x0023, actual = 0x0023
** PASS **: value = 0x0129, bits =  8, wanted = 0x0029, actual = 0x0029
** PASS **: value = 0x03E9, bits =  8, wanted = 0xFFE9, actual = 0xFFE9
PASS - All 16 tests passed

I did make a deliberate error in one of the tests after everything was passing first go, just to ensure that failures would be spotted.

In your code, you might use it like this:

signed short value = …;

switch (instr):
{
case IMMI_10:
    value = sign_extend(value, 10);
    break;
case IMMI_12:
    value = sign_extend(value, 12);
    break;
case IMMI_15:
    value = sign_extend(value, 15);
    break;
default:
    assert(0 && "can't happen!");
}

If the case labels IMMI_10 , IMMI_12 and IMMI_15 have the values 10 , 12 , 15 , then you could avoid the switch and simply use an assignment:

signed short value = …;   // Get the value from somewhere
value = sign_extend(value, instr);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM