转换可变位大小的有符号整数
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 的补码,但在其他方面是完全可移植的。
我在无符号整数 (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 的补码,但在其他方面是完全可移植的。