简体   繁体   English

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

[英]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). 我在无符号整数(uint32_t)中有很多位数(位数可以更改)。 For example (12 bits in the example): 例如(示例中为12位):

uint32_t a = 0xF9C;

The bits represent a signed int of that length. 这些位表示该长度的有符号整数。 In this case the number in decimal should be -100. 在这种情况下,十进制数应为-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) 它只是值3996,因为它已强制转换为(0x00000F9C),但实际上需要为(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 现在我得到正确的值-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). 我的解决方案不是很灵活,因为我提到位数可以变化(1-64位之间的任何值)。

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

A branch free way to sign extend a bitfield (Henry S. Warren Jr., CACM v20 n6 June 1977) is this: 一种免费的分支签名方式来扩展位域(Henry S. Warren Jr.,CACM v20 n6 1977年6月)是这样的:

// 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 根据@Lundin的评论进行更新

Here's tested code (prints -100): 这是经过测试的代码(打印-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;
}

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: 首先,将无符号整数一直向左移动,直到符号位变为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;
}

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

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. 执行此操作的理智,可移植且有效的方法是简单地屏蔽掉数据部分,然后用0xFF ...填充其他所有内容以获取正确的2的补码表示形式。 You need to know is how many bits that are the data part. 您需要知道多少位是数据部分。

  • We can mask out the data with (1u << data_length) - 1 . 我们可以用(1u << data_length) - 1屏蔽数据。
  • In this case with data_length = 8 , the data mask becomes 0xFF . data_length = 8情况下,数据掩码变为0xFF Lets call this data_mask . 让我们称之为data_mask
  • Thus the data part of the number is a & data_mask . 因此,数字的数据部分是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. 只需执行~data_mask即可实现。
  • C code: a = (a & data_mask) | ~data_mask C代码: a = (a & data_mask) | ~data_mask a = (a & data_mask) | ~data_mask . a = (a & data_mask) | ~data_mask Now a is proper 32 bit 2's complement. 现在, a是正确的32位2的补码。

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. 这依赖于int是32位2的补码,但否则可以完全移植。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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