前言

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

一、算数指令概述


作用

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

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

分类

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

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

byte、short、char和booleanl类型说明

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

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

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

image.png

运算时的溢出

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

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

其实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;
    }
}

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

前面我们提到过float类型的范围数值是0-2,若超出可以采用ldc指令压操作数栈

image.png

同时将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,针对float类型使用fload_1

image.png

此时我们进行运算操作,将局部变量表索引为:1 取出来进行操作

image.png

当我们操作数进行取反操作后,此时将操作完的结果压入局部变量表新的位置上
image.png

此时我们发现代码还有进行取反的操作,所以还是与之前一致并重新压入局部变量表

image.png

接下来再使用第二个示例代码来演示一下上面提到的指令

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

前面我们提到过int类型的范围数值由不同的范围,具体范围有具体的指令进行操作

image.png

下面我们进行压入局部变量表的的指令,看看是怎么回事

image.png

与上面同样,我们需要弹出进行运算并再次将结果放入原局部变量表的索引位置

image.png

若我们修改一下示例代码,采用+=的方式进行增加操作看看具体的字节码会是什么?

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

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

根据字节码我们进行分析看看,具体+=的方式做了什么指令?

image.png

接下来再使用第四个示例代码来演示一下上面提到的指令

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

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

类似的指令我们就不再进行重复讲解了,我们关注具体的操作数指令

image.png

接下来再使用第五个示例代码来演示一下上面提到的指令

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

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

类似的指令我们就不再进行重复讲解了,我们关注具体的操作数指令

image.png
image.png

接下来我们使用示例代码,从字节码角度来演示一下++i

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

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

接下来我们使用示例代码,从字节码角度来演示一下i++

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

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

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

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

接下来我们编译代码使用插件查看具体的字节码指令是怎么样的?

image.png

image.png
image.png

 三、算数指令的比较指令


比较指令的说明

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

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

比较指令有: 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


28640
116 声望25 粉丝

心有多大,舞台就有多大