news 2026/10/14 10:11:19

原码、反码、补码:一张图讲透计算机里的负数表示

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
原码、反码、补码:一张图讲透计算机里的负数表示

一、动机:为什么需要补码?

先看一个事实:8 位无符号数能表示0 ~ 255,共 256 个状态。
一旦要表示负数,同样 8 位还是只有 256 个状态,必须"挤"进去:

表达需求需要的状态数
0 ~ 127128
-1 ~ -128128
合计256

刚好塞满,一个不多一个不少。所以补码的表示范围是-128 ~ 127(-128是"多出来"的那个特殊值,来源见第四节)。

那问题来了:负数存进去之后,计算机要怎么算数?
如果把符号位和数值位分开对待,加减法会爆炸式地分情况:

(+5) + (+3) → 同号,直接相加 (-5) + (-3) → 同号,取绝对值相加,再加负号 (+5) + (-3) → 异号,大减小,符号跟大的走

一套加法器要分三类实现,硬件代价极高。
补码的贡献就是:让"加法"变成唯一一种运算,减法变成"加一个相反数"。

结论前置:补码不是"某种编码偏好",它是为了让 CPU 只用一套加法电路而设计的表示法。
现代 CPU 的 ALU 里根本没有减法器,全是加法器。


二、三种码的定义

设一个数:-3,用 8 位表示。

2.1 原码(Sign-Magnitude)

原码 = 符号位 + 绝对值的二进制
-3 → 1 | 000 0011 → 1000 0011 3 → 0 | 000 0011 → 0000 0011
  • 优点:人类直观,符号位一目了然。
  • 缺点:加减要分情况,且存在+0(0000 0000) 和-0(1000 0000) 两个"零"。
  • 范围:-127 ~ 127(浪费了-128这个位置)。

2.2 反码(Ones’ Complement)

反码 = 符号位不变,数值位按位取反
-3 原码 1000 0011 -3 反码 1111 1100 ← 数值位 0000011 取反 → 1111100 3 原码 0000 0011 3 反码 0000 0011 ← 正数反码 == 原码
  • 正数的反码永远等于原码。
  • 负零:1111 1111表示-0,和0000 0000的+0并存。
  • 范围:-127 ~ 127。

2.3 补码(Two’s Complement)

补码 = 反码 + 1(只在负数上加 1)
-3 原码 1000 0011 -3 反码 1111 1100 -3 补码 1111 1101 ← 末位 +1 3 原码 0000 0011 3 反码 0000 0011 3 补码 0000 0011 ← 正数三者完全相同

核心口诀(记这一句就够):

正数:原码 = 反码 = 补码
负数:反码 = 原码符号位不变、其余取反;补码 = 反码 + 1


三、转换规则与对照表

以 8 位为例,符号位恒为最高位,0 正 1 负:

数值原码反码补码说明
+60000 01100000 01100000 0110正数三码完全一致
-61000 01101111 10011111 1010数值位取反 → 末位 +1
-31000 00111111 11001111 1101最常考的 −3 补码
00000 00000000 00000000 0000补码的零唯一
-128(无)(无)1000 0000补码独有,比原码多一个数

一眼看出规律:正数三码完全一致;负数原码→反码是"除符号位外全翻",反码→补码是"末位 +1"。

规则三条,照记:

  • 正数:原码 = 反码 = 补码,一个字节用满 8 位。
  • 负数 · 反码:符号位不变,其余 7 位按位取反。
  • 负数 · 补码:反码末位 +1,即[x]补 = x + 2ⁿ。

负数转换链(以-6为例,一图看清三步):

原码 1 000 0110 符号位 1 不动,数值 0000110 按位取反 │ ▼ 反码 1 111 1001 末位 +1 │ ▼ 补码 1 111 1010 ← -6 在计算机里的真实形态

正数(如+6)三码完全相同:0000 0110,不需要任何转换。


四、实战演算:-3 + 2,三种码各走一遍

-3 + 2正确答案是-1。下面把三种码的计算过程都列出来,差异一目了然。

4.1 用原码算(异号相加无能为力)

1000 0011 (-3 原码) + 0000 0010 ( 2 原码) ----------- 1000 0101 (看起来像 -5)

按原码规则解读结果:1000 0101→ 符号位1,数值000 0101= 5 → 得到-5。
原码加法只适用于同号;这里是异号,原码没有定义该怎么做,硬做就会得到错误的结果。

4.2 用反码算(对了,但有坑)

1111 1100 (-3 反码) + 0000 0010 ( 2 反码) ----------- 1111 1110 (-1 反码) ✅

这一次结果正确了。但换个数据就出岔子:

1111 1101 (-2 反码) + 0000 0010 ( 2 反码) ----------- 1 1111 1111 ----------- 1111 1111 ( 丢弃进位 → 这是 -0!) ❌

-2 + 2明明等于0,反码体系却给出-0。
虽然可以靠额外规则把1111 1111归一化成0000 0000,但每个结果都要检查是不是负零,硬件上又是一堆判断。

4.3 用补码算(一路到底,零特判)

1111 1101 (-3 补码) + 0000 0010 ( 2 补码) ----------- 1 1111 1111 ----------- 1111 1111 (丢掉最高位进位)

得到1111 1111,它是不是-1?把补码还原成原码验证:

补码 1111 1111 ↓ 符号位不变,数值位取反 → 反码 反码 1000 0000 ↓ 末位 +1 → 原码 原码 1000 0001 → 符号位 1,数值 0000001 = 1 → -1 ✅

对照一下-3自己(-3 补码 = 1111 1101,1111 1101 → 1000 0010 → 1000 0011✅),规律完全一致。

4.4 三种码结果对比

编码方式-3 + 2运算结果解读对不对
原码1000 0101-5❌ 异号相加无定义
反码1111 1110-1✅(但-2 + 2会得到-0)
补码1111 1111-1✅无特判

这才是补码的价值:不管加什么,相加 → 丢弃最高位进位 → 结果就是对的。

三种码列竖式对比(-3 + 2):

【原码】 【反码】 【补码】 1 0 0 0 0 0 1 1 (-3) 1 1 1 1 1 1 0 0 (-3) 1 1 1 1 1 1 0 1 (-3) +0 0 0 0 0 0 1 0 (+2) +0 0 0 0 0 0 1 0 (+2) +0 0 0 0 0 0 1 0 (+2) ──────────────── ──────────────── ──────────────── 1 0 0 0 0 1 0 1 → -5 ❌ 1 1 1 1 1 1 1 0 → -1 ✅ 1 1 1 1 1 1 1 1 1 (进位1丢弃) └──────────┘ 1 1 1 1 1 1 1 1 = -1 ✅

原码竖式把符号位当普通位相加,异号时直接算错;反码对但-2+2会出-0;
补码多出的进位直接丢弃,结果永远正确。


五、为什么补码"相加就一定对"?

补码的定义可以写成数学形式(这才是它的本质):

[x]补 = x + 2ⁿ (n = 位宽,x 为真值)

既然[x]补只是x平移了2ⁿ,那加减法在补码空间里就退化成模 2ⁿ 运算:

[x + y]补 = [x]补 + [y]补 (mod 2ⁿ)

而"丢弃最高位进位"正好就是mod 256。
所以它天然成立 ——补码的正确性不是巧合,是定义使然。

-128是怎么来的?

-128 真值 = -128 -128 补码 = -128 + 256 = 128 = 1000 0000

1000 0000在无符号视角是128,在补码视角是-128。
同一个 bit 序列解释出两个数,这就是补码"挤满 256 个状态"的体现,也是它比原码多表示一个-128的原因。

进位丢弃 = 模运算的直观证明

1111 1101 (-3) + 0000 0010 (+2) = 1 1111 1111

这个"多出来的 1"就是2⁸,减去它(等价于丢弃)就完成了mod 256。
丢弃进位不是"无奈之举",而是补码算法的一部分。


六、零与范围的对比(补码的"额外福利")

对比项原码反码补码
0的表示0000 0000和1000 0000两个0000 0000和1111 1111两个只有0000 0000一个✅
8 位范围-127 ~ 127-127 ~ 127-128 ~ 127✅
加减法是否需分情况必须分(同号/异号)分,且要处理负零完全不分✅
加法器电路多套多套 + 负零修正一套✅
是否需要额外校验位需要需要不需要✅
溢出判断麻烦麻烦简单(见下节)

补码零的唯一性带来的连锁好处:判断x == 0不需要同时比较两个 bit 模式;
变长整数(C/C++ 的char*手写解析、大小端转换)用补码时也不必为负零写特例。

补码溢出判断:只看两个符号位

两个同号数相加,结果符号位与它们不同→ 溢出。

0111 1111 (+127) 两个 0,符号位不变 → +127,无溢出 + 0000 0001 (+1) 1 1000 0000 → 模 256 后 1000 0000 = -128 ❌ 溢出

这就是为什么很多语言都提供「溢出即报错」的安全加法函数的原因。


七、现代写法:写代码时补码怎么出现

以下均用伪代码表达,与具体语言无关。主流语言的整数类型(定长有符号)一律采用补码,不存在原码/反码参与运算。

7.1 把整数展开成补码位串

// 把整数按 n 位补码展开成二进制串 toBinary(x, n): if x >= 0: return x 的 n 位二进制 // 正数补码即原码 else: return (2^n + x) 的 n 位二进制 // 负数补码 = x + 2^n toBinary(-3, 8) → "11111101" toBinary( 2, 8) → "00000010" (-3) + 2 → -1

7.2 位运算符是补码语义的直接体现

x = -8 // 补码 ...11111000 算术右移 x >> 1 → ...11111100 = -4 // 左侧补符号位 1 逻辑右移 x >>> 1 → 0111...1100 = 2147483644 // 左侧补 0

7.3 一个"教科书级"的分支无关绝对值

利用补码「负数右移得到全 1、正数右移得到全 0」的特性,可以不借助判断/分支求出绝对值:

// 负数时 (x XOR -1) - (-1) = ¬x + 1 = -x;正数时 = x abs(x) = (x XOR (x >> 位宽-1)) - (x >> 位宽-1)

同样利用补码,可以判断两数是否同号(符号位相同 → 异或后最高位为 0):

sameSign(a, b) = (a XOR b) >= 0 // 同号返回真;异号或含 0 需另判

7.4 补码回原码(按位操作)

// 以 8 位为例:负数补码 → 原码 = 取反 +1,再置回符号位 compToOrig(x): if x 的最高位 == 1: // 负数 return ( (NOT x + 1) AND 01111111 ) OR 10000000 return x

7.5 Kernighan 位技巧集(全部基于补码)

isPow2(x) = (x > 0) AND (x AND (x - 1) == 0) // 是否 2 的幂 clearLowest1(x) = x AND (x - 1) // 清除最低位的 1 popCount(x): // 统计 1 的个数 c = 0 while x != 0: x = x AND (x - 1) // 每次消掉一个 1 c = c + 1 return c same(a, b) = (a XOR b) == 0 // 两数是否完全相同 merge(lo, hi) = (lo AND 0xFFFF) OR ((hi AND 0xFFFF) << 16) // 合并高低 16 位

7.6 溢出安全的加减

// 加法前先检查是否会溢出,溢出则报错 addExact(a, b): result = a + b if 同号相加却得异号: // 溢出特征 error "overflow" return result

7.7 不同语言对溢出的处理差异

整数模型表示方式溢出行为坑
定长补码(C / Java / Go / Rust 等)补码,定长静默回绕(Rust 默认报错)溢出不报错,结果错误
C / C++补码(标准强制)有符号溢出属未定义行为编译器可能优化成任意结果
无限精度(如 Python)符号-数值,内部处理永不溢出负数不是补码,不能用bin(负数)验证补码

⚠️ 面试/实战高频陷阱:无限精度语言的负数不是补码存储。
它直接输出带符号的二进制(如-0b11),位运算时对负数"按位截取"才得到补码位串,
不要用这类语言去"验证补码"。

7.8 补码在工程里最常见的场景

场景补码如何参与
网络字节序大小端转换函数处理的就是补码 + 字节序的组合
TCP 校验和16 位按大端取补码求和,溢出即回绕
CRC / 哈希(CRC32)本质是 GF(2) 上的补码多项式运算
HMAC / 签名算法字节序、补码进位规则直接影响实现兼容
字节码指令局部变量索引和常量池索引都是补码整数
Base64 解码后的字节直接按无符号读,切勿误当补码字符
数据库 DECIMAL 转整数依赖十进制补码定义(不同厂商曾因此差 1)

八、常见误区速查

误区正解
“反码是符号位也取反”❌ 符号位始终不变,只翻数值位
“正数的补码要加 1”❌ 只有负数反码才 +1;正数三码全等
“补码要额外加一个符号位”❌ 符号位就在最高位里,8 位就是 8 位,不多不少
“负数相加要手动处理进位”❌ 最高位进位直接丢,这正是补码正确性的来源
“-3 + 2原码得到的结果是答案”❌ 原码异号相加无定义,会得到错误值;补码下答案恒为-1
“补码里 0 有两个”❌ 补码只有0000 0000一个零
“Python 的bin(-3)能验证补码”❌ Python 是符号-数值模型,不是补码
“位运算会把负数当无符号”❌ 位运算在补码语义下操作,Python 除外

九、总结

  1. 正数:原码 = 反码 = 补码;负数:反码 = 原码数值位取反,补码 = 反码 + 1。
  2. 补码 =x + 2ⁿ的模表示,所以[x+y]补 = [x]补 + [y]补 (mod 2ⁿ)—— 加法天然正确。
  3. 丢掉最高位进位不是 bug,而是mod 2ⁿ的实现,是补码算法的一部分。
  4. 补码带来三个工程红利:唯一零、多表示一个数(-128)、只需一套加法电路。
  5. -3 + 2:原码异号相加无定义、反码对但负零有坑、补码恒为-1且零特判。
  6. 写代码时:定长补码溢出通常是静默的,应优先使用「溢出即报错」的安全加法;分支无关绝对值((x XOR (x>>位宽-1)) - (x>>位宽-1))玩的就是补码;无限精度语言不是补码,别拿来验证。

一句话记忆

原码是"人话",反码是"半成品",补码是"CPU 的语言"。
正数三者相同,负数取反再加一,加法直接丢进位。


本文的换算过程建议动手敲一遍,尤其是-2 + 2(反码出-0)和127 + 1(补码溢出)这两个反例,比正面例子记得牢得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 10:11:01

智慧矿山建设规划方案:从121页WORD文档到可落地技术蓝图

简介&#xff1a;本资源为一份面向矿业企业管理者、信息化建设人员及智慧矿山方案设计者的完整规划文档&#xff0c;围绕云计算、物联网、大数据与移动互联网等技术&#xff0c;系统阐述矿山自动化、信息化、数字化与智能化的建设路径&#xff0c;可用于项目立项参考、方案汇报…

作者头像 李华
网站建设 2026/10/14 10:10:59

EtherCAT从站控制器LAN9252故障排查与日志打印实践

1. LAN9252故障排查与日志打印概述在工业自动化领域&#xff0c;EtherCAT从站控制器LAN9252与ZYNQ平台的集成应用中&#xff0c;状态机跳转异常和PDO数据交互问题是最常见的故障场景。通过系统性地添加日志打印&#xff0c;我们可以快速定位从站初始化、状态切换和数据传输过程…

作者头像 李华
网站建设 2026/10/14 10:10:58

Agent技能封装实战:从工具调用到稳定技能链的完整设计指南

在折腾了大半年Agent开发之后&#xff0c;我越来越觉得&#xff1a;真正拉开智能体能力差距的&#xff0c;不是模型选得多强&#xff0c;也不是提示词写得有多华丽&#xff0c;而是你往它身上挂了多少靠谱的"技能"。这个感悟&#xff0c;正好和"agent-skills&qu…

作者头像 李华
网站建设 2026/10/14 10:10:37

两阶段鲁棒优化在微电网容量配置中的应用与实践

1. 两阶段鲁棒优化算法在微网容量配置中的应用解析 微电网作为分布式能源系统的重要实现形式&#xff0c;其电源容量配置直接影响系统经济性和可靠性。传统确定性优化方法难以应对风光出力波动和负荷变化带来的不确定性&#xff0c;而两阶段鲁棒优化&#xff08;Two-Stage Robu…

作者头像 李华
网站建设 2026/10/14 10:10:26

Django+深度神经网络:教学评价系统与可视化大屏实战

做一个教学评价系统不难&#xff0c;但把它做成一个“有灵魂”的教学评价系统&#xff0c;就很难。所谓“有灵魂”&#xff0c;我指的是两层意思。第一层&#xff0c;评价数据不能被论坛投票式的打分限制住&#xff0c;学生提交的上千条开放评语里&#xff0c;藏着“课程太难”…

作者头像 李华
网站建设 2026/10/14 10:09:48

知识工作插件化:构建采集、整理、语义检索与生成自动化管线

这几年做知识管理相关的工作&#xff0c;我越来越感受到一个尴尬的现实&#xff1a;我们的知识生产力工具足够多&#xff0c;但知识工作本身的效率并没有跟上。资料疯狂涌入之后&#xff0c;真正能被调用、被复用的&#xff0c;往往只有极少一部分。最近我把手头这套东西整理成…

作者头像 李华