
为什么计算机不用十进制、二十进制,偏偏就要用 0 和 1?
原因其实很简单,不是它“喜欢”二进制,而是它的硬件决定了它更适合二进制。
计算机内部本质上都是集成电路。集成电路的引脚上,电压通常只有两种明显状态:
两种状态,刚好就可以对应成:
所以计算机用二进制来处理信息,不是人规定出来的,而是硬件天然就长这样。

平时在计算机里,经常会把数据按 8 位一组来处理。
8 位二进制数,也叫 1 字节(byte)。
1 字节 = 8 比特(bit)
如果一个二进制数本来不到 8 位怎么办?
那就往高位补 0。
100111
相等于
00100111
二进制本质上也是“按位展开”。
从右往左看:
2^02^12^2
比如:
39 -> 00100111
= 1×2^0 + 1×2^1 + 1×2^2 + 1×2^5
= 1 + 2 + 4 + 32
这里还有两个词:
2^02^12^22如果只看到这里,很容易以为:
“哦,二进制就是拿来表示数字的。”
其实不是。
二进制本质上表示的是信息。
比如字符也能表示:
'A' -> ASCII = 65 -> 二进制 01000001
也就是说:
所以计算机处理信息,说到底就是在处理一堆 0 和 1。
移位运算,分左移和右移。
在整数的情况下,你可以先这么理解:
比如:
00000010 = 2 -> 左移一位 -> 00000100 = 4
00000100 = 4 -> 右移一位 -> 00000010 = 2
在 C 语言里,常见写法就是:
<<左移>>右移a = 39;
b = a << 2; // 左移两位
那 39 左移两位之后是多少?
当然不是 3900。
因为这里不是十进制字符串后面补 0,而是对它的二进制位做移动。
39 = 00100111
左移两位 -> 10011100 = 156
不过这里要注意一点:
移位这种“乘 2 / 除 2”的理解,主要是拿来帮助你理解整数。
像 、 这些小数类型,底层虽然也还是二进制,但它们的表示规则和整数不一样,所以不能直接按这个思路去套。floatdouble
前面说的,基本都是正数和 0。
但现实里还有负数,那计算机怎么表示负数?
这里就会用到补码。
假设现在有个时钟,我让你把 6 点拨到 0 点,怎么做?
你可以:
这就是一种“绕圈子”的思路,也就是模运算的感觉。
比如一个字节全是 1:
11111111
如果再加 1,理论上会变成:
100000000
但问题是,前面那个 已经跑到第 9 位去了,超出了 8 位的范围。1
这时候它就会被丢掉,最后只剩下:
00000000
这就叫溢出。
00000010
11111110
--------
100000000
最高位溢出后丢掉,最后就变成了:
00000000
所以在 8 位系统里:
11111110
既可以表示 ,也可以表示 。254-2
关键在于:
你到底是把它当成无符号数,还是有符号数。
这里还有一个很常见的快速规则:
求负数的补码 = 取反 + 1
这里可能还会有一个疑问:
为什么负数非得搞这么麻烦,直接拿一位表示正负不行吗?
当然也不是完全不行,但那样做会带来一个问题:
加减法处理起来很麻烦。
而补码的好处就在于:
CPU 不需要专门搞两套加法规则。
正数、负数,很多时候都可以统一用加法器去算。
所以补码不是“故意搞复杂”,而是为了让硬件更方便处理正负数。
回想前面的右移,对正数来说,前面补 0 就行。
00001000 = 8 -> 右移两位 -> 00000010 = 2
那负数呢?
11111100 = -4
如果你还是机械地往前补 0:
00111111
那这个结果显然就不对劲了。
所以这里就要区分两种右移:
也就是说:
所以你不能简单理解成“逻辑右移专门给正数,算术右移专门给负数”,更准确地说:
这是两种不同的补位规则。
符号扩展的逻辑和上面是一样的。
比如:把一个 8 位二进制,在不改变值的情况下,扩展成 16 位
01111111 -> 000000000111111111111111 -> 1111111111111111这样扩展完之后,它表示的值还是原来的值。
算术很简单,就是四则运算:
+-×÷逻辑运算则是:
它们的含义分别是:
这些逻辑运算为什么重要?
因为计算机做判断、做条件控制、做位操作,本质上都离不开这些东西。
这篇讲下来,其实核心就一句话:
计算机之所以能处理信息,是因为它能把各种信息都变成 0 和 1,然后再对这些 0 和 1 做运算。
所以你看到的:
到了计算机底层,最后都会落到二进制上。
也正因为这样,二进制不是“数学题”,而是计算机处理信息最基础的语言。