前言
前篇文章解说了局部变量压栈指令、常量入栈指令以及出栈装入局部变量表指令,那么本篇文章接着解说算数指令,让咱们开始吧
一、算数指令概述
作用
================================
算术指令用于对两个操作数栈上的值进行某种特定运算
,并把后果从新压入操作数栈
分类
================================
大体上算术指令能够分为两种:对整型数据
进行运算的指令与对浮点类型
数据进行运算的指令。
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 zeroat 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 }}//输入后果如下:InfinityNaN
咱们说除数与被除数雷同的话,返回后果应该是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