我所有的引用都来自 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;