我所有的引用都来自 C 标准,第 6.3.1.3 节。当值在有符号类型的范围内时,无符号到有符号是很好的定义:
1 当一个整数类型的值被转换为另一个整数类型时
除 _Bool 以外,如果该值可以用新类型表示,则
没有改变。
有符号到无符号定义明确:
2 否则,如果新类型是无符号的,则将值转换为
反复加或减一大于最大值
可以用新类型表示,直到值在
新类型。
无符号到有符号,当值超出范围时定义不太好:
3 否则,新类型有符号,值不能
代表其中;结果是实现定义的或
引发了实现定义的信号。
不幸的是,您的问题在于第 3 点的领域。C 不保证任何隐式机制来转换超出范围的值,因此您需要明确提供一个。第一步是决定您打算使用哪种表示形式:1 的补码、2 的补码或符号和幅度
您使用的表示将影响您使用的翻译算法。在下面的示例中,我将使用二进制补码:如果符号位为 1 且值位均为 0,则这对应于您的最低值。您的最低值是您必须做出的另一个选择:在二进制补码的情况下,使用INT16_MIN (-32768) 或INT8_MIN (-128) 是有意义的。在其他两个的情况下,使用INT16_MIN - 1 或INT8_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_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;