转换可变位大小的有符号整数

Convert signed int of variable bit size

我在无符号整数 (uint32_t) 中有多个位数(位数可以更改)。例如(示例中为 12 位):

uint32_t a = 0xF9C;

这些位表示该长度的有符号整数。 在这种情况下,十进制数应为 -100。 我想将变量存储在一个带符号的变量中并获取实际值。 如果我只是使用:

int32_t b = (int32_t)a;

它将只是值 3996,因为它被强制转换为 (0x00000F9C) 但实际上需要是 (0xFFFFFF9C)

我知道一种方法:

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

现在我得到正确的值 -100

但是有更好的方法吗? 我的解决方案不是很灵活,正如我提到的位数可以变化(1-64 位之间的任何值)。

这依赖于右移有符号负整数时符号扩展的实现定义行为。首先,将无符号整数一直向左移动,直到符号位变为 MSB,然后将其转换为有符号整数并移回:

#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;
}

But is there a better way to do it?

嗯,取决于你所说的 "better" 是什么意思。下面的示例显示了一种更灵活的方法,因为位字段的大小不固定。如果您的用例需要不同的位大小,您可以考虑使用 "better" 方式。

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);
}

输出:

-100
1948

这是一个解决你的问题的方法:

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;
}

一种无分支方式来签署扩展位域(Henry S. Warren Jr.,CACM v20 n6 June 1977)是这样的:

// 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;

根据@Lundin 的评论更新

这是经过测试的代码(打印 -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;
}

执行此操作的明智、便携且有效的方法是简单地屏蔽掉数据部分,然后用 0xFF 填充其他所有内容...以获得正确的 2 的补码表示。您需要知道数据部分有多少位。

  • 我们可以用 (1u << data_length) - 1 屏蔽掉数据。
  • 在这种情况下 data_length = 8,数据掩码变为 0xFF。让我们称之为 data_mask.
  • 所以数字的数据部分是a & data_mask.
  • 数字的其余部分需要用零填充。也就是说,所有不属于数据掩码的部分。只需 ~data_mask 即可实现。
  • C 代码:a = (a & data_mask) | ~data_mask。现在 a 是正确的 32 位 2 的补码。

示例:

#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);
}

输出:

FFFFFF9C        -100

这依赖于 int 是 32 位 2 的补码,但在其他方面是完全可移植的。