要说位运算这个话题,我们必须先从一个牛人说起。

这个牛人是莱布尼茨。我们先来看一下百度百科:
戈特弗里德·威廉·莱布尼茨(Gottfried Wilhelm Leibniz,1646年7月1日-1716年11月14日),德国哲学家、数学家,是历史上少见的通才,被誉为十七世纪的亚里士多德。
他本人是一名律师,经常往返于各大城镇,他许多的公式都是在颠簸的马车上完成的,他也自称具有男爵的贵族身份。
莱布尼茨在数学史和哲学史上都占有重要地位。在数学上,他和艾萨克·牛顿先后独立发现了微积分,而且他所使用的微积分的数学符号被更广泛的使用,莱布尼茨所发明的符号被普遍认为更综合,适用范围更加广泛。莱布尼茨还发现并完善了二进制

看到了吗?“发现并完善了二进制”。因此位运算能有今天,还是离不开莱布尼茨的贡献。

那么莱布尼茨的二进制翻译成中文大概就长这样,我们小学就学过10进制与2进制的转换:
2 = "10"; 17 = "10001"; 108 = "110110";

而这一点早期依靠晶体管首次在集成电路上被大规模实现,位之间的“与”“或”“非”运算以2进制为前提构成了计算机的CPU。
今天你看见的Core i9、M1之类的芯片,其实大公司们比的就是谁的与或非算得快、算得多
因此,位运算也是计算机的运算符中速度最快的一种。

我们今天就来讲一讲:位运算有什么用?

很多人在很多平台用很多方式讲过很多种位运算的机制,如果你看过其中的一篇或数篇,大多数情况下它既不会提你的胃口,也不会让你从此爱上计算机科学,甚至可能给你留下阴影。

这一点是正常的。假如计算机有灵魂,那么它看到你的第一个瞬间应该就会想:
“这人咋回事?为什么Ta算加法靠存储就能完成?”——这和你第一回看见CPU内部构造时的看法应该是相同的。

如果你在街上看见一个人(在哪里看见都可以),这个人用位运算解决一切生活问题,那这个人多半是从精神病院跑出来的(如果他连交谈都不离位运算的话)。因此位运算并不是什么强大的理论,它只是计算机世界中形同人类四则运算一样的东西。
同样,假如哪一个人从硬件方面发明了以加减乘除为基础运算的计算机,那这台机子也绝对是计算机里的异类。

以后你有了孩子,也许你可以从0和1开始教他数学——不知道结果会怎么样(建议你试试)。

对于一个正常人来说,对2个整数进行位运算有以下几个步骤——举个例子——计算17 or 47的值:
1. 把两个整数化为2进制——17 = “10001”;47 = “101111”.
2. 计算——“010001” or “101111” = “111111”.(先往下看,不要在意计算过程)
3. 转为10进制——“111111” = 63.
最后,我们便得出:17 or 47 = 63.

位运算最底层的逻辑运算符有3个:and、or和not。一般我们这么使用(x与y均为0或1):

x and y,一般记为x&y,将二进制下的x和y的进行and运算。
其中,x与y之间的and运算遵循原则“有0出0,全1出1”,也就是说,一旦x和y有一个是0,就返回0;只有x=y=1时,才返回1。这个规则恰好返回x*y的积。

x or y,一般记为x|y,将二进制下的x和y的进行or运算。
其中,x与y之间的or运算遵循原则“有1出1,全0出0”,也就是说,一旦x和y有一个是1,就返回1;只有x=y=0时,才返回0。

not x,一般记为!x,将二进制下的x求反。
当x=0时,!x=1,当x=1时,!x=0。

为了帮助理解,我们用生活中的实际应用来解释这些逻辑运算符。

假如一所A学校,他们的招生方式如下:
“我们想要的学生,中考分数大于705,并且语文分数大于130。”

这里的“并且”,其实就是and运算。一个总分710,语文132的考生显然符合条件;但是如果语文只有125,或者总分连700都没有,那他只能和A校无缘了。这实际上应用了and运算的性质:假如考生总分未上705,那么KO;如果他上了705的话,继续考虑语文。如果语文也让人满意,那么他就可以参加面试了!
(这是一所什么学校啊?!)

再假如一家B公司,他们希望找到这样的人:
“我们的程序员,应该是985,或者是个博士。”(不看专业的吗?)

“或者”其实就是or运算。假如一个北大才女过来,那么程序员们一定欢迎她(是因为这个么?);一个普通大学的博士也可以,但是可能没人和他做朋友;但是假如只是普通大学的本科毕业,那么就不行了。
我们也会遇到像毕导这样的985博士,这个时候显然公司巴不得他加入。上述情况归纳起来就是:假如两个条件都不符合,我们才会返回“否”。

如果C公司不太喜欢A校,它的条件是:
“如果你不是从A校高中毕业,你就可以来。”(初中毕业也行吗?)
这一点一目了然了:如果你是A校的,那么即使你中考语文150,人家也不会要你。

有时,这些运算符可以搭配使用。例如:
D公司想要的人,不是C公司的,但是必须高中毕业。
那么这里的条件无非就是:(!在C公司工作) and 高中毕业

那么我们回归主题。了解了0和1之间的位运算,我们下面来看一下整数之间的位运算。

我先问个小问题:在二进制下如何不用负号表示-1?
这个问题其实很久以前就有答案了:答案很流氓——它似乎在对你说“你管我怎么弄出来的,我行就是了”。
(像极了某些数学证明)

很久以前的答案就是:二进制所有(无限个)数位全部都是1时,它表示的值可以看作-1。
你可能要问:为什么?
其实这种表示方法相当自洽:我们知道,(-1) + 1 = 0。二进制下的0是啥?所有数位都是0时,值就是0。
当我们对这个全是1的数再+1的时候,这些1全部进位,凡是有1的地方都变成了0。如果一个全都是1的数,所有数位变成了0,那么它就表示0。
因此我们认为这种方法可以表示负整数。

在有限个数位的存储器中,我们一般用64个数位存储一个数字。我们将这些数位从个位开始,从右至左标号0~31,再从左到右标号0~(-32)。第31位被认为是符号位,当存储数据为负数时,第31位为1,正数则为0。
而整数一般只占32位,因此我们只用4个字节(32 bit)存储整数。
因此1111 1111 1111 1111 1111 1111 1111 1111就是-1在32-bit下的存储值。

因此a and b,我们认为它是a和b的每一个数位都取and运算后返回的数c。比如,3 and 2 = 2。
同样,a or b,也可以将每一个数位or运算。比如,3 or 5 = 7。
not a,我们记作~a。在任何情况下,~a = -a-1。

那么,位运算有什么用呢?

你可能已经注意到了——计算机基于位运算,而计算机可以算加法,说明我们可以用位运算实现加法。

很显然,我们要算两个整数之和,就要把对应数位的和全部放进新数里。我们假设10+45=c,a是数位中允许2存在的二进制数。
10和45在2进制下是1010和101101
将每一个数位分别相加,我们得到c="102111"。将所有2都进位,易得,c=110111,也就是55。

因此我们只需考虑0和1之间的加法。这一点我们列一下所有情况就知道了。
0+1=01,1+0=01,1+1=10,0+0=00。
首先,十位只有1+1时才为1,而这恰好符合and运算性质。
其次,个位在a与b不相同时返回1,否则返回0。

十位很简单,个位怎么办?又有一群神人给出了答案:他们命名一个运算符a^b,读作a“异或”b。

而异或公式被他们硬是想了出来:a^b = (a & ~b) | (b & ~a)

因此加法问题被解决。电子计算器也随之被发明。CPU的计算实际上也就是将两个数加起来返回罢了。位运算是人类通往计算机科学和互联网的大门。

我的分享到这里,谢谢朋友们!

This post belongs to Column 「rice0208跳坑记」 .

3 comments
latest

  • Deleted Flog User

    建议

    1. 不要加入“(初中毕业也行吗?)”之类的文本,真正想求知的读者不想被这些话分心。

    2. 我认为不需要加入“生活中的例子”,本来与、或、非三个字就已经能够解释它所表达的含义了。加之“有1出1,全0出0”之类的解释后,能看到你文章的人(你应该知道本站对本校外部成员封闭)应该都理解了。

    3.(!在C公司工作) and 高中毕业 这个东西有BUG,Python中不存在!这个操作符,据我所知只有C++同时支持!、&&、||和not、and、or两种表示法,我想你应该不是想秀自己的C++吧。你应该写成 (not 在C公司工作) and 高中毕业

  • Miki_Sayaka
    Admin
    :

    建议

    1. 不要加入“(初中毕业也行吗?)”之类的文本,真正想求知的读者不想被这些话分心。

    2. 我认为不需要加入“生活中的例子”,本来与、或、非三个字就已经能够解释它所表达的含义了。加之“有1出1,全0出0”之类的解释后,能看到你文章的人(你应该知道本站对本校外部成员封闭)应该都理解了。

    3.(!在C公司工作) and 高中毕业 这个东西有BUG,Python中不存在!这个操作符,据我所知只有C++同时支持!、&&、||和not、and、or两种表示法,我想你应该不是想秀自己的C++吧。你应该写成 (not 在C公司工作) and 高中毕业

    但其实写得还可以

  • Itachi
    Admin

    下期和算法专栏来个联动如何?

    主讲数据结构,大致讲单链表、双链表、队列、栈这4种

    你讲概念和应用。我这边逐步创建并分析代码,讲点TDD相关内容。