【牛筋】计算机的世界跟男铜一样只有0和1是怎么运算得?

牛筋,指牛老师笔经面筋系列,这是系列第一篇。
如题,本篇为计算机二进制基础篇。阅读本篇你可以学到以下内容:
整数二进制,负数补码,位运算,半加器与全加器,不使用加号的加法运算,各种位运算的风骚技巧。
还有如何判断南通是1还是0(大雾)。

整数二进制

二进制都懂吧,只有1和0,逢二进一。
(15).toString(2)
// "1111"
整数15可以转为二进制为0b1111,15 == 23+22+21+20
注意这里有个笔试考点,15要用()括起来,不然JavaScript会解析出错,因为他以为15后面的.是小数的小数点。
那么负数怎么表示的呢?试一下
(-15).toString(2)
// "-1111"
,我tm,你是来捣乱的吧。你在前面加个-就完事了是吧?
这肯定是JavaScript偷懒了,不行,我换个python试试。
print(bin(-15));
// -0b1111
好吧,这应该是语言特性,为了便于理解有意为之。
于是我搜了一个编码转化工具;输入数字-15,得到下面得结果。

注意,这是默认是整数16位便于展示的,要是64位展示就不礼貌了。
由于是16位的,那么左侧第一位就是符号位。符号位为1就是负数,为0就是整数。
看这个原码是最方便人类理解的,比如15的原码是00001111,符号位0,后面4个1.-15的原码就是1000 1111。
那么反码和补码是啥玩意呢:如果是正数,他的反码和补码跟原码保持一致,因为正数没那么多花里胡哨的皮肤,实际上也不需要。
如果是负数,那么就将原码取反(符号位除外)得到反码,反码加+1得到补码。

不多说了,直接上结论:
结论1:计算机中负数是用补码表示的(11110001 才是-15,)原码和反码其实没卵用,(上学时老是搞不清);
结论2:负数等于正数取反再加1。
比如15是00001111,取反~15为11110000,再加1为11110001,一定等于-15补码(11110001)。
结论2很重要。记住准没错。

位运算


偷懒借个图,如上。
  1. 按位与&:,例如判断x的奇偶 :x&1=== true 为奇数,例2: x&(-x)可以得到x中最右边的1位的位置,(某些算法题中会用到),例3用来指定位设置为0,比如x &0b1111可以将x除后4位以外的位置0;x&3结果等同于x%4
  2. 按位或|:用来置1,例子忘了。
  3. 异或^: (以前老把这个看出指数冥运算。。。);实际上是运算规则是相同为0 ,不同为1,异或自身为0 :x ^ x =0这个特性用处很多。比如牛客编程题NC231,只出现一次的数。一堆无序数字中,只有一个数出现1一次,其他的数均出现2次,找出这个数。只需要所有数字连续异或操作就行。
  4. 按位反~:没啥好说的。求x的负数  -x即 ~x+1,同上结论2.
牛客编程题NC231一行解决如下,时间复杂度O(n):
return array.reduce((a,b) => a^b,0)

如何做简单的运算

位运算是计算机中速度最快的运算。这跟底层门电路有关。
门电路偷个图如下:

拿这个与门举例,跟按位与操作的规则一致,可以理解位只有左边2个输入有电,那么右侧输出就有电。
要实现1bit的加法,即1+1,光要与门可不行,因为1+1可能产生进位,2个同为1才有进位,对应与运算;抛开进位不看,其最后位的运算结果正好对应异或操作(0+1为1,1+0为1,0+0为0,1+1=10,最后一个是0),因此还得需要一个异或门。
由此可以得到一个半加器。

细想一下为啥叫半加器,因为他直接做1bit的运算,要是11+01,这左一位1+0还得加上来由右边的进位的1,
顾名思义,2个半加器串起来等于一个全加器。16个全加器串起来就能进行16位整型加法运算了。
但实际上这种串行的方式算的太慢了,还有更先进的超前进位加法器,就不多展开了。

最后做一题

牛客题库NC258 不用加减乘除做加法
很显然,计算机里的加法本来就是异或和与运算的结合,想办法把加法转为这两种运算就行。
直接拿2数出来看,设x为0b1111,y为0b0101;
x^y == 0b1010,这个结果是两数相加不考虑进位的效果0b1010
x&y == 0b0101,这个结果是两数相加种需要进位的位置,即右侧其第一个第三位需要向左进一位。
向左进一位还不简单,当然是移位运算符来帮忙,0b0101 <<1 == 0b1010;
于是x+y 可以转化为x^y + (x&y)<<1,即0b1010 + 0b1010,这不是还有+号吗?
别急,虽然有加号,但是可以重复上速步骤。
0b1010为新的x,新的y也是0b1010,0b1010+0b1010 == 0b1010^0b1010 +(0b1010&0b1010)== 0b0000+0b10100
注意啦,出现了0b0000,这是啥,这是个0啊,x+y 转成了0+z,这个z就是要求的结果了。


class Solution {
public:
    int Add(int num1, int num2) {
                      
        // 当一个为0终止,返回另一个就行
        while(num2 != 0) {              
            // 将每轮的无进位和与进位值做异或求和
            int temp = num1 ^ num2;      
            // 进位值是用与运算产生的
            num2 = (num1 & num2) << 1;    
            // 更新sum为新的和
            num1 = temp;                
        }
        return num1;
    }
};

最后

你问减法怎么做?记得-x = ~x+1 吗?
#面经笔经#
全部评论
雪碧哥写的好棒
点赞 回复
分享
发布于 2022-08-22 16:09 安徽

相关推荐

3 1 评论
分享
牛客网
牛客企业服务