【问题标题】:How to sign extend a 9-bit value when converting from an 8-bit value?从 8 位值转换时如何对 9 位值进行符号扩展?
【发布时间】:2013-04-03 02:52:36
【问题描述】:

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

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

我当前的代码如下所示:

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

但是,我不相信差异会被检测为小于 0。我对签名到未签名的工作方式感到生疏。除此之外,我不确定在 case 参数是 -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。
  • 我认为您可以将argument 的类型设置为已签名而不是未签名。
  • 你不能投射或分配给更大的类型吗...你不能免费获得符号扩展。

标签: c int bit-manipulation signed stdint


【解决方案1】:

澄清一下:您担心左移一个负 8 位数字会使它看起来像一个正 9 位数字吗?只需在左移前用初始数字的符号位填充前 9 位即可:

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

现在您的diff16 已签名2*diff

正如 Richard J Ross III 所指出的,您可以使用条件分支来避免乘法(如果在您的平台上代价高昂的话):

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

如果您担心事情会停留在范围内等(“未定义行为”),您可以这样做

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

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

不过,最干净的解决方案似乎是“cast and shift”:

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 确保该数字被正确解释为负数。我相信这种形式的构造永远不会“未定义”。

【讨论】:

  • 你在倍增,考虑到 OP 的情况,这可能是一个糟糕的举动。
  • 我试图避免条件分支。同样的事情可以通过if() then add 来实现
  • 或者,一个三元表达式:)
  • C 标准的第 6.5p5 节将此答案归类为“未定义的行为”:如果在计算表达式期间出现异常情况(即,如果结果不是数学定义或不在其类型的可表示值范围内),则行为未定义。
  • @modifiablelvalue - 你是说当左移溢出时,行为没有定义?我以为是......你失去了顶端的位并用零填充底部。如果我错了,我真的很想参考。
【解决方案2】:

我所有的引用都来自 C 标准,第 6.3.1.3 节。当值在有符号类型的范围内时,无符号到有符号是很好的定义:

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

有符号到无符号定义明确:

2 否则,如果新类型是无符号的,则将值转换为 反复加或减一大于最大值 可以用新类型表示,直到值在 新类型。

无符号到有符号,当值超出范围时定义不太好:

3 否则,新类型有符号,值不能 代表其中;结果是实现定义的或 引发了实现定义的信号。

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

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

将符号位与值位分开。 value 应该是绝对值,除非在二进制补码最小值的情况下,sign 为 1,value 为 0。当然,符号位可以是任何你喜欢的位置是,尽管它通常位于最左侧。因此,右移 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;

【讨论】:

  • 嗯...那么,有没有使用 1s 补码的 C 编译器/处理器?快速搜索没有发现任何内容,但我可以看到为什么该标准因此而“开放”
  • @Earlz 无关紧要。问题不在于编译器/处理器使用的表示,而是使用表示将无符号值转换为有符号值。
【解决方案3】:

如果偏移量是 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;

【讨论】:

    猜你喜欢
    • 2016-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2011-07-24
    • 2021-07-21
    • 2021-03-24
    相关资源
    最近更新 更多