揭开二进制与位移运算的神秘面纱

摘要

  在计算机的世界中,根据其电气特性,其底层存储和操作的是以0101表示的二进制数。所以要理解一些计算机的操作原理,就必须了解相关二进制的计算规则。

移位运算符

  移位运算符是将数据看成二进制数,对其进行向左或向右移动若干位的运算。位移位运算符分为左移和右移两种,均为双目运算符。第一运算对象是移位对象,第二个运算对象是所移的二进制位数。

  移位时,移出的位数全部丢弃,移出的空位补入的数与左移还是右移有关。如果是左移,则规定补入的数全部是0;如果是右移,还与被移位的数据是否带符号有关。若是不带符号数,则补入的数全部为0;若是带符号数,则补入的数全部等于原数的最左端位上的原数(即原符号位)。这里就扯出 一个符号和无符号的概念,这里预说明下,若是带符号数,符号位为1则表示负数,为0则表示正数。

方向 规则
左移<< 低位补0
右移>> unsigned(高位补0)、signed(与符号位相同)

负数的右移

  这里特别提一下负数的右移操作。相对于其他移位操作而言,负数的右移操作相对较复杂,下面是-100右移四位的其操作步骤:

注:右移的补位数与符号位相同,符号位为0则补0,为1则补1

  1. 保持符号位不变
  2. 其余位向右移动4位
  3. 移动过程中高位补1
  4. 保持符号位不变,取反加一得移位后的原码
1
2
3
4
5
6
-100
原码:1110 0100
反码:1001 1011
补码:1001 1100
右移四位:1111 1001
右移后的原码:1000 0111

顺便提一下signed int 型1的左移问题

1
2
3
4
5
6
7
1的补码为 00000000 00000000 00000000 00000001  //最高位为符号位
当1不断左移时,直到移动到符号位,补码如下:
10000000 00000000 00000000 00000000
此时符号为为1表示负数,其余位为0
保持符号位不变取反加1后仍为
10000000 00000000 00000000 00000000
表示整数-0

六种位运算符

运算符 含义 语法 效果
<< 左移 a<<x 整数a按二进制位左移x位
>> 右移 a>>x 整数a按二进制位右移x位
& 位与 a&b 整数a和b按二进制位对齐,按位进行与运算(除了11得1,其他均为0)
` ` 位或 `a
^ 位异或 a^b 整数a和b按二进制位对齐,按位进行异或运算(相同为0,不同为1)
~ 位取反 ~a 整数a的二进制的每一位进行0变1、1变0的操作

signed&unsigned

  拿charunsigned char类型举例:Q1:若将char类型强转为unsigned char类型,又会发什么什么样的变化?Q2:为什么char类型移位后与unsigned char类型移位后转化为int的结果不一样?

  首先在内存中两者没有什么区别,都是占了8个比特——1个字节,唯一的区别是char的首位是符号位,而unsigned char的首位是数字位。所以这就导致了他俩所能表示的数的范围区间不同。

类型 范围 十六进制0x80~0xFF
char -128~127 表示-128~-1
unsigned char 0~255 表示+128~+255

  这已经差不多能够回答Q1了;倘若将char类型转换为unsigned char类型,其存储在内存当中的底层数据是不变的,而变化的是其高位由原来的符号位变成了数字位——即其意义发生了改变。换句话说,指针变了,数据的2进制表示没变,变的是根据指针类型来解释这段数据的方式。

原码、反码、补码

  在回答第二个问题前,有必要简单说一下源码、反码、补码的相关概念。在计算机表示的二进制数以及相关的计算都与他们有着不可分割的关系。

  在计算机系统中,数值一律用补码来存储。 因为使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。更加符合计算机计算的逻辑。

  • 源码:正整数转化为二进制数

  • 反码:原码按位取反

  • 补码:反码+1

计算方法

数值的补码表示也分两种情况:

  1. 正数的补码:与原码相同。

    例如:+9的原码是00001001,所以补码是00001001。

  2. 负数的补码:符号位为1,其余位为该数绝对值的原码按位取反;然后整个数加1。

    例如,-7的补码:因为是负数,则符号位为“1”,整个为10000111;其余7位为-7的绝对值+7的原码0000111按位取反为1111000;再加1,所以-7的补码是11111001。

    关于-16补码的计算方式:

    原码:1001 0000 (最高位为1,后n-1位是其绝对值16的二进制表示)

    反码:1110 1111 (符号位不变,后n-1位取反)

    补码:1111 0000 (反码+1)

  • 已知一个数的补码,求原码的操作分两种情况:

    • 如果补码的符号位为“0”,表示是一个正数,所以补码就是该数的原码。

    • 如果补码的符号位为“1”,表示是一个负数,求原码的操作可以是:符号位为1,其余各位取反,然后再整个数加1。

      例如,已知一个补码为11111001,则原码是10000111(-7):因为符号位为“1”,表示是一个负数,所以该位不变,仍为“1”;其余7位1111001取反后为0000110;再加1,所以是10000111。

短字节转长字节

  char类型占1个字节,而int类型占4个字节,若将短字节类型转为长字节类型,则需要在前面三个字节补符号位;若将长字节类型转为短字节类型,则需要对长字节类型进行截取,因此会出现精度,或者数据的丢失。关于补充符号位的规则如下表所示:

数据类型 补符号位
signed char(首位为1,是负数) 补1,0xFFFFFF
signed char(首位为0,是正数) 补0,0x000000
unsigned char(无所谓正负,则认为是正) 补0,0x000000

  至次,比可以回答Q2了,由于计算机里数值都是以补码形式存在,且buffer的高位为1,所以补0xFFFFFF,转化为int的Buffer 则为0xFFFFFFF0(将-16以补码形式的表现),然后在做右移操作;而作为unsigned char类型,则无所谓正负,补0x000000,最终Buffer 则为0x000000F0还是0xF0,所以不会有影响。

二进制转换

主要想记录下带有小数点的十进制如何转换为二进制

心法口诀:整数部分除基取余,小数部分乘基取整

----\(˙<>˙)/----赞赏一下吧~