我正在简单的虚拟机中实现相对分支功能。
基本上,我给出了一个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
希望这会让它更加清晰。
无论如何,我怎么这么便宜呢?我看到一个类似问题的“解决方案”,但它涉及到分裂。我正在使用缓慢的嵌入式处理器(假设没有有效的乘法和除法方式),所以这是我想避免的一件非常重要的事情。
答案 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 - 1
或INT8_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_t
和int8_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;