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

参考:

  1. C语言强制类型转换规则实例详解
  2. C 语言整数强制类型转换

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

参考:

  1. 什么是整数溢出?溢出方式(回绕,截断),表现,危害
  2. 详解负数取模运算
posted @ 2022-11-08 22:57  sureZ_ok  阅读(383)  评论(0编辑  收藏  举报