计算机基础(三):深入解析Java中的原码、反码、补码

发布于:2025-06-18 ⋅ 阅读:(14) ⋅ 点赞:(0)

计算机基础系列文章

计算机基础(一):ASCll、GB2312、GBK、Unicode、UTF-32、UTF-16、UTF-8深度解析

计算机基础(二):轻松理解二进制、八进制、十进制和十六进制

计算机基础(三):深入解析Java中的原码、反码、补码

引言

  在Java的世界里,我们每天都在与整数打交道:int age = 30;long balance = 1000000L;。但你是否思考过,这些数字在计算机内部的真实形态?理解原码、反码、补码不仅是计算机科学的基础,更是深入Java底层、避免隐蔽bug的关键。本文将带你彻底掌握这些二进制表示法的奥秘及其在Java中的实际应用。


一、 基础概念:三种编码的诞生背景

计算机只能处理二进制(0和1)。如何表示有符号整数(正数、负数)?工程师们设计了三种方案

1、原码

  • 定义:最高位表示符号位(0=正数,1=负数),其余位表示数值的绝对值
  • 示例 (8位byte为例):
    • +5原码:0000 0101 (符号位0,绝对值5)
    • -5原码:1000 0101 (符号位1,绝对值5)
  • 优点:人类直观,理解容易
  • 致命缺陷:
    • 存在两种零+0的原码为00000000-0的原码为10000000( 以一个字节长表示 ),浪费表示范围,逻辑上冗余
    • 加减运算复杂:正数和负数相加,数值部分实际上是相减,符号取决于绝对值大者的符号,硬件不能直接相加符号位和数值位
      • 示例1:(+5) + (-5) = 0,程序直接运算❌
      • 原码的加法不支持符号修正(不能自动让结果的符号与数值正确对应)
            0000 0101  (+5)
          + 1000 0101  (-5)
          -----------------
            1000 1010  结果为 -10
        
      • 示例2:(-5) + (+3) = -2,程序直接运算❌
            1000 0101  (-5)
          + 0000 0011  (+3)
          -----------------
            1000 1000  结果为 -8
        
      • 示例3:(-5) + (-5) = -10,程序直接运算❌
      • 原码的加法不支持进位消除(多出的进位被丢弃,不影响结果)
            1000 0101  (-5)
          + 1000 0101  (-5)
          -----------------
          1 0000 1010  结果为 10(注意:最高位溢出了1位舍弃)
        

2、反码

  • 定义:
    • 正数:反码 = 原码
    • 负数:反码 = 负数原码符号位不变,数值位按位取反,或者更简单点正数原码全部按位取反
  • 示例 (8位byte为例):
    • +5反码:0000 0101 (同原码)
    • --5反码:1111 1010 (-5的原码1000 0101按位取反,符号位不变或者+5原码全部按位取反)
  • 遗留问题:
    • 两种零依然存在+0反码为0000 0000-0反码1111 1111(+0的原码按位取反)
    • 循环进位:计算结果最高位有进位时,需要把进位“回加”到最低位(称为“末位加1”或“循环进位”),硬件实现仍不理想
      • 示例1:(+5) + (-5) = 0,程序直接运算✅
      • 反码的加法在数值上可以看作正确,但从表达角度来看,有点不完美
            0000 0101  (+5)
          + 1111 1010  (-5)
          -----------------
            1111 1111  结果是反码,符号位不变其他按位取反,原码就是1000 0000也就是-0(负零)
        
      • 示例2:(-5) + (+3) = -2,程序直接运行✅
            1111 1010  (-5)
          + 0000 0011  (+3)
          -----------------
            1111 1101  结果是反码,原码为1000 0010也就是-2
        
      • 示例3:(-5) + (-5) = -10,程序直接运行❌
      • 反码的加法需要循环进位
            1111 1010  (-5)
          + 1111 1010  (-5)
          -----------------
          1 1111 0100  结果是反码(注意:最高位溢出了1位舍弃),最高位进位:1(需循环加回最低位)
        
         	1111 0100
          +         1
          -----------------
            1111 0101 这里还是反码,原码为1000 1010也就是-10
        

3、补码 🏆 - Java的选择

  • 定义:
    • 正数:补码 = 原码
    • 负数:补码 = 反码 + 1
  • 示例 (8位byte为例):
    • +5补码:0000 0101
    • -5补码:+5的原码按位取反获得反码1111 1010,再加1获得补码1111 1011
  • 核心优势 (完美解决前两者问题):
    • 唯一的零:+0补码为0000 0000-0补码是+0的原码全部按位取反再加1得到还是0000 0000,溢出一位舍去
    • 减法变加法:A - B = A + (-B) 直接成立,无需额外判断符号位处理循环进位。硬件只需一套加法电路
      • 示例1:(+5) + (-5) = 0,程序直接运算✅
      • 补码加法支持进位消除(多出的进位被丢弃,不影响结果)
            0000 0101  (+5)
          + 1111 1011  (-5)
          -----------------
          1 0000 0001  结果是补码,先减1获得反码0000 0000,也就是0
        
      • 示例2:(-5) + (+3) = -2,程序直接运算✅
            1111 1011  (-5)
          + 0000 0011  (+3)
          -----------------
            1111 1110  结果是补码,先减1获得反码1111 1101,符号位不变其他按位取反获取原码1000 0010,也就是-2
        
      • 示例3:(-5) + (-5) = -10,程序直接运算✅
            1111 1011  (-5)
          + 1111 1011  (-5)
          -----------------
          1 1111 0110  结果为是补码,先减1获得反码1111 0101,符号位不变其他按位取反获得原码1000 1010,也就是-10
        

二、 Java的坚定选择:补码一统天下

1、为什么Java整数表示使用补码?

  • 补码解决了原码和反码的固有问题(双零复杂运算),简化了CPU硬件设计,提高了运算效率
    1. 最大优势正数和负数的加法、减法可以统一处理(解决痛点:原码需要判断正负)
    2. 补码中只有一个 0(解决痛点:原码和反码有+0和-0,也需要单独判断)
    3. 补码支持进位消除(解决痛点:符号参加运行,多出的进位丢弃,不影响结果,反码需要循环进位)
  • Java的所有整数类型(byte, short, int, long)均使用补码表示!这是现代计算机体系结构的标准

2、为什么byte的范围是-128到127,而不是-127到127?

8 位二进制的表示能力

  • Byte 类型占用 8 位(1 字节)存储空间,共有2^8 = 256种可能的二进制组合
  • 在补码体系中,最高位为符号位(0 正 1 负),剩余 7 位表示数值

补码表示法的规则

  • 正数和零:补码与原码相同,范围是 0000 0000(0)到 0111 1111(127),共 128 个值
  • 负数:补码 = 原码取反 + 1,范围是 1000 0001(-127)到 1111 1111(-1),占 127 个值
  • 关键点:1000 0000 被定义为 -128
二进制(补码) 十进制值
1000 0000 -128
1000 0001 -127
1000 0010 -126
...
1111 1110 -2
1111 1111 -1
0000 0000 0
0000 0001 1
0000 0010 2
...
0111 1110 126
0111 1111 127

为何不是 -127 到 127?

  • 若范围设为 -127 到 127(含 0),仅能表示 127 + 128 = 255 127 + 128 = 255 127+128=255 个值,无法覆盖全部 256 种组合
  • 补码的连续性要求:将 1000 0000 分配给 -128 后:
    • 数值序列形成闭环:127(0111 1111)+1 溢出为 -128(1000 0000),实现连续循环
    • 若不这样设计,会浪费一个二进制组合(1000 0000),且破坏数值连续性,-127(1000 0001)-1正好是-128(1000 0000

编程语言中的实际表现

  • Byte.MAX_VALUE = 127,Byte.MIN_VALUE = -128
  • 赋值超出范围(如 byte b = 128;)会触发编译错误,如127 + 1 = -128(因 0111 1111 + 1 = 1000 0000

三、 眼见为实:Java代码验证补码

  • Integer.toBinaryString(int i) 方法会返回一个整数补码表示的字符串(省略前导零,负数显示完整的32位,int占4个字节
public class ComplementDemo {
    public static void main(String[] args) {
        int positive = 5;
        int negative = -5;

        // 打印正数5的二进制(补码,省略前导零)
        System.out.println(Integer.toBinaryString(positive)); // 输出: 101

        // 打印负数-5的二进制(32位完整补码)
        System.out.println(Integer.toBinaryString(negative)); // 输出: 1111 1111 1111 1111 1111 1111 1111 1011
    }
}

解读负数输出:

  • 11111111111111111111111111111011 就是 -5 的 32 位补码
  • 它是由 +5 (00000000_00000000_00000000_00000101) 按位取反 (11111111_11111111_11111111_11111010)
  • 再加 1 得到的 (11111111_11111111_11111111_11111011)

总结

  • 原码:直观但有双零,运算复杂(历史概念)
  • 反码:试图改进运算,仍有双零和循环进位问题(历史概念)
  • 补码 (Java的选择):统一零表示,完美支持 A - B = A + (-B),硬件实现高效简单,是现代计算机整数表示的标准
  • Java实践:byte, short, int, long 均用补码。Integer.toBinaryString() 可查看补码形式

网站公告

今日签到

点亮在社区的每一天
去签到