整数编码
[!IMPORTANT]
数字是以“补码”的形式存储在计算机中。
- 原码(sign-magnitude):原码中,将数据的二进制表示的最高位视为符号位,0表示正数,1表示负数。
- 反码(1‘s complement):正数的反码与原码相同,负数的反码是除符号位外的所有位取反。
- 补码(2’s complement):正数的补码与原码相同,负数的补码是在其反码的基础上加1。

计算机内部的硬件电路主要是基于加法运算实现的,因此,我们需要考虑三种数据表示方法对加法运算的支持程度。
- 原码最直观,但负数的原码不能直接用于计算,比如上面的$+5(00000101)+ (-0(10000000))= -5(10000101)$,正确答案应该是5。
- 通过取反码相加再取反,可以得到正确结果,$+5(00000101) + (-0(11111111))= 5 (00000101)$(反码相加需要循环进位,最高位的1进位到最低位),但反码有正负0两种表示,存在歧义。
- 如果换成补码,则 $+5(00000101) + (-0(0000000)) = 5 (00000101)$(补码相加,溢出位直接丢弃。)
正是因为补码完美解决了原码和反码的痛点,现代计算机的硬件电路里其实根本没有独立的“减法器”。
当你执行减法(比如 A - B)时,CPU 内部的加法器会通过一个简单的逻辑电路,把减数 B 的每一位取反,并在最低位的进位输入端偷偷加一个 1。这正好完美实现了“取反加一”的补码转换操作。这样一来,计算机只需要一套加法器电路,就能通吃所有的加减运算,极大地简化了硬件设计并提升了运算速度。
浮点数编码
浮点数由三个部分组成,由32比特长度的二进制数表示。
$b_{31}b_{30}b_{29}...b_{2}b_{1}b_{0}$
- 符号位S:占1位,对应 $b_{31}$,
- 指数位E: 占8位,对应 $b_{30}b_{29}…b_{23}$
- 分数位N:占23位,对应 $b_{22}b_{21}…b_{0}$

其转换公示如上图所示,由于指数位的存在,它的取值范围远大于int,但也牺牲了精度,并且两个相邻的数字的差值会随表示数据的增大而增大。

对于指数位,E=0和E=255都有特殊含义,因此最float可表示的最大值为 $2^{254-127} \cdot (2-2^{23})$ 。
字符编码
计算机中的数据都是二进制表示,因此,为了表示字符,需要规定字符与二进制数的对应关系。
- 最早的字符集就是 ASCII 字符集。它只有128个编码,只能表示英文。
- 为了表示中文,中国国家标准总局发布了GB2312字符集,支持简体字。后来,扩展为GBK字符集,兼容了繁体字和罕见字。
- 字符集限制了多语言环境下的数据表示,不同语言的字符集会发生冲突。因此,unicode诞生,它支持了几乎所有的语言,甚至表情符。为了解决多种长度的unicode的解析歧义问题,unicode采用了等长编码的方式,每个字符都占用2个字节。
- utf-8在unicode的基础上,实现了变长编码,它通过引入了首个字节的最高n位以及后续字节的前两位来表示字节数。