从8位值转换时如何签名扩展9位值?

时间:2013-04-11 02:43:19

标签: c int bit-manipulation signed stdint

我正在简单的虚拟机中实现相对分支功能。

基本上,我给出了一个8位的相对值。然后我将其向左移1位以使其成为9位值。因此,举例来说,如果你说“分支+127”,这实际上意味着127条指令,因此会增加256条IP。

我目前的代码如下:

uint8_t argument = 0xFF; //-1 or whatever
int16_t difference = argument << 1;
*ip += difference; //ip is a uint16_t

我不相信差异会被检测为小于0但是。关于如何签署未签名的作品,我很生气。除此之外,我不确定在IP参数是-1还是-2或者什么的情况下,从IP中减去差异是正确的。

基本上,我想要满足这些“测试”的东西

//case 1
argument = -5
difference -> -10
ip = 20 -> 10 //ip starts at 20, but becomes 10 after applying difference

//case 2
argument = 127 (must fit in a byte)
difference -> 254
ip = 20 -> 274

希望这会让它更加清晰。

无论如何,我怎么这么便宜呢?我看到一个类似问题的“解决方案”,但它涉及到分裂。我正在使用缓慢的嵌入式处理器(假设没有有效的乘法和除法方式),所以这是我想避免的一件非常重要的事情。

3 个答案:

答案 0 :(得分:0)

澄清一下:您担心左移8位负数将使其看起来像正9位数?只需在左移之前使用初始数字的符号位填充前9位:

diff = 0xFF;
int16 diff16=(diff + (diff & 0x80)*0x01FE) << 1;

现在您的diff16已签名2*diff

正如理查德·J·罗斯三世所指出的那样,你可以避免使用条件分支进行乘法(如果你的平台价格昂贵):

int16 diff16 = (diff + ((diff & 0x80)?0xFF00:0))<<1;

如果您担心某些事情会停留在范围内(“未定义的行为”),您可以

int16 diff16 = diff;
diff16 = (diff16 | ((diff16 & 0x80)?0x7F00:0))<<1;

这不会产生超出范围的数字。

最干净的解决方案,似乎是“投射和转移”:

diff16 = (signed char)diff; // recognizes and preserves the sign of diff
diff16 = (short int)((unsigned short)diff16)<<1; // left shift, preserving sign

这会产生预期的结果,因为编译器会在第一行自动处理符号位(因此不需要掩码);在第二行中,它对unsigned int进行左移(根据标准很好地定义了溢出);最终投回short int确保该数字被正确解释为否定。我相信在这种形式下,构造永远不会“未定义”。

答案 1 :(得分:0)

我所有的引用都来自C标准,第6.3.1.3节。当值在签名类型的范围内时,未签名的签名是明确定义的:

  

1当具有整数类型的值转换为另一个整数类型时   除了_Bool之外,如果值可以用新类型表示,那么   没有改变。

签名为无符号的定义很明确:

  

2否则,如果新类型是无符号的,则转换为   重复加或减一个以上的最大值   可以用新类型表示,直到值在范围内   新类型。

无符号签名,当值超出范围时定义不太明确:

  

3否则,新类型已签名且值不可   代表其中;结果是实现定义的还是   实现定义的信号被提出。

不幸的是,您的问题在于第3点.C不保证任何隐式机制来转换超出范围的值,因此您需要明确提供一个。第一步是决定你打算使用哪种表示:一个补码,两个补码或符号和幅度

您使用的表示法将影响您使用的翻译算法。在下面的例子中,我将使用二进制补码:如果符号位为1且值位全为0,则这对应于您的最低值。你的最低值是你必须做出的另一个选择:在两个补码的情况下,使用INT16_MIN( - 32768)或INT8_MIN( - 128)中的任何一个都是有意义的。在另外两个的情况下,由于存在负零,使用INT16_MIN - 1INT8_MIN - 1是有意义的,这可能被翻译为与常规零无法区分。在此示例中,我将使用INT8_MIN,因为(uint8_t) -1应该将-1转换为-1 int16_t

将符号位与值位分开。 value应该是绝对值,除非在sign为1且value为0时为2的补码最小值,当然,符号位可以是在哪里 - 你喜欢它,虽然它常常在最左边休息。因此,右移7个位置获得传统的“符号”位:

uint8_t sign =  input >> 7;
uint8_t value = input & (UINT8_MAX >> 1);
int16_t result;

如果符号位为1,我们将其称为负数并添加到INT8_MIN以构造符号,这样我们就不会陷入与我们开始时相同的难题,或者更糟:未定义的行为(这是其中一个答案的命运)。

if (sign == 1) {
    result = INT8_MIN + value;
}
else {
    result = value;
}

这可以缩短为:

int16_t result = (input >> 7) ? INT8_MIN + (input & (UINT8_MAX >> 1)) : input;

...或者,更好的是:

int16_t result = input <= INT8_MAX ? input
                                   : INT8_MIN + (int8_t)(input % (uint8_t) INT8_MIN);

符号测试现在涉及检查它是否在正范围内。如果是,则值保持不变。否则,我们使用加法和模来产生正确的负值。这与上面的C标准语言相当一致。它适用于两个补码,因为int16_tint8_t保证在内部使用二进制补码表示。但是,像int这样的类型不需要在内部使用二进制补码表示。例如,当将unsigned int转换为int时,需要进行另一次检查,以便我们将小于或等于INT_MAX的值视为正值,并将值大于或等于(unsigned int) INT_MIN为负数。任何其他值都需要作为错误处理;在这种情况下,我将它们视为零。

/* Generate some random input */
srand(time(NULL));
unsigned int input = rand();
for (unsigned int x = UINT_MAX / ((unsigned int) RAND_MAX + 1); x > 1; x--) {
    input *= (unsigned int) RAND_MAX + 1;
    input += rand();
}


int result = /* Handle positives: */ input <= INT_MAX ? input
           : /* Handle negatives: */ input >= (unsigned int) INT_MIN ? INT_MIN + (int)(input % (unsigned int) INT_MIN)
           : /* Handle errors: */ 0;

答案 2 :(得分:0)

如果偏移量在2的补码表示中,那么

转换此

uint8_t argument = 0xFF; //-1
int16_t difference = argument << 1;
*ip += difference;

进入这个:

uint8_t argument = 0xFF; //-1
int8_t signed_argument;

signed_argument = argument; // this relies on implementation-defined
                            // conversion of unsigned to signed, usually it's
                            // just a bit-wise copy on 2's complement systems
// OR
// memcpy(&signed_argument, &argument, sizeof argument);

*ip += signed_argument + signed_argument;