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

计算机基础系列文章

计算机基础(一):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,程序直接运算❌

      • 原码的加法不支持符号修正(不能自动让结果的符号与数值正确对应)

        java 复制代码
            0000 0101  (+5)
          + 1000 0101  (-5)
          -----------------
            1000 1010  结果为 -10
      • 示例2:(-5) + (+3) = -2,程序直接运算❌

        java 复制代码
            1000 0101  (-5)
          + 0000 0011  (+3)
          -----------------
            1000 1000  结果为 -8
      • 示例3:(-5) + (-5) = -10,程序直接运算❌

      • 原码的加法不支持进位消除(多出的进位被丢弃,不影响结果)

        java 复制代码
            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,程序直接运算✅

      • 反码的加法在数值上可以看作正确,但从表达角度来看,有点不完美

        java 复制代码
            0000 0101  (+5)
          + 1111 1010  (-5)
          -----------------
            1111 1111  结果是反码,符号位不变其他按位取反,原码就是1000 0000也就是-0(负零)
      • 示例2:(-5) + (+3) = -2,程序直接运行✅

        java 复制代码
            1111 1010  (-5)
          + 0000 0011  (+3)
          -----------------
            1111 1101  结果是反码,原码为1000 0010也就是-2
      • 示例3:(-5) + (-5) = -10,程序直接运行❌

      • 反码的加法需要循环进位

        java 复制代码
            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,程序直接运算✅

      • 补码加法支持进位消除(多出的进位被丢弃,不影响结果)

        java 复制代码
            0000 0101  (+5)
          + 1111 1011  (-5)
          -----------------
          1 0000 0001  结果是补码,先减1获得反码0000 0000,也就是0
      • 示例2:(-5) + (+3) = -2,程序直接运算✅

        java 复制代码
            1111 1011  (-5)
          + 0000 0011  (+3)
          -----------------
            1111 1110  结果是补码,先减1获得反码1111 1101,符号位不变其他按位取反获取原码1000 0010,也就是-2
      • 示例3:(-5) + (-5) = -10,程序直接运算✅

        java 复制代码
            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个字节
java 复制代码
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() 可查看补码形式
相关推荐
小白学大数据3 分钟前
实战:Python爬虫如何模拟登录与维持会话状态
开发语言·爬虫·python
一念&5 分钟前
每日一个C语言知识:C 结构体
c语言·开发语言
Chen-Edward12 分钟前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
锦***林43 分钟前
用 Python 写一个自动化办公小助手
开发语言·python·自动化
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July2 小时前
Hikari连接池
java
微风粼粼2 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud