您现在的位置: 365建站网 > 365文章 > 原码、反码、补码怎么表示?计算机机器数三种编码与取值范围

原码、反码、补码怎么表示?计算机机器数三种编码与取值范围

文章来源:365jz.com 点击数:53 更新时间:2026-11-07 17:14

学 C/Java 时第一次接触“补码”几乎都会晕:为什么 -1 在内存里是 8 个 F?为什么 byte 的范围是 -128~127 而不是对称的 -127~127?答案不在“规则”,而在硬件为什么要这么设计。本文从模运算讲起,把原码、反码、补码一次说清。

原码反码补码 机器数表示方法

一、机器数与“符号位”的由来

计算机里数值以二进制位串存储,一个 int 固定 32 位。要表示负数,就必须从这些位里“借”一个含义出来,约定最高位当符号位:0 正 1 负。带上符号编码的位串就叫机器数,它对应的真实数值叫真值。

于是同一个位串,按不同编码规则能读出不同结果——三种编码就是这么来的:

  • 原码:符号位 + 真值绝对值的二进制。8 位下 +5 = 0000 0101,-5 = 1000 0101。
  • 反码:正数同原码;负数符号位不变、其余位取反。-5 = 1111 1010。
  • 补码:正数同原码;负数是反码 +1。-5 = 1111 1011。

记口诀:负数补码 = 反码加 1;等价做法是“从右往左找到第一个 1,它左边的位全部取反”。

二、为什么必须是补码

两个根本原因,都来自硬件:

1. 让减法变成加法。CPU 只有加法器最划算。8 位模 2^8 = 256 的运算里,-5 的等价表示就是 256 - 5 = 251 = 1111 1011——这正是补码。于是 7 + (-5) 可以算 0000 0111 + 1111 1011 = 1 0000 0010,丢掉溢出到模外面的最高位,结果 0000 0010 = 2,完全正确。补码本质就是模运算下的表示。

2. 零只有一个表示。原码里 +0 = 0000 0000、-0 = 1000 0000 两个编码,比较和判零都麻烦;反码同样有两个零(1111 1111 是 -0)。补码下只有 0000 0000,而 1000 0000 被“白捡”给了 -128。

反码只存在于历史与教学:早期的 CDC 160A 等机器真用过,它同样有双零问题。今天通用 CPU 全是补码。

三、取值范围为什么不对称

n 位有符号补码范围是 [-2^(n-1), 2^(n-1)-1]:

  • byte(8 位):-128 ~ 127,共 256 个数,负数多一个;
  • int(32 位):-2147483648 ~ 2147483647;
  • long(64 位):-9223372036854775808 ~ 9223372036854775807。

这解释了很多经典 bug:Math.Abs(int.MinValue) 在 C# 里会溢出(因为正数侧没有 2147483648);Java Integer.MIN_VALUE 取负还是它自己;C 里 -x 对最小值未定义。

无符号类型则全用于表示正数:unsigned char 0~255、unsigned int 0~4294967295。

四、符号扩展与截断

把 byte 提升到 int 时,高位补的是符号位而不是 0,这叫符号扩展:byte b = -5(0xFB)→ int = 0xFFFFFFFB = -5。若误当成无符号处理,0xFB 会变成 251,这是位操作里最常见的错。

C#/byte 与 Java 的差异要记住:Java 没有无符号 byte(直到 Java 8 才有 Byte.toUnsignedInt()),所以 Java 里读一个 0~255 的值必须 buf[i] & 0xFF;而 C# 有 byte 无符号类型,sbyte 才是有符号。

int v = buffer[0] & 0xFF;   // Java:正确的 0~255 取法

五、溢出与“绕回”

补码运算遇结果超出范围就是溢出。两种语言行为完全不同:

  • Java/C# 默认静默绕回(wrap-around):int.MaxValue + 1 == int.MinValue;
  • C/C++ 有符号溢出是未定义行为,编译器可以据此优化掉你以为存在的判断(比如 if (x + 1 > x) 被优化成恒真)。安全代码要用 __builtin_add_overflow 或无符号类型;
  • C# 可用 checked { ... } 块或编译选项让溢出抛 OverflowException。

判断溢出是否发生:两个同号数相加,结果符号相反即溢出,即 ((a ^ s) & (b ^ s)) < 0(a、b 为加数,s 为和)。

六、快速心算方法

  1. 求 -x 的补码:把 x 的所有位取反再加 1;
  2. 从补码读回真值(若符号位为 1):先减 1、再取反得绝对值,加上负号;或直接“取反加 1”求其绝对值;
  3. 例:1110 0100。符号位为 1 → 取反 0001 1011 → 加 1 0001 1100 = 28 → 真值 -28。

验算工具:Python 里 ctypes.c_int8(-5) 看 wrap;np.int8 也能复现。C 里可以直接 printf("%hhu", (unsigned char)-5) 打印出 251,直观看到补码表示。

七、常见问题 FAQ

问:补码是“规定”还是“推导出来的”?
答:可以推导。要求在 2^n 的模空间里 x + (-x) ≡ 0,则 -x ≡ 2^n - x,展开后恰好等于“取反加 1”。规则只是这个等式的实现形式。

问:原码/反码今天还有用吗?
答:有。反码在网络校验和(IPv4 头部 checksum,RFC 1071)里被使用,因为它对循环移位不敏感;原码思想则体现在符号-幅值表示的某些硬件与旧语言中。

问:浮点数用补码吗?
答:不用。IEEE 754 是符号位 + 阶码(移码/excess)+ 尾数三段,所以 -0.0 是存在的,0.1 + 0.2 != 0.3 也和补码无关。

核心只需一句话:补码 = 模 2^n 下的负数表示,它让加减共用一套加法器、让零唯一,代价是取值范围向负数偏一格。

生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »