JVM——(20)字节码指令集与解析二(算数指令)

往期文章

JVM——(1)为什么学习虚拟机
JVM——(2)聊聊JVM虚拟机
JVM——(3)类加载子系统
JVM——(4)运行时数据区的概述与程序计数器(PC寄存器)
JVM——(5)运行时数据区的虚拟机栈
JVM——(6)运行时数据区的本地方法栈
JVM——(7)运行时数据区的堆空间
JVM——(8)运行时数据区的方法区
JVM——(9)对象的实例化与访问定位
JVM——(10)执行引擎
JVM——(11)String Table(字符串常量池)
JVM——(12)垃圾回收概述
JVM——(13)垃圾回收相关算法
JVM——(14)垃圾回收相关概念的概述
JVM——(15)垃圾回收器详细篇
JVM——(16)Class文件结构一(描述介绍)
JVM——(17)Class文件结构二(解读字节码)
JVM——(18)Class文件结构三(JAVAP指令)
JVM——(19)字节码指令集与解析一(局部变量压栈、常量变量压栈、出栈局部变量表指令)
JVM——(20)字节码指令集与解析二(算数指令)
JVM——(21)字节码指令集与解析三(类型转换指令)
JVM——(22)字节码指令集与解析四(对象创建与访问指令)
JVM——(23)字节码指令集与解析五(方法调用指令与方法返回指令)
JVM——(24)字节码指令集与解析六(操作数栈管理指令)

前言

前篇文章讲解了局部变量压栈指令、常量入栈指令以及出栈装入局部变量表指令,那么本篇文章接着讲解算数指令,让我们开始吧

一、算数指令概述

作用

================================

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈

分类

================================

大体上算术指令可以分为两种:对整型数据进行运算的指令与对浮点类型数据进行运算的指令。

byte、short、char和booleanl类型说明

================================

在每一大类中,都有针对Java虚拟机具体数据类型的专用算术指令。

但没有直接支持byte、short、char和boolean类型的算术指令,对于这些数据的运算都使用int类型的指令来处理。此外,在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理。
在这里插入图片描述

运算时的溢出

================================

数据运算可能会导致溢出,例如两个很大的正整数相加,结果可能是一个负数。

其实Java虚拟机规范并无明确规定过整型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中当出现除数为0时会导致虚拟机抛出异常ArithmeticException

运算模式

================================

向最接近数舍入模式:

JVM要求在进行浮点数计算时,所有的运算结果都必须舍入到适当的精度非精确结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,将优先选择最低有效位为零的

向零舍入模式:
将浮点数转换为整数时`采用该模式,该模式将在目标数值类型中`选择一个最接近但是不大于原值的数字作为最精确的舍入结果
NaN值使用

================================

当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NaN值来表示。而且所有使用NaN值作为操作数的算术操作,结果都会返回NaN;

接下来我们可以使用示例代码来体会一下NaN 和 除数抛出异常的示例代码

public class ArithmeticTest {
    @Test
    public void method1(){
        int i = 10;
        double j = i / 0;
        system.out.print1n(j);
    }
}

//输出结果如下:
java.lang.Arithmeticception:/ by zero
at com.atguigu.java.ArithmeticTest.method1(ArithmeticTest.java:15)<22 internal calls>

此时我们将除数0 改成double类型的0.0,在运行一下结果看看

public class ArithmeticTest {
    @Test
    public void method1(){
        int i = 10;
        double j = i / 0.0;
        system.out.print1n(j);//无穷大
        
        double d1 = 0.0; I
        double d2 = d1 / 0.0;
        system.out.println(d2);//NaN: not a number
    }
}

//输出结果如下:
Infinity
NaN

我们说除数与被除数相同的话,返回结果应该是1,但是由于分子也是0,所以无法确认具体数值

二、算数指令的所有运算指令

指令介绍

================================

  • 加法指令: iadd、ladd、fadd、dadd
  • 减法指令: isub、lsub、fsub、dsub
  • 乘法指令: imul、lmul、fmul、dmul
  • 除法指令: idiv、ldiv、fdiv、ddiv
  • 求余指令: irem、lrem、frem、drem //remainder:余数
  • 取反指令: ineg、lneg、fneg、dneg //negation:取反
  • 自增指令: iinc
  • 位运算(位移指令): ishl、 ishr、 iushr、lsh1、lshr、lushr
  • 位运算(按位或指令): ior、lor
  • 位运算(按位与指令): iand、land
  • 位运算(按位异或指令): ixor、lxor
  • 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

接下来采用示例代码来演示一下上面提到的指令

public class ArithmeticTest {
    
    public void method2(){
        f1oat i = 10;
        float j = -i;
        i = -j;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述
前面我们提到过float类型的范围数值是0-2,若超出可以采用ldc指令压操作数栈
在这里插入图片描述
同时将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,针对float类型使用fload_1
在这里插入图片描述
此时我们进行运算操作,将局部变量表索引为:1 取出来进行操作
在这里插入图片描述
当我们操作数进行取反操作后,此时将操作完的结果压入局部变量表新的位置上
在这里插入图片描述
此时我们发现代码还有进行取反的操作,所以还是与之前一致并重新压入局部变量表
在这里插入图片描述
接下来再使用第二个示例代码来演示一下上面提到的指令

public class ArithmeticTest {
    public void method3(int j){
        int i = 100;
        i = i + 10;
    }
}

前面我们提到过int类型的范围数值由不同的范围,具体范围有具体的指令进行操作
在这里插入图片描述
下面我们进行压入局部变量表的的指令,看看是怎么回事
在这里插入图片描述
与上面同样,我们需要弹出进行运算并再次将结果放入原局部变量表的索引位置
在这里插入图片描述
若我们修改一下示例代码,采用+=的方式进行增加操作看看具体的字节码会是什么?

public class ArithmeticTest {
    public void method3(int j){
        int i = 100;
        i += 10;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述
根据字节码我们进行分析看看,具体+=的方式做了什么指令?
在这里插入图片描述
接下来再使用第四个示例代码来演示一下上面提到的指令

public class ArithmeticTest {
    public int method4(){
        int a = 80;
        int b = 7;int c = 10;
        return (a + b)*c;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述
类似的指令我们就不再进行重复讲解了,我们关注具体的操作数指令
在这里插入图片描述
接下来再使用第五个示例代码来演示一下上面提到的指令

public class ArithmeticTest {
    public int method5(int i ,int j){
        return ( (i + j - 1) &~(j - 1));
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述
类似的指令我们就不再进行重复讲解了,我们关注具体的操作数指令
在这里插入图片描述
在这里插入图片描述
接下来我们使用示例代码,从字节码角度来演示一下++i

public class ArithmeticTest {
    //关于(前)++和(后)++
    public void method6(){
        int i = 10;
        ++i;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述
接下来我们使用示例代码,从字节码角度来演示一下i++

public class ArithmeticTest {
    //关于(前)++和(后)++
    public void method6(){
        int i = 10;
        i++;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述

我们发现没有涉及其他运算符操作的时候,他们都是一样的字节码,这时我们运用起来再看看

public class ArithmeticTest {
    
    public void method7(){
        int i = 10;
        int a = i++;
        int j = 20;
        int b = ++j;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?
在这里插入图片描述在这里插入图片描述在这里插入图片描述

三、算数指令的比较指令


比较指令的说明

================================

比较指令的作用是比较栈顶两个元素的大小,并将比较结果入栈

比较指令有: dcmpg,dcmpl、fcmpg、fcmpl、lcmp

与前面讲解的指令类似,首字符d表示double类型,f表示float,l表示long

对于double和float类型的数字,由于NaN的存在,各有两个版本的比较指令

以float为例有fcmpg和fcmpl两个指令,区别在于在数字比较时若遇到NaN值处理结果不同

指令dcmpl和dcmpg也是类似的,根据其命名可以推测其含义,在此不再赘述

指令lcmp针对long型整数,由于long型整数没有NaN值,故无需准备两套指令

举例比较

================================

指令fcmpg和fcmpl都从栈中弹出两个操作数,并将它们做比较

设栈顶的元素为v2,核顶顺位第2位的元素为v1

  • 若v1=v2则压入0
  • 若v1>v2则压入1
  • 若v1<v2则压入1

两个指令的不同之处在于,如果遇到NaN值,fcmpg会压入1,而fcmpl会压入-1

下面是本人的公众号:(有兴趣可以扫一下,文章会同步过去)
在这里插入图片描述

我是小白弟弟,一个在互联网行业的小白,立志成为一名架构师
https://blog.csdn.net/zhouhengzhe?t=1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhz小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值