在C语言中将16位符号扩展为32位

7

我需要对一个16位整数进行符号扩展,但出现了问题,似乎无法正常工作。有人能告诉我代码中的错误在哪里吗?我已经花了几个小时在这上面。

int signExtension(int instr) {
    int value = (0x0000FFFF & instr);
    int mask = 0x00008000;
    int sign = (mask & instr) >> 15;
    if (sign == 1)
        value += 0xFFFF0000;
    return value;
}

指令(instr)为32位,其中包含一个16位的数字。

4
首先,你实际上从未使用“sign”变量。 - Chris Eberle
等一下,这台机器上的“int”是16位的吗?还是“instr”应该是“short”?此外,提供一些输入和(不正确的)输出示例可能会有所帮助。 - andrewdski
我刚刚尝试了你的代码。它对我有效。它在哪方面没有正常工作? - JeremyP
5个回答

19

为什么这个有问题:

int16_t s = -890;
int32_t i = s;  //this does the job, doesn't it?

3
考虑到问题明确要求从16位扩展到32位,建议使用int16_tint32_t代替shortint - JeremyP
@Nawaz:你的解决方案不具备可移植性。类型大小可能会因编译器而异。 - qbert220
4
你的意思是 int16_t 不一定是指 16 位有符号整数吗? - Nawaz
3
@Nawaz: 我认为他在评论你的原回答。 - JeremyP
1
@Nawaz:但是对帖子的编辑不会推送给已经加载页面的人,因此阅读您上面答案的时间就是人们对您答案评论过时的时间。 - Steve Jessop
显示剩余2条评论

12

使用内置类型有什么问题吗?

int32_t signExtension(int32_t instr) {
    int16_t value = (int16_t)instr;
    return (int32_t)value;
}

更好的做法是使用下面的代码(如果传递的是 int32_t 类型可能会生成警告):

int32_t signExtension(int16_t instr) {
    return (int32_t)instr;
}

如果需要的话,可以用((int32_t)(int16_t)value)替换signExtension(value)

显然需要包含<stdint.h>头文件以使用int16_tint32_t数据类型。


1
请看我对Nawaz答案的评论。 - JeremyP
你也可以使用宏代替函数#define signExtension(x)((int32_t)((int16_t)x)),因为这比通过函数调用更快 :) - DipSwitch
4
@DipSwitch: 我的编译器比你的更好,它可以内联这样的函数调用,所以宏不会更快 :-) - Steve Jessop
1
@CAFxX:我希望“或者更好”的提示会引起一些多疑的编译器警告,因为如果使用大于16位的计算值调用它,则会丢弃信息,如果结果值超出范围。也许传递的值不应该超出范围,也许丢弃顶部位是有意的,但编译器不知道您是否知道这一点。因此,一些编译器/选项将强制调用者使用显式转换,这可能是可取的,也可能不是,具体取决于为什么在32位类型中使用了这个16位指令。 - Steve Jessop
@Steve 你说得对,我只是不确定 OP 是在 int32_t 还是 int16_t 中有一个 16 位值。 - CAFxX
显示剩余3条评论

8

我在找其他东西时偶然发现这个,可能有点晚了,但也许对其他人有用。我认为所有的C程序员都应该从汇编语言开始学习编程。

总之,符号扩展比那些提案要简单得多。只需确保使用带符号的变量,然后使用两次位移即可。

long value;   // 32 bit storage
value=0xffff; // 16 bit 2's complement -1, value is now 0x0000ffff
value = ((value << 16) >> 16); // value is now 0xffffffff

如果变量是有符号的,那么C编译器将 >> 翻译为算术右移,从而保留符号。这种行为与平台无关。

因此,假设 value 的初始值为 0x1ff,则 << 16 会使 instr 左移 (SL) 值,因此 instr 现在为 0xff80,然后 >> 16 将 ASR 值,所以 instr 现在为 0xffff。

如果您真的想玩宏,那么可以尝试类似以下的语法(该语法适用于GCC,尚未在MSVC中尝试)。

#include <stdio.h>

#define INT8 signed char
#define INT16 signed short
#define INT32 signed long
#define INT64 signed long long
#define SIGN_EXTEND(to, from, value) ((INT##to)((INT##to)(((INT##to)value) << (to - from)) >> (to - from)))

int main(int argc, char *argv[], char *envp[])
{
    INT16 value16 = 0x10f;
    INT32 value32 = 0x10f;
    printf("SIGN_EXTEND(8,3,6)=%i\n", SIGN_EXTEND(8,3,6));
    printf("LITERAL         SIGN_EXTEND(16,9,0x10f)=%i\n", SIGN_EXTEND(16,9,0x10f));
    printf("16 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=%i\n", SIGN_EXTEND(16,9,value16));
    printf("32 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=%i\n", SIGN_EXTEND(16,9,value32));

    return 0;
}

这会产生以下输出:
SIGN_EXTEND(8,3,6)=-2
LITERAL         SIGN_EXTEND(16,9,0x10f)=-241
16 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=-241
32 BIT VARIABLE SIGN_EXTEND(16,9,0x10f)=-241

3
这个问题在于对于有符号整数来说,向符号位进行移位是未定义行为。详情请参考:http://blog.regehr.org/archives/738 - thenickdude
这是最优秀的答案 - 适用于任何位数 - 谢谢,正是我所需要的 :-) - Holger Bille
“long”不能保证为32位(它只保证至少与“int”一样大,而“int”本身不需要为32位)-因此,声称这里的方法“平台无关”是可笑的。另请参见@thenickdude有关UB的评论。 - CAFxX

6

尝试:

int signExtension(int instr) {
    int value = (0x0000FFFF & instr);
    int mask = 0x00008000;
    if (mask & instr) {
        value += 0xFFFF0000;
    }
    return value;
}

1
@Sorin:您已更新问题,使其行为类似于我的答案。两者都可以将16位值正确地符号扩展为32位值。 - qbert220
1
Nawaz的修订答案比我的更好! - qbert220
4
不要使用 + 进行位操作,正确的运算符是 | - Ben Voigt
2
这个答案调用了 UB(未定义行为),假设 int 只有 32 位,因为有符号的溢出。如果你要实现自己的符号扩展,你需要使用无符号类型,但最好还是让编译器来完成。 - R.. GitHub STOP HELPING ICE

5

有人指出使用强制类型转换和左移操作后跟算术右移操作。还有一种不需要分支的方法:

(0xffff & n ^ 0x8000) - 0x8000

如果高16位已经都是0:
(n ^ 0x8000) - 0x8000

• 社区维基,因为它是来自于“The Aggregate Magic Algorithms, Sign Extension”的想法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接