向左和向右旋转以使用 C 中的 (signed short) 进行符号扩展

Rotate left and back to the right for sign extension with (signed short) cast in C

以前,我有以下 C 代码,我打算通过它在变量 'sample_unsigned' 的 'signed short' 强制转换后对变量 'sample' 进行符号扩展。

unsigned short sample_unsigned;
signed short sample;

sample = ((signed short) sample_unsigned << 4) >> 4;

在二进制表示中,我希望 'sample' 的最高有效位重复 4 次。例如,如果:

sample_unsigned = 0x0800 (corresponding to "100000000000" in binary)

我理解 'sample' 结果应该是:

sample = 0xF800 (corresponding to "1111100000000000" in binary)

然而,'sample' 总是以与 'sample_unsigned' 相同的方式结束,我不得不按如下方式拆分赋值语句,这很有效。这是为什么?

sample = ((signed short) sample_unsigned << 4);
sample >>= 4;

这是因为(signed short) sample_unsigned由于整数提升而自动转换为int作为操作数。

sample = (signed short)((signed short) sample_unsigned << 4) >> 4;

同样有效。

你的方法行不通。不保证右移会保留标志。即使,它也只适用于 16 位 int。对于 >=32 位 int ,您必须手动将符号复制到高位,否则它只会来回移动相同的数据。一般来说,有符号值的位移很重要——详情请参阅[标准](http://port70.net/~nsz/c/c11/n1570.html#6.5.7。一些星座会调用未定义的行为。最好避免它们,只使用无符号整数。

但是,对于大多数平台,以下内容有效。它不一定更慢(在 16 位 int 的平台上,它可能更快):

uint16_t usample;
int16_t ssample;

ssample = (int16_t)usample;
if ( ssample & 0x800 )
    ssample |= ~0xFFF;

转换为 int16_t 是实现定义的;您的编译器应指定它是如何执行的。对于(几乎?)所有最近的实现,都没有执行额外的操作。只需在生成的代码或您的编译器文档中进行验证。逻辑或依赖于 intX_t 使用标准保证的 2s 补码 - 而不是标准类型。

在 32 位平台上,可能存在符号扩展的固有指令(例如 ARM Cortex-M3/4 SBFX)。或者编译器提供内置函数。根据您的用例和速度要求,可能适合使用它们。

更新:

另一种方法是使用位域结构:

struct {
    int16_t val : 12;    // assuming 12 bit signed value like above
} extender;

extender.val = usample;
ssample = extender.val;

这可能会导致使用我在上面建议的相同汇编器指令。