二进制补码计算工具(有符号整数二进制转换)
将有符号十进制整数转换为8/16/32/64位的二进制补码表示,或进行反向转换。同时显示无符号整数解释、十六进制表示,并检测超出范围的输入。
使用提示
- 8位有符号整数的范围是-128到127。可以尝试先输入127再输入128,观察溢出边界——128无法容纳并会触发范围错误,而这正是原本会回绕到-128的边界点。
- 「无符号十进制」这一行是将同一比特序列不作为符号位处理而直接解读得到的值。与有符号那一行对比,能立刻看出有符号与无符号解释之间的差异。
- 二进制输入的位数必须与所选位宽完全一致——8位输入8位数字,16位输入16位数字,以此类推。
- 64位的计算在内部使用BigInt而非JavaScript的Number类型,因此接近2^63的数值也能无精度损失地转换。
- 本工具也可用于理解C语言、Java等语言中整数溢出的行为,它们的int(32位)、long(64位)类型都会在同样的边界处发生回绕。
常见问题
原码表示和反码表示都会产生两种不同的零的比特模式(「+0」和「-0」),这会使比较和运算电路变得复杂。补码表示的零只有一种比特模式,而且同样重要的是,用于加法的加法器电路也可以直接用于减法。这两个特性让CPU设计者能够简化算术逻辑单元,这正是几乎所有现代处理器内部都使用补码来表示有符号整数的原因。
-128到127。由于最高位被保留作为符号位,正数一侧只能表示128种不同的模式(0到127),而负数一侧可以表示128种不同的值(-1到-128),因为补码的零只占用一种比特模式,多出的一种模式被分配给了负数一侧,这就是范围不对称的原因。
反码只需将正数的每一位取反即可得到负数。补码则在取反的基础上再加1。这多出的一步消除了反码固有的「双零问题」(00000000表示「+0」,11111111表示「-0」),这也是补码成为标准表示方式的原因。
因为位宽是固定的,任何超出可表示范围的运算结果都会导致高位被截断,从而改变了符号位的含义。例如,对8位有符号最大值127加1会得到比特模式10000000,按有符号规则解读为-128。超过上限就会回绕到下限,这种违反直觉的跳变正是溢出的本质。
比特模式本身不会改变,但由于不再将最高位当作符号位,每一位都只贡献其常规的位权值。例如8位比特模式11111011,按有符号解读为-5,但按无符号解读则为251。当你把一个原本按有符号处理的值用于像C语言unsigned int这样的「无符号」类型时,实际上就会发生这种重新解释。
闲话 ― 为什么计算机使用二进制补码
二进制补码并不是表示负数的唯一方式。历史上还使用过原码表示(仅将最高位当作符号标志)和反码表示(简单地将正数的每一位取反)。这两种方式都有一个共同的缺陷:会出现两种不同的零的比特模式,即「+0」和「-0」,这使得比较逻辑和电路设计变得复杂。而二进制补码完全避免了这个「双零问题」——每一种比特模式都唯一对应一个整数值。
补码更大的优势在于,用于加法的同一套加法器电路也可以直接用于减法,因为减去一个数等价于加上它的补码。CPU无需判断某个值是正数还是负数,只需将比特模式相加即可得到正确结果。这使得算术逻辑单元(ALU)可以省去专门的减法电路,这也是几乎所有现代CPU架构都采用补码来表示有符号整数的原因。
整数溢出正是这种表示方式的直接结果。8位有符号最大值127(01111111)加1后,比特模式简单地进位为10000000——若按有符号方式解读,这就是-128。在C语言中,有符号整数溢出在技术上属于「未定义行为」,编译器优化可能在这个边界附近产生出人意料的结果,因此在实际生产代码中需要格外谨慎。相比之下,Java明确规定溢出会静默地回绕,这也是一个有趣的例子,说明即使底层比特表示完全相同,不同语言之间的行为保证也可能大相径庭。