代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。
数学指令大致对应Java语言中的加、减、乘、除等数学运算符。
数学指令包括算术指令、位移指令和布尔运算指令等。
1.算术指令
1.作用:
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。
注意与其他指令区别

2.分类:
大体上算术指令可以分为两种:对整型数据进行运算的指令与对浮点类型数据进行运算的指令。
3.byte、short、char和boolean类型说明:
在每一大类中,都有针对Java虚拟机具体数据类型的专用算术指令。但没有直接支持byte、 short、char和boolean类型的算术指令,对于这些数据的运算,都使用int类型的指令来处理。此外,在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理。

4.运算时的溢出
数据运算可能会导致溢出,例如两个很大的正整数相加,结果可能是一个负数。其实Java虚拟机规范并无明确规定过整型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中当出现除数为0时会导致虚拟机抛出异常ArithmeticException。
5.运算模式
- 向最接近数舍入模式: JVM要求在进行浮点数计算时,所有的运算结果都必须舍入到适当的精度,非精确结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,将优先选择最低有效位为零的;
- 向零舍入模式:将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果;
6.NaN值使用
当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NaN值来表示。而且所有使用NaN值作为操作数的算术操作,结果都会返回NaN;
算术指令又可以进一步分为加法(add)指令、减法(sub)指令、乘法(mul)指令、除法(div)指令、求余(rem)指令和取反(neg)指令6种。
整体代码
在instructions\math目录下创建add.go文件
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Add double
type DADD struct{ base.NoOperandsInstruction }
func (self *DADD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v1 := stack.PopDouble()
v2 := stack.PopDouble()
result := v1 + v2
stack.PushDouble(result)
}
// Add float
type FADD struct{ base.NoOperandsInstruction }
func (self *FADD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopFloat()
v1 := stack.PopFloat()
result := v1 + v2
stack.PushFloat(result)
}
// Add int
type IADD struct{ base.NoOperandsInstruction }
func (self *IADD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
result := v1 + v2
stack.PushInt(result)
}
// Add long
type LADD struct{ base.NoOperandsInstruction }
func (self *LADD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
result := v1 + v2
stack.PushLong(result)
}在instructions\math目录下创建div.go文件
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Divide double
type DDIV struct{ base.NoOperandsInstruction }
func (self *DDIV) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopDouble()
v1 := stack.PopDouble()
result := v1 / v2
stack.PushDouble(result)
}
// Divide float
type FDIV struct{ base.NoOperandsInstruction }
func (self *FDIV) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopFloat()
v1 := stack.PopFloat()
result := v1 / v2
stack.PushFloat(result)
}
// Divide int
type IDIV struct{ base.NoOperandsInstruction }
func (self *IDIV) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
if v2 == 0 {
panic("java.lang.ArithmeticException: / by zero")
}
result := v1 / v2
stack.PushInt(result)
}
// Divide long
type LDIV struct{ base.NoOperandsInstruction }
func (self *LDIV) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
if v2 == 0 {
panic("java.lang.ArithmeticException: / by zero")
}
result := v1 / v2
stack.PushLong(result)
}在instructions\math目录下创建mul.go文件
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Multiply double
type DMUL struct{ base.NoOperandsInstruction }
func (self *DMUL) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopDouble()
v1 := stack.PopDouble()
result := v1 * v2
stack.PushDouble(result)
}
// Multiply float
type FMUL struct{ base.NoOperandsInstruction }
func (self *FMUL) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopFloat()
v1 := stack.PopFloat()
result := v1 * v2
stack.PushFloat(result)
}
// Multiply int
type IMUL struct{ base.NoOperandsInstruction }
func (self *IMUL) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
result := v1 * v2
stack.PushInt(result)
}
// Multiply long
type LMUL struct{ base.NoOperandsInstruction }
func (self *LMUL) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
result := v1 * v2
stack.PushLong(result)
}
在instructions\math目录下创建neg.go文件
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Negate double
type DNEG struct{ base.NoOperandsInstruction }
func (self *DNEG) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopDouble()
stack.PushDouble(-val)
}
// Negate float
type FNEG struct{ base.NoOperandsInstruction }
func (self *FNEG) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopFloat()
stack.PushFloat(-val)
}
// Negate int
type INEG struct{ base.NoOperandsInstruction }
func (self *INEG) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopInt()
stack.PushInt(-val)
}
// Negate long
type LNEG struct{ base.NoOperandsInstruction }
func (self *LNEG) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopLong()
stack.PushLong(-val)
}
在instructions\math目录下创建sub.go文件
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Subtract double
type DSUB struct{ base.NoOperandsInstruction }
func (self *DSUB) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopDouble()
v1 := stack.PopDouble()
result := v1 - v2
stack.PushDouble(result)
}
// Subtract float
type FSUB struct{ base.NoOperandsInstruction }
func (self *FSUB) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopFloat()
v1 := stack.PopFloat()
result := v1 - v2
stack.PushFloat(result)
}
// Subtract int
type ISUB struct{ base.NoOperandsInstruction }
func (self *ISUB) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
result := v1 - v2
stack.PushInt(result)
}
// Subtract long
type LSUB struct{ base.NoOperandsInstruction }
func (self *LSUB) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
result := v1 - v2
stack.PushLong(result)
}
在instructions\math目录下创建rem.go文件,在其中定义4条求余指令,代码如下:
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
"math"
)
// Remainder double
type DREM struct{ base.NoOperandsInstruction }
func (self *DREM) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopDouble()
v1 := stack.PopDouble()
result := math.Mod(v1, v2) // todo
stack.PushDouble(result)
}
// Remainder float
type FREM struct{ base.NoOperandsInstruction }
func (self *FREM) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopFloat()
v1 := stack.PopFloat()
result := float32(math.Mod(float64(v1), float64(v2))) // todo
stack.PushFloat(result)
}
// Remainder int
type IREM struct{ base.NoOperandsInstruction }
func (self *IREM) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
if v2 == 0 {
panic("java.lang.ArithmeticException: / by zero")
}
result := v1 % v2
stack.PushInt(result)
}
// Remainder long
type LREM struct{ base.NoOperandsInstruction }
func (self *LREM) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
if v2 == 0 {
panic("java.lang.ArithmeticException: / by zero")
}
result := v1 % v2
stack.PushLong(result)
}
代码解释
irem和lrem代码差不多,以irem为例,先从操作数栈中弹出两个int变量,求余,然后把结果推入操作数栈。这里注意一点,对int或long变量做除法和求余运算时,是有可能抛出ArithmeticException异常的。其Execute()方法如下:

frem和drem指令差不多,其Execute()方法如下:

Go语言没有给浮点数类型定义求余操作符,所以需要使用math包的Mod()函数。另外,浮点数类型因为有Infinity(无穷大)值,所以即使是除零,也不会导致ArithmeticException异常抛出。
math包的Mod()函数用法:Mod返回x/y的浮点余数。结果的大小小于y,其符号与x的符号一致。

2.位移指令
位移指令可以分为左移和右移两种,右移指令又可以分为算术右移(有符号右移)和逻辑右移(无符号右移)两种。算术右移和逻辑位移的区别仅在于符号位的扩展,如下面的Java代码所示。
int x = -1; System.out.println(Integer.toBinaryString(x)); // 11111111111111111111111111111111 System.out.println(Integer.toBinaryString(x >> 8)); // 11111111111111111111111111111111 System.out.println(Integer.toBinaryString(x >>> 8)); // 00000000111111111111111111111111
整体代码
在instructions\math目录下创建sh.go文件,在其中定义6条位移指令,代码如下:
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Shift left int
type ISHL struct{ base.NoOperandsInstruction }
func (self *ISHL) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
s := uint32(v2) & 0x1f
result := v1 << s
stack.PushInt(result)
}
// Arithmetic shift right int
type ISHR struct{ base.NoOperandsInstruction }
func (self *ISHR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
s := uint32(v2) & 0x1f
result := v1 >> s
stack.PushInt(result)
}
// Logical shift right int
type IUSHR struct{ base.NoOperandsInstruction }
func (self *IUSHR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
s := uint32(v2) & 0x1f
result := int32(uint32(v1) >> s)
stack.PushInt(result)
}
// Shift left long
type LSHL struct{ base.NoOperandsInstruction }
func (self *LSHL) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopLong()
s := uint32(v2) & 0x3f
result := v1 << s
stack.PushLong(result)
}
// Arithmetic shift right long
type LSHR struct{ base.NoOperandsInstruction }
func (self *LSHR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopLong()
s := uint32(v2) & 0x3f
result := v1 >> s
stack.PushLong(result)
}
// Logical shift right long
type LUSHR struct{ base.NoOperandsInstruction }
func (self *LUSHR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopLong()
s := uint32(v2) & 0x3f
result := int64(uint64(v1) >> s)
stack.PushLong(result)
}
代码解释
左移指令比较简单,以ishl指令为例,先从操作数栈中弹出两个int变量v2和v1。v1是要进行位移操作的变量,v2指出要移位多少比特。位移之后,把结果推入操作数栈。这里注意两点:第一,int变量只有32位,所以只取v2的前5个比特(二进制11111转换十进制是31,也就是与0x1F就行)就足够表示位移位数了;第二,Go语言位移操作符右侧必须是无符号整数,所以需要对v2进行类型转换。其Execute()方法如下:

算术右移指令需要扩展符号位,代码和左移指令基本上差不多。以lshr指令为例,long变量只有64位,所以只取v2的前6个比特(二进制111111转换十进制是63,也就是与0x3F就行)就足够表示位移位数了,其Execute()方法如下:

最后以iushr为例,Go语言并没有Java语言中的>>>运算符,为了达到无符号位移的目的,需要先把v1转成无符号整数,位移操作之后,再转回有符号整数。

3.布尔运算指令
布尔运算指令只能操作int和long变量,分为按位与(and)、按位或(or)、按位异或(xor)3种。以按位与为例介绍布尔运算指令。
整体代码
在instructions\math目录下创建and.go文件,在其中定义iand和land指令,代码如下:
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Boolean AND int
type IAND struct{ base.NoOperandsInstruction }
func (self *IAND) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
result := v1 & v2
stack.PushInt(result)
}
// Boolean AND long
type LAND struct{ base.NoOperandsInstruction }
func (self *LAND) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
result := v1 & v2
stack.PushLong(result)
}
在instructions\math目录下创建or.go文件,代码如下:
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Boolean OR int
type IOR struct{ base.NoOperandsInstruction }
func (self *IOR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopInt()
v1 := stack.PopInt()
result := v1 | v2
stack.PushInt(result)
}
// Boolean OR long
type LOR struct{ base.NoOperandsInstruction }
func (self *LOR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v2 := stack.PopLong()
v1 := stack.PopLong()
result := v1 | v2
stack.PushLong(result)
}
在instructions\math目录下创建xor.go文件,代码如下:
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Boolean XOR int
type IXOR struct{ base.NoOperandsInstruction }
func (self *IXOR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v1 := stack.PopInt()
v2 := stack.PopInt()
result := v1 ^ v2
stack.PushInt(result)
}
// Boolean XOR long
type LXOR struct{ base.NoOperandsInstruction }
func (self *LXOR) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
v1 := stack.PopLong()
v2 := stack.PopLong()
result := v1 ^ v2
stack.PushLong(result)
}代码比较简单,就不多解释了。
4.iinc指令
iinc指令给局部变量表中的int变量增加常量值,局部变量表索引和常量值都由指令的操作数提供。
整体代码
在instructions\math目录下创建iinc.go文件,在其中定义iinc指令,代码如下:
package math
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Increment local variable by constant
type IINC struct {
Index uint
Const int32
}
func (self *IINC) FetchOperands(reader *base.BytecodeReader) {
self.Index = uint(reader.ReadUint8())
self.Const = int32(reader.ReadInt8())
}
func (self *IINC) Execute(frame *rtda.Frame) {
localVars := frame.LocalVars()
val := localVars.GetInt(self.Index)
val += self.Const
localVars.SetInt(self.Index, val)
}代码解释
FetchOperands()函数从字节码里读取操作数:

Execute()方法从局部变量表中读取变量,给它加上常量值,再把结果写回局部变量表,代码如下:

5.参考
尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ
周志明:深入理解java虚拟机
张秀宏:自己动手写Java虚拟机 (Java核心技术系列)


