十进制转二进制:除二取余,余数从下往上读。 二进制转十进制:按位权展开,只把 1 的那几位加起来,权重是 128 64 32 16 8 4 2 1。 小数部分反过来:乘二取整,从上往下读。方向相反,这是最容易记混的一处。
上一篇认写法,这一篇讲换算。两个方向各一套动作,都不难,但要练熟。 教材用 173 当例子,因为它正好八位、二进制里 0 和 1 都有,看得清。 最后一节会说到 0.1 存不下——那是「小数部分转换」最实际的后果。
一、十进制转二进制:除二取余
把 173 一直除以 2,每次记下余数:173 除 2 商 86 余 1;86 除 2 商 43 余 0;一直除到商是 0 为止。
八行除完,余数从上到下是 1、0、1、1、0、1、0、1。
从下往上读,得到 10101101。
为什么从下往上读?因为先除出来的余数是最低位(个位对应的那一位),最后除出来的才是最高位。 顺序反了,数就全错了。而且反过来的结果看着也很像样——不会报错,只是错,所以这个方向必须记牢。
二、反过来:按位权展开
10101101 一共 8 位,从左边开始每一位的权重是 128、64、32、16、8、4、2、1。
只把「这一位是 1」的那几个加起来:128 + 32 + 8 + 4 + 1 = 173。
| 位 | 权重 | 这一位是 1 吗 | 加起来 |
|---|---|---|---|
| 1 | 128 | 1 | 128 |
| 2 | 64 | 0 | 不加 |
| 3 | 32 | 1 | 32 |
| 4 | 16 | 0 | 不加 |
| 5 | 8 | 1 | 8 |
| 6 | 4 | 1 | 4 |
| 7 | 2 | 0 | 不加 |
| 8 | 1 | 1 | 1 |
这个方法不用记公式,只需要认权重。记住那八个数,八位二进制就能心算。 权重是怎么来的?每一位是 2 的幂:从右往左数第 0 位是 1,第 1 位是 2,第 7 位是 128。 这就是「位权」两个字的意思。 这也是第 2.4 讲数组地址能用乘法算出来的同一个道理——位置决定权重,权重决定贡献多少。
三、小数部分:乘二取整
整数部分除二取余,小数部分反过来:每次乘 2,取出整数部分,剩下的小数继续乘。 0.625 乘 2 得 1.25,取 1,剩 0.25;0.25 乘 2 得 0.5,取 0,剩 0.5;0.5 乘 2 得 1.0,取 1,剩 0。 停。 整数部分从上往下读,得到 0.101。
验算一下:0.101 对应 1/2 加 1/8,也就是 0.5 加 0.125,正好 0.625。 所以两个方向的动作是相反的: 整数部分除二取余、从下往上读;小数部分乘二取整、从上往下读。 这一处最容易记混。
四、0.1 为什么存不下
0.625 三步就停了。但不是所有小数都这么听话。
0.1 转二进制是 0.0001100110011…,1 和 0 循环下去,永远停不下来。
道理和十进制里的 1/3 一样:1/3 写成小数是 0.3333…,也永远写不完。
只是换了个进制,写不完的数换了一批。
电脑的位数有限,只能截断。所以存进去的 0.1 并不是精确的 0.1,而是「最接近它的那个能存的数」。 这就是 0.1 加 0.2 在某些语言里不等于 0.3 的根源。它跟算法无关,跟「表示」有关。 工程上的处理办法是:比大小别用等号,用「差的绝对值小于某个很小的数」;金额别用小数,用整数分。
五、动手 Demo:173 除二取余,再按位权加回去
先把 173 一路除以 2,把每行的商和余数打出来,再从下往上读。 再用权重 128 到 1 逐位验证,看加出来是不是 173。 最后转一个带小数的 0.625,看三个 0 和 1 是怎么取的。
n, bits = 173, []
while n:
print(n, n // 2, n % 2)
bits.append(str(n % 2))
n //= 2
print('从下往上读 0b' + ''.join(reversed(bits)))
s = '10101101'
total, w = 0, 128
for c in s:
if c == '1':
total += w
print(c, w, total)
w //= 2
f, out = 0.625, []
while f and len(out) < 8:
f *= 2
i = int(f)
out.append(str(i))
f -= i
print('%.4f' % f, i)
print('小数部分 0.' + ''.join(out))
实跑输出:
--------------------------------------------------------------------
二进制与十进制互转
--------------------------------------------------------------------
① 十进制转二进制:除二取余
--------------------------------------------------------------------
把 173 一直除以 2,记下每次的余数:
| 当前数 | 除以 2 后的商 | 余数 |
|---:|---:|---:|
| 173 | 86 | 1 |
| 86 | 43 | 0 |
| 43 | 21 | 1 |
| 21 | 10 | 1 |
| 10 | 5 | 0 |
| 5 | 2 | 1 |
| 2 | 1 | 0 |
| 1 | 0 | 1 |
余数从下往上读:0b10101101
为什么从下往上读?因为先除出来的是最低位(个位对应的那一位),最后除出来的是最高位。
顺序反了,数就全错了。
② 反过来:按位权展开
--------------------------------------------------------------------
10101101 一共 8 位,从左边开始权重是:
128 64 32 16 8 4 2 1
只把 1 的那几位加起来:128 + 32 + 8 + 4 + 1
= 173
| 位 | 权重 | 这一位是 1 吗 | 加起来 |
|---:|---|---:|---|
| 1 | 128 | 1 | 128 |
| 2 | 64 | 0 | 不加 |
| 3 | 32 | 1 | 32 |
| 4 | 16 | 0 | 不加 |
| 5 | 8 | 1 | 8 |
| 6 | 4 | 1 | 4 |
| 7 | 2 | 0 | 不加 |
| 8 | 1 | 1 | 1 |
每一位的权重是 2 的幂:1、2、4、8、16、32、64、128。记住这八个数,八位二进制就能心算了。
③ 小数部分:乘二取整
--------------------------------------------------------------------
把 0.625 转成二进制:
| 当前值 | 乘 2 | 取整数部分 |
|---:|---:|---|
| 0.6250 | 1.2500 | 1 |
| 0.2500 | 0.5000 | 0 |
| 0.5000 | 1.0000 | 1 |
整数部分从上往下读:0.101
验算:0.5 + 0.125 = 0.625
整数部分「除二取余、从下往上读」,小数部分「乘二取整、从上往下读」。
方向相反,这是最容易记混的一处。
还有一件事要知道:0.1 这种十进制小数,转成二进制是无限循环的,存不下,只能截断。
这就是 0.1 + 0.2 在某些语言里不等于 0.3 的根源。
①n // 2 是商,n % 2 是余数。这一对就是从 2.1 讲到现在的「能分几组、剩几个」。
②bits 里存的是「先出来的低位」,所以最后要用 reversed 倒过来读。倒这一步忘了,结果就反了。
③第二段从 128 开始,每轮 w //= 2 折半。打印出来的 total 是一路累加的过程。
④小数那段 while f 的循环条件是「还有小数没取完」。
len(out) < 8 是保险——防 0.1 那种无限循环把程序卡死。
30 秒实验
- 把 173 换成 172,看除二取余的结果变成什么(提示:末位会变成 0)。
- 把
0.625换成0.1,看那个保险条件起没起作用。 - 写一句
0.1 + 0.2 == 0.3,看它返回什么。
常见误区
- 「余数从上往下读就行。」要从下往上读。反了不报错,只是数全错。
- 「小数也是除二取余。」小数是乘二取整、从上往下读,方向相反。
- 「0.1 存不存在是语言的问题。」二进制存不下 0.1,所有语言都一样,只是显示时四舍五入了。
自测三问(点开对答案)
1. 十进制转二进制怎么算? —— 一直除以 2 记余数,除到商为 0,余数从下往上读。 2. 八位二进制的权重是哪八个数? —— 128、64、32、16、8、4、2、1,从高位到低位依次减半。 3. 为什么 0.1 加 0.2 可能不等于 0.3? —— 0.1 转二进制是无限循环的,只能截断存储,存进去的已经不是精确的 0.1。正数会转了,负数呢? 二进制里没有「减号」这个东西——下一节讲补码,一台只会加法的机器怎么算减法。
- ☐ (本篇完,下一篇 8.3 负数怎么表示)