摸鱼人的正常操作
进制转换原理完全解析
发布于: 2021-07-17 更新于: 2021-12-11 分类于: 数理 阅读次数: 

尝试写一篇仅代表个人观点的, 不严谨的, 小学生都能看懂的学术 科普 儿童读物

数字是什么

假设我们已经小学毕业, 通过小学课堂我们对数字已经有了一定的刻板印象.
现在下面分别代表两样东西的数字:

85467根头发
1颗脑袋

我们会直观地觉得上面的东西比下面大的多. 这就是数字表示量的功能, 而实现这种功能的基本前提就是我们提供过学习数字这一人类造物, 从而形成了用数字抽象描述量的共识.
数字的这种抽象, 就像刚学画画的时候我们会参照鸡蛋在纸上画椭圆形. 这一过程中鸡蛋是无法直接装进纸面上的, 但是鸡蛋的抽象–椭圆形却可以呈现在纸上, 椭圆与鸡蛋的关系是在平面上呈现相近的形状;
人类在认识数量的多少时无法直接描述数量的概念, 所以发明了数字, 数字可以呈现在人类的文字和语言中, 并且通过相同的计算规则(加减乘除等)相联系.
也就是说数字的本质是数量在人类可表达领域的抽象, 它是人类为了描述数量而创造的一种符号.

小数点的含义

小数点是数字系统中的分隔符, 一般定义中它的左边是整数部分, 右边是小数部分.
我们引入一个数轴:

0 1 2 3 -1 -2 -3

数轴是一个一维空间, 由于数量也是一维的, 所以可以很方便地用垂直于数轴的直线在数轴上标记对应的数量.
例如整数1, 2和实数1.1的位置分别如下图所示:

0 1 2 3 -1 -2 -3 1 1.1 2

可以看出整数在数轴上是离散分布的, 要填满整数间的空隙就需要用到小数点右边的小数部分. 小数表示的是对整数之间间隔的单位区间切分的量.

精度

当我们使用小数是精度就随之出现, 通常语言上的表达是”精确到小数点后某位”.
那么精度有什么含义呢? 我们同样借助数轴, 尝试用数轴上不同高度的垂直线来表示不同精度, 精度越高标识线越长.
我们将上面的数轴局部放大100倍并标识四个数, 分别是实数域中的整数1, 1.1, 1.233保留小数点后一位得到的1.2, 1.230保留小数点后两位得到的1.23 如下图所示:

1.5 1 1.1 1.2 1.3 1.4 1 1.1

整数1确定的实数1.1, 我们用射线表示他们在实数域中的精度, 也就是说我们在实数计算中使用整数1时, 它的实际形态应该是1.00000...(无穷个0)它的精度是小数点后无穷位. 同理, 确定的实数1.1的实际形态是1.100000...(无穷个0).
而保留或者说精确到小数点后有限位时, 在数轴上呈现柱形. 例如上面的1.233保留小数点后一位得到的1.2或者说≈1.2这个量, 他的实际取值范围可以是[1.15,1.25)这个区间(根据四舍五入)内的任何一个值而不仅仅是1.20;而≈1.23相较≈1.2有更高的精度, 实际取值范围也更窄(更精确).
数学计算是要求严谨的, 所以精度不能凭空产生, 也就是说计算过程中的精度只能减少不能增加.

进制是什么

谈到进制转换就”逢n进1”, 这个概念从小学甚至学龄前就开始生根发芽.
“逢n进1”的规则放在整数领域没有问题, 然而到了小数进制转换时, 这个规则非但没有用, 还可能对认识小数的进制转换造成干扰.
它是进制转换原理在整数领域的一个抽象, 是现象而非本质.
进制首先是一种表达数量的有限符号与规则的集合;其次需要包含”进”这一概念, 也就是说进制中使用的规则存在递进关系(例如十进制中十位是个位的进阶).
进制的本体是符号, 二进制使用两种符号通常是01;十进制通常使用0,19这十种符号;十六进制通常使用0,19,A,BF这十六种符号.
这里之所以说是通常, 是因为使用这些符号符合人类知识体系的常识, 而非必须使用这些符号才能表达数量. 比如, 二进制可以使用,这两种符号, 只要知道数字系统中包含的符号全集, 以及它们表示的大小关系, 就能利用这一数字系统表达和解读数量. 我们来看看520社会主义核心价值语境中会变成什么 😎.
上面说到数字系统, 实际上并不是所有的数字系统都需要进制, 例如手指计数和”正”字计数法. 而使用进制的一个优势就在于使用较少的记录空间表示较大的数量范围, 从而提高了表达和解读的效率.

进制转换原理

进制转换需要遵循的一个原则是保持所表示的数量不变.
此外, 按照人类的习惯我们在进制转换过程中选择十进制作为表示数量的一个基准.

整数

整数中负数可以看作正数的镜像, 表示上只多了前面的负号前缀, 为节约篇幅以下内容只涉及正整数范围.
以十进制与二进制之间的相互转换为例, 我们在下面的数轴上分别以十进制和二进制的表示方式标识数量:

2 3 4 5 1 0 -1 -1 0 1 10 11 100 101 -(1x20) 6 0x20 1x20 1x21+0x20 1x21+1x20 110 1x22+0x21+0x20 1x22+0x21+1x20 1x22+1x21+0x20

想要知道十进制是如何与二进制相互转换的, 就需要了解数量是如何通过二进制表达的.
在常规二进制数字系统中我们只有两种数字符号01.
我们可以用0来表达数量0;1来表达数量1.
但是当数量来到2时, 符号系统中就没有能直接表达数量2的符号了.
那么为了在二进制数字系统中高效地表达”2”这个概念, 我们需要多使用一个二进制位并制定相应的进位规则. 二进制数字系统很小, 这种情况下只存在两种可能的表达方式, 列举如下:
a. 11, 相应的规则是: 当前位置上表达的数量达到2则向高位进1, 原位置不变;
b. 10, 相应的规则是: 当前位置上表达的数量达到2则向高位进1, 当前位置清零;
显然, 作为数字系统两种规则都能用于表达数量, 也就是说他们都合法;而我们通常所使用的进制都是b类型的规则.
为了展现二者之间的差异, 我们分别套用上述两种规则来表示数量3, 结果如下:
a. 111
b. 11
规则a占用了3个数字位, 而规则b只占用2个, 换言之, 表达同样的数量规则b规则a更高效, 并且这种差异会随着数量的增大而急剧增加.

n进制转十进制

从n进制转换成十进制, 也可以说是解读n进制数实际表示的数量.
n进制计数方法中它的第i个数字位所代表的数量权重就是 $n^i$ .
那么,从n进制转换到十进制只要累加各个位上数字字符所代表的数量乘以对应位数量的权重就可以了. 例如, 二进制的11转换为十进制的算式就像这样: $12^1+12^0 = 3$

十进制转n进制

从十进制转换成n进制, 也可以说是用n进制表达数量.
如同上面所提到的进位规则, 我们可以用数数的方式, 把现有的数量一个一个往n进制里移, 例如数量5表示成二进制我们可以按照如下步骤将数量5最终表示成二进制的101 :

步骤 剩余数量 二进制表示
0 5 0
1 4 1
2 3 10(发生了进位)
3 2 11
4 1 100(发生了进位)
5 0 101

当然, 这样的方式过于低效.
我们注意到上面过程中往二进制数从0开始每添加2个数量, 它就从第0位向第1位(权重 $2^1$ )进位;每添加4个数量, 它就从第1位向第2位(权重 $2^2$)进位. 依此类推, 每增加 $2^i$个数量就会从第i-1位向第i位发生一次进位.
于是, 我们可以将总量除以 $2^i$ 来获悉二进制表示中地i位产生的增量次数.
以上面的数量5来举例, 我们通过 $5/(2^0)=5$ 获悉它的二进制表示在第0位产生了5次增量, 而这些增量每满2就会发生一次进位.
我们通过 $5/2 = 2…1$ 得知数量5的二进制表示在第0位上向第1位进位了2次, 且在第0位上余下数量1,用二进制符号1表示.
由于能影响高位的只有低位的进位, 我们在观察第1位时只需要考虑从地0位进位上来的2次, 也就是说第1位上的增量是2.
我们通过 $2/2 = 1…0$ 得知数量5的二进制表示在第1位上向第2位进位了1次, 且在第1位上余下数量0, 用二进制符号0表示.
接下来我们将视线移向二进制的第2位, 这里只从第1位进位上来一次, 直接用二进制符号1表示.
通过上述步骤, 我们同样将数量5表示成了二进制的101.
将上述方法推广到n进制, 就有了n进制表示数量的一般方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
---------
将数量A用n进制表示
输入: 进制数n, 长度为n的不重复数字符号序列U(按表示数量从小到大排列), 数量A
输出: 由U中符号组成的n进制表示X
---------
let 逆序X = []
let 剩余数量 = A
while(剩余数量>0):
let 余数 = 剩余数量 整除取余 n
let 剩余数量 = 下取整(剩余数量 除以 n)
逆序X.append(U[余数])
X = 逆序X.reverse()

更高效的方法?

oB1111<->0xF(空间换时间)

小数

小数表示的是不足单位1的数量, 而表示的方式是对单位1空间的再切分.
为了说明小数表示数量的方式, 需要引入数轴和区间, 同样以二进制表示为例, 我们尝试使用二进制表示0.38这个量 , 首先我们需要把这个量在数轴上标识出来(为了减少计算量我们把他作为保留小数点后两位的0.38处理):

0.5 0 0.1 0.2 0.3 0.4 1 0.6 0.7 0.8 0.9 0.38

从十进制的角度看, 十进制的小数点后1位从0.00.9将数轴上01的区间且分成均匀的10份(编号0~9的10个区间), 而小数点后第二位又将每份区间再均匀切成10份. 用0.3表示第一次切分后的第3个区间, 用0.38表示第一次切分后的第三个区间中第二次切分的第八个区间, 从而用两位小数就标识出了数量0.38在数轴上的位置.
那么二进制呢? 同样是切分, 但由于二进制的数字符号只有0, 1两种, 同时只能表示两个区间, 因此它的切分每次只能将区间平均分成两份.
以下是使用二进制表示的第一次切分结果:

0.5 0 0.1 0.2 0.3 0.4 1 0.6 0.7 0.8 0.9 0 1

可以观察到我们的目标值0.38落在以符号0代表的第一块区间, 记录下二进制符号0, 并对该区间进行第二次切分:

0.5 0 0.1 0.2 0.3 0.4 1 0.6 0.7 0.8 0.9 0 1 0

这一次0.38落在第二次切分后符号1所代表的区间中, 记录下该符号1, 接着进行第三次切分:

0.5 0 0.1 0.2 0.3 0.4 1 0.6 0.7 0.8 0.9 0 1 1 0

经过第三次切分后, 切分的第二快小区间的下界(二进制0.011所表示的量0.375)已经十分接近目标值0.38了. 并且0.375保留两位小数后的值恰好是0.38.
这种情况下, 我们相当于已经完成≈0.38这个数量的二进制表示了.
通过上述例子我们可以推广得出小数范围的n进制数实际表示的数量:
$$B=\sum^{k}_{i=1} x_i*n^{-i}$$
其中 $x_i$ 为长度为 $k$ 位的 n 进制小数小数点后的第 $i$ 位字符所代表的实际数量.
同理我们可以推广的出小数数量的n进制表示方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
---------
将小数数量B用n进制表示
输入: 进制数n, 长度为n的不重复数字符号序列U(按表示数量从小到大排列), 小数数量B(取值区间(0,1))
输出: 由U中符号组成的n进制表示X(代表小数点后的数)
---------
let X = []
let 区间宽度 = B
let 权重 = 1/n
while( 未达到精度截至条件 ):
let 目标前的分块数量 = 下取整(区间宽度/权重)
区间宽度 = 区间宽度-(目标前的分块数量*权重)
权重 = 权重/n
X.append(U[目标前的分块数量])

精度截至条件

上面的例子我们实际上以保留到原始数量的有效精度能够还原原数量为准来控制精度, 这样的出来的小数位数比较少;
当然我们也可以用更精确的条件加以限制, 比如划分的最小区间宽度小于目标值对应的最高精度范围, 这样表示的数值会更精确, 但是会使用更多的数字位数.
此外还可以使用固定精度等其他条件.

额外的话题

进制转换小数时使用无穷精度会发生什么?
(待续未完…)

--- The End ---