• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SOC/IP验证工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

负数的计算机中的存储方式

在当代计算机体系结构(包括ARM、x86等)中,负数几乎全部采用“补码”(Two's Complement)形式存储。

这是数字电路设计上最优解,因为它将减法运算统一为了加法运算,省去了昂贵的减法器硬件。

为了彻底讲透,我们从三种编码方案的对比,深入到补码的数学本质和硬件溢出规则。

1. 三种二进制编码方案对比(以4位为例)

十进制 原码 (Sign-Magnitude) 反码 (Ones' Complement) 补码 (Two's Complement)
+7 0111 0111 0111
+1 0001 0001 0001
+0 0000 0000 0000
-0 1000 1111 不存在
-1 1001 1110 1111
-7 1111 1000 1001
-8 无法表示 无法表示 1000

为什么淘汰了前两种?

  • 原码(符号位+数值位):存在“正零”和“负零”两个编码,浪费了状态;且做加法时符号位需要单独处理,电路极其复杂。
  • 反码(按位取反):虽然解决了部分加减问题,但依然存在“负零”(1111),且加法进位时需要“循环进位(End-around carry)”,增加延迟。
  • 补码(取反+1):消除了“负零”,使得 \(n\) 位二进制能表示的范围从 \([-(2^{n-1}-1), 2^{n-1}-1]\) 扩展到了 \([-2^{n-1}, 2^{n-1}-1]\)(例如4位可表示-8)。

2. 补码的数学本质:模运算(时钟原理)

补码的核心数学逻辑是模 \(2^n\) 同余。

对于一个 \(n\) 位的系统,任何数值 \(X\) 都会因为溢出而对 \(2^n\) 取模。负数的补码 \([X]_{补}\) 满足:

\[[X]_{补} = 2^n + X \quad (X < 0) \]

举个直观的“时钟”例子(4位,模16):
假设你站在数字 0000(0点)上。

  • 你想表示 -1,相当于顺时针往回走1格。往回走等价于向前走 15 格(因为 \(16-1=15\))。
  • 因此,-1 的补码就是 1111(即十进制的15)。
  • 同理,-2 对应 14(1110),-8 对应 8(1000)。

3. 补码的快速人工计算法

对于负数 \(X\),求其 \(n\) 位补码二进制:

  1. 写出 \(|X|\) 的二进制(原码)。
  2. 从右往左(从LSB到MSB)找第一个 '1'。
  3. 这个 '1' 不动,将这个 '1' 左边的所有位全部取反(0变1,1变0)。

示例:求 -6 的 4 位补码

  1. \(| -6 | = 6 = 0110\)。
  2. 从右往左看 0110,最低位是0(跳过),第二位是 1(找到)。
  3. 这个1保持不变,左边的 01 取反变为 10。
  4. 得到结果:1010。

验证公式:\(2^4 - 6 = 16 - 6 = 10 = 1010_2\)。


4. 硬件视角:补码为什么天然适配加法器?

CPU内部只有加法器(Adder),没有减法器。补码让减法 \(A - B\) 变成了 \(A + (\text{补码}(B))\)。

验算:\(1 - 1 = 0\)(4位)

  • 十进制:\(1 + (-1)\)
  • 二进制:0001 + 1111 = 10000。
  • 由于寄存器只有4位,最高位的 1 被自然溢出丢弃,最终结果为 0000,完美计算正确!

验算:\(-8 - 1 = -9\)(4位)

  • 二进制:\(1000 + 1111 = 10111\)。截取低4位为 0111(十进制+7)。
  • 发生了负溢出(Negative Overflow)。因为4位补码最小只能表示 -8,-9 超出了范围,所以结果错误。在硬件中,状态寄存器(PSR)的 V位(溢出标志位) 会被置1。

5. SystemVerilog / 硬件设计中的实战要点

既然你在学习SystemVerilog和ARM,这块细节直接关乎RTL代码的正确性:

  1. 显式声明 signed:

    logic signed [3:0] neg_val; // 声明为有符号
    neg_val = -4'sd6;           // 结果为 4'b1010 (-6)
    
  2. 位宽匹配陷阱(最高位扩展):
    当你将一个负数赋值给更大位宽的变量时,必须进行符号位扩展,而不是补零。

    • 错误:4'b1010 (-6) 赋给 8位 -> 补零变成 0000_1010 (+10),数值完全变了。
    • 正确:4'b1010 赋给 8位 -> 符号位扩展变成 1111_1010 (-6)。
    • SystemVerilog语法:使用 { {4{neg_val[3]}}, neg_val } 或直接赋值给 signed 变量(综合工具通常会自动扩展)。
  3. 无符号与有符号混合运算:
    在Verilog/SV中,只要操作数中有一个是无符号的,整个表达式就被视为无符号运算,这会导致负数被当作极大的正数处理。

    logic signed [3:0] a = -1;      // 1111
    logic [3:0] b = 1;              // 无符号
    logic [3:0] c;
    c = a + b; // 结果:1111 + 0001 = 10000 -> 截断为 0000 (0)
               // 但如果你期望的是 (-1 + 1 = 0),结果虽然是0,但中间加法器是按无符号处理的,
               // 在处理比较操作 (> <) 时会产生严重错误!
    

    切记:比较和运算时,务必保证两边类型一致,或使用 $signed() 强制转换。


总结

  • 存储形式:补码(取反加一)。
  • 核心优势:减法转加法,统一了加减法器,简化CPU设计。
  • 数学边界:\(n\) 位有符号数范围是 \(-2^{n-1}\) ~ \(2^{n-1}-1\)(多了一个最小负数)。
  • 硬件坑位:符号位扩展和有符号/无符号混合运算是RTL仿真中80%数值错误的根源,写代码时务必高度警惕。🚀

posted on 2026-07-05 11:41  SOC验证工程师  阅读(14)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3