三种进制的分工
同一个数可以用任何进制写,但不同场景下习惯不一样,这不是随便定的:
- 二进制:分析位域、看标志位。因为每一位都直接对应硬件里的一根线
- 十六进制:读内存、看协议、查寄存器。因为 4 位一组正好一个十六进制字符,和字节的边界对得整齐
- 十进制:业务逻辑、用户界面。人能直观比较大小
- 八进制:现在主要是历史遗留,Unix 文件权限(
0755)还在用
为什么十六进制比八进制流行?因为一个字节是 8 位,十六进制正好两位表示一个字节
(0xFF),而八进制需要三位,永远对不齐。
负数:补码
计算机里没有「减号」,只有加法器。所以负数用一种巧妙的方式表示:补码。
规则是:把负数加上 2 的位宽次方,让它落到正数范围里。
比如 8 位的 -1,就是 -1 + 256 = 255,二进制 11111111。
用一个更好记的说法:从全 0 借位减 1,结果就是全 1。 所以 -1 在任何位宽下都是「全部位都是 1」。
几个关键的补码值(以 8 位为例):
0xFF= -10x80= -128(8 位能表示的最小值)0x7F= 127(8 位能表示的最大值)
注意不对称:负数比正数多一个。因为 0 占了一个位置,而「负零」被用来表示最小负数了。
同样的位,两种解释
0xFFFF 这 16 个 1,当无符号读是 65535,
当 16 位有符号读是 -1。
硬件层面完全一样,都是 16 个高电平。差别只在编译器怎么解释它。 所以跨语言传数据时,必须明确「这个字段是有符号还是无符号」—— 这是协议对接里最常见的争议来源之一。
工具里切换「有符号/无符号」看十进制那栏的变化,就能直观感受到这一点。
位宽截断
填入的数超过所选位宽时,高位会被丢掉。这不是 bug,是硬件行为: 32 位寄存器存不下 64 位的值,高位就是没有了。
常见的坑是把 64 位时间戳塞进 32 位字段,结果高位被截断,数值变得莫名其妙。 工具会在提示行标出「已截断」,提醒你这件事发生了。
常见问题
为什么负数用补码表示?
因为补码让减法可以复用加法器。用反码或原码表示负数,做减法时要额外判断符号、处理进位;补码不需要——-1 就是全 1,加任何数都自然溢出成正确结果。所以现代 CPU 全部用补码,代价是同样位宽下能表示的正数范围比负数少一个。
16 位的 -1 为什么是 0xFFFF?
按补码规则,-1 = 0 减 1,从全 0 借位减一次,结果就是全 1。16 位全 1 就是 0xFFFF。同理 8 位是 0xFF,32 位是 0xFFFFFFFF。-1 是所有位数下最好记的一个补码。
有符号和无符号到底差在哪?
同样的位模式,解释方式不同。0xFFFF 当无符号是 65535,当 16 位有符号是 -1。硬件里没有区别,都是 16 个 1;差的是编译器怎么读它。所以跨语言、跨协议传数据时,一定要说清楚是有符号还是无符号。
为什么不支持任意大的数?
技术上可以(JavaScript 的 BigInt 本身无上限),但现在限定了 64 位,因为再大就没有实际意义了——常见场景是寄存器、地址、位运算,最多用到 64 位。如果填的数超过位宽,会按位宽截断并提示。
常用进制怎么选?
二进制看位,十六进制看字节,十进制看数值,八进制现在很少用了(主要是历史遗留,和 Unix 文件权限有关)。读芯片手册用十六进制,分析位域用二进制,写业务逻辑用十进制。