C语言整数操作
文中的用例都在OnlineGDB测试过。
OnlineGDB: https://onlinegdb.com/online_c_compiler
1 整数的强转
1. 等长度数据转换:
如:等字长的无符号数和有符号数强制类型转换,不改变数据内容,但是改变了数据的解释形式。
# 有符号转无符号
如:
int8_t 强转为uint8_t
int16_t 强转为uint16_t
int32_t 强转为uint32_t
# 无符号转有符号
如:
uint8_t 强转为int8_t
uint16_t 强转为int16_t
uint32_t 强转为int32_t
举例:
int32_t val = -121;
uint32_t new = (uint32_t)val;
printf("val = 0x%x, %d, new = 0x%x, %d\n", val, val, new, new);
结果为:
val = 0xffffff87, -121, new = 0xffffff87, 4294967175
负数在计算机是以补码形式存在的,-121的补码为0xFFFFFF87,如果解释为无符号数即为4294967175,
也就说明等长度数据转换不改变数据内容,但是改变了数据的解释形式。
2. 长的整数转为短的整数:
会发生截断,保留低位,舍掉高位。
3. 短的整数转为长的整数:
有符号数:高位填充符号位,即整数填充0,负数填充1
无符号数:高位填充0
参考:
2 整数的移位
整数移位分为逻辑移位(逻辑左移、逻辑右移)与算术移位(算术左移、算术右移)。C语言中,是通过被操作数的类型来确定逻辑移位与算术移位的:
若被操作数定义为无符号数,>> 与 << 表示逻辑移位;
若被操作数定义为有符号数,>> 与 << 表示算术移位(这种情况C语言标准并未定义,是编译器行为,常见为算术移位)。
-
算术左移与逻辑左移一样,都是右边补0
-
逻辑右移将二进制数整体右移,左边补0
-
算术右移将二进制数整体右移,左边补符号位(如果是正数则左边补0,负数则左边补1)
1 左移n位等于原数值乘以2的n次方
算术左移与逻辑左移一样,都是右边补0
逻辑左移:要避免溢出,如果左移n位后被舍弃的高位含1则会发生溢出
算术左移:正数要避免反转,如果左移n位后,符号位由0到1,则发生了符号反转;负数要避免溢出,如果左移后被舍弃的高位含1则会发生溢出。
2 逻辑右移n位等于原数值除以2的n次方(注意:操作数为负数时算术右移并不一定等于原数值除以2的n次方)
算术右移:注意操作数为负数时算术右移并不一定等于原数值除以2的n次方, -5/4 并不等于 -5 >> 2,前者的结果是0xFFFF(即-1) 而后者为 0xFFFE(即-2),
而-1比较特殊,右移n位还是-1。(负数如果右移n位,被舍弃的低位含1,结果可能不准确,这是因为负数在计算机是以补码形式存在的)
参考:https://blog.csdn.net/lit_sang/article/details/125970729 《算术左移,算术右移;逻辑左移,逻辑右移》
3 整数的溢出与回绕
只有有符号数会发生溢出,无符号数发生回绕。
溢出:(对于signed整型的溢出,C的规范定义是“undefined behavior”,取决于编译器的实现,多为溢出)
# 举例
int16_t aa = 32767;
int16_t bb = aa + 1;
printf("aa = %x, %d, bb = %x, %d\n", aa, aa, bb, bb);
结果为:
aa = 7fff, 32767, bb = ffff8000, -32768
32767 + 1后变成了-32768,这是因为int16_t的表达范围为[-32768, 32767], 而32767 + 1 = 0xffff8000 正是-32768的补码。
回绕:(对于unsigned整型溢出,C的规范是有定义的——“溢出后的数会以2^(8*sizeof(type))作模运算)
# 举例
uint16_t aa = 65535;
uint16_t bb = aa + 1;
printf("aa = %x, %d, bb = %x, %d\n", aa, aa, bb, bb);
结果为:
aa = ffff, 65535, bb = 0, 0
(因为65535 + 1是65536,与2^16求模后就是0)
4 整数的求余
整数a对整数b取模的结果为:
r = a - func(a/b) * b
取模的结果依赖于func的定义:
主流的func定义有两种:截断(truncate)和 向下取整(floor)
- 当a和b同为正整数或负整数时(即商a/b为正数时),truncate与floor表现相同,如:truncate(3.3) = 3,floor(3.3) = 3
- 当a和b为一正一负(即商a/b为负数时),truncate与floor表现不同,例如truncate(-3.3) = -3,floor(-3.3) = -4。
所以选择不同的策略导致不同的结果:
c与java使用截断(truncate),所以在计算-6 % 5 的时候是这么算的:
-6 - (5*trunc(-6/5))= -6 - (5 * -1) = -1
python使用floor,所以在计算-6 % 5 的时候是这么算的:
-6 - (5*floor(-6/5))= -6 - (5 * -2) = 4
参考: