共计 3694 个字符,预计需要花费 10 分钟才能阅读完成。
前言
前篇文章解说了局部变量压栈指令、常量入栈指令以及出栈装入局部变量表指令,那么本篇文章接着解说算数指令,让咱们开始吧
一、算数指令概述
作用
================================
算术指令用于对 两个操作数栈上的值进行某种特定运算
,并把 后果从新压入操作数栈
分类
================================
大体上算术指令能够分为两种: 对 整型数据
进行运算的指令与对 浮点类型
数据进行运算的指令。
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