问题描述
假设一个人必须将两个有符号数相加(或相减等),如下编码:
short short_sum (int i1,int i2) {
assert (SHRT_MIN <= i1 && i1 <= SHRT_MAX);
assert (SHRT_MIN <= i2 && i2 <= SHRT_MAX);
return (short)(i1 + i2);
}
我需要模拟相同的效果,但分别用有符号字节和(理论上的)5 位有符号整数替换 int 和 short。类似于以下内容:
signed char tiny_sum (signed char c1,signed char c2) {
assert (-16 <= c1 && c1 <= 15);
assert (-16 <= c2 && c2 <= 15);
return (signed char)(((c1 + c2) << (CHAR_BIT - 5)) >> ((CHAR_BIT - 5)));
}
虽然上面的代码已经完成了这个技巧,但它有一些缺陷:右移负数是实现定义的,更糟糕的是,左移这样的数字,或者左移“溢出”的正数是不明确的。所以代码既不可移植也不真正可预测。
这就是我的问题:如何通过快速、可移植且定义明确的位操作来达到预期的效果?
提前致谢。
解决方法
使用 https://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend 做:
#include <stdio.h>
// start with good typedefs...
typedef signed char int5_t;
typedef unsigned char uint5_t;
#define INT5_MAX 15
#define INT5_MIN -16
#define UINT5_MAX 31
int5_t int5_add(int5_t a,int5_t b) {
// converting signed to unsigned is defined
// `% 32` is just the same as `& 0x1f`.
const unsigned c = ((unsigned)a + (unsigned)b) % 32;
// Just copying from the link...
const unsigned m = 1U << (5 - 1);
// Just two operations.
return (c ^ m) - m;
}
void test(int a,int b,int c) {
int d = int5_add(a,b);
printf("%3hhd + %3hhd = %3hhd shouldbe %3hhd - %s\n",a,b,d,c,d == c ? "OK" : "ERROR");
}
int main() {
test(-16,-1,15);
test(-16,-16);
test(-16,1,-15);
test(15,-16);
test(15,15);
test(15,14);
return -1;
}
更糟糕的是,左移这样的数字,或者左移“溢出”的正数是未定义的
标准中的“未定义”,但请参阅您的编译器文档。在 gcc
,我们“知道”integers implementation:
GCC 仅支持二进制补码整数类型,并且所有位模式都是普通值。
[...]
- 对有符号整数(C90 6.3、C99 和 C11 6.5)进行一些按位运算的结果。
按位运算符作用于值的表示,包括符号位和值位,其中符号位被考虑 紧接在最高值位之上。签名的“>>”作用于 带符号扩展的负数。
作为 C 语言的扩展,GCC 不使用 C99 和 C11 中给出的纬度,仅将带符号的“