大家好,我是怒码少年小码。
计算机到底是怎么处理数字的?
数字在计算机中的表示
机器数
一个数在计算机中的二进制表示形式,叫做这个数的机器数。
机器数是带符号的,在计算机用一个数的最高位存放符号,正数为0,负数为1。比如,十进制中的数+3,计算机字长为8位,转换成二进制就是00000011。如果是-3,就是10000011。这里的 00000011 和 10000011 都是机器数。
真值
将带符号位的机器数对应的真正数值称为机器数的真值。
例如上述-3的机器码10000011的真值是-3而不是131。(10000011换成十进制是131)
计算机对机器数的表示进一步细化:原码, 反码, 补码。
原码
第一位表示符号,其余位表示值。例如如果是八位二进制:
- [+1] 原 = 0000 0001
- [-1 ] 原 = 1000 0001
八位二进制有一位用于表示符号,所以原码的表示范围是[-127,127]。
反码
- 正数的反码是其本身
- 的反码是在其原码的基础上,符号位不变,其余各位取反。
- [+1] = [00000001]原 = [00000001]反
- [-1] = [10000001]原 = [11111110]反
补码
- 正数的补码就是其本身
- 负数的补码是在其原码的基础上你,符号位不变,其余各位取反,最后+1(也就是在反码的基础上+1)
- [+1] = [00000001]原 = [00000001]反 = [00000001]补
- [-1] = [10000001]原 = [11111110]反 = [11111111]补
看到这里你应该知道一个数在计算机中有三种编码方式。
拓展:反码、补码在人脑无法直观的看出来它的数值,那为啥还要有它们?
答:人脑可以知道第一位是符号位,在计算的时候我们会根据符号位选择对真值区域的加减。但是计算机要辨别"符号位"就必须获得全部的位的数据才可以,显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法。例如:根据运算法则减去一个正数等于加上一个负数。
1+(-1)=[00000001]反 + [11111110]反 = [11111111]反=[10000000]原 = -0
"0"的表示有点奇怪,+0和-0是一样的,而且0带符号是没有任何意义,而且要浪费[0000 0000]原和[10000000]原两个编码来表示0。于是补码的出现,解决了0的符号以及两个编码的问题:
1 + (-1) =[0000 0001]原 + [10000001]原= [00000001]补 + [1111 1111]补= [0000 0000]补=[0000 0000]原
[10000000]表示-128,所以补码的范围是[-128, 127]。(八进制中)
位运算规则
位运算主要有:与、或、异或、取反、左移和右移,其中左移和右移统称位移位运算,移位运算又分为算术移位和逻辑移位。
与
与运算的符号是&
运算规则:对于每个二进制位,当两个数对应的位都为1时,结果才为1,否则结果为0。
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
或
或运算的符号是|
运算规则:对于每个二进制位,当两个数对应的位都为0时,结果才为0,否则结果为1
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
异或
异或运算的符号是⊕(代码表示是^)
运算规则:当两个数对应的位相同时,结果为0,否则结果为1
0 ⊕ 0 = 0
0 ⊕ 1 = 1
1 ⊕ 0 = 1
1 ⊕ 1 = 0
取反
取反运算的符号是~
运算规则:对一个数的每个二进制位进行取反操作,0变成1,1变成0
~0 = 1
~1 = 0
移位运算
移位运算按照移位方法分类可以分成左移和右移,按照是否带符号分类可以分成算术移位和逻辑移位
- 原始: 0000 0110 ===> 6
- 右移一次: 0000 0011 ===> 3 相当于除以2
- 左移一次: 0000 1100 ===> 12 相当于乘以2
左移运算的符号是<< ,左移运算时,将全部二进制位向左移动若干位,高位丢弃,低位补0。对于左移运算,算术移位和逻辑移位是相同的。
右移运算的符号是>>。右移运算时,将全部二进制位向右移动若干位,低位丢弃,高位的补位由算术移位或逻辑移位决定:
- 算术右移时,高位补最高位;
- 逻辑右移时,高位补0
- 示例1:29的二进制表示是0001 1101。 29左移2位的结果是116,对应的二进制是01110100;29左移3位的结果是-24,对应的二进制是11101000
- 示例2:-50的二进制表示是11001110(补码)。-50算术右移2位的结果是-13,对应的二进制表示是11110011;-50逻辑右移2位的结果是51,对应的二进制表示是00110011
拓展:计算机内部的右移运算采取的是哪一种?
答:
- 对于 C/C++ 而言,对于有符号类型,右移运算为算术右移;对于无符 号类型,右移运算为逻辑右移。
- 对于 Java 而言,不存在无符号类型,所有的表示整数的类型都是有符号类型,因此需要区分算术右移和逻辑右移。在Java中,算术右移的符号是>>,逻辑右移的符号是 >>>。
位运算常用技巧
位运算代码套路
获取
将 1 左移 i 位,得到形如 00010000 的值,接着堆这个值与num执行”位与“操作,从而将i位之外的所有位清零,最后检查该结果是否为零。不为零说明i位为1,否则i位为0。代码如下:
boolean getBit(int num,int i){
return ((num&(1<<i))!=0);
}
设置
setBit先将1左移i位,得到形如00010000的值,接着堆这个值和num执行”位或“操作,这样只会改变i 位的数据。这样除i位外的位均为零,故不会影响num的其余位。代码如下:
int setBit(int num,int i){
return num | (1 << i);
}
清零
与setBit相反,将1左移i位获得形如00010000的值,对这个值取反进而得到类似11101111的 值,接着对该值和num执行”位与“,故而不会影响到num的其余位,只会清零i位。
int clearBit(int num,int i){
int mask = ~(1 << i);
return num &mask;
}
更新
这个方法是将setBit和clearBit合二为一,首先用诸如11101111的值将num的第i位清零。接着将待写入 值v左移i位,得到一个i位为v但其余位都为0的数。最后对之前的结果执行”位或“操作,v为1这num的i 位更新为1,否则为0:
int updateBit(int num,int i,int v){
int mask=~(1<<i);
return (num&mask)|(v<<i);
}
END
说实话,更文到这里其实有点累了,学计算机也有点累了。网上负面消息一大堆,近几年就业形势不好,自己在学的东西也得不到正向反馈,有时候真的觉得自己挺废物的😔。或许看到这的小伙伴也和我有一样的想法,这里我想分享一段翁凯老师的话:
计算机的所有东西都是人做出来的,别人能想得出来的,你也一定能想得出来。在计算机里头没有任何黑魔法,所有的东西只不过是我现在不知道而已,总有一天我会把所有的细节、所有的内部的东西都搞明白了。
这贼船都上了,咱就好好走下去吧😁