3.7运算符

      数学运算是计算机的基本用途之一,Java提供了非常丰富的运算符来支持。我们根据运算的特点和性质,把运算符划分为几组:基本算数运算符、自增自减运算符、关系运算符、位运算符、逻辑运算符、赋值运算符、其他运算符。下面分别介绍。

3.7.1基本算数运算符

       在Java中,采用+、-、*、/、%来表示加、减、乘、除、取余(取模),这种运算小学就学过,无需多讲,列表举例如下:

运算非常简单,但是还是有一些问题需要注意,下面分别用实例来说明。

3.7.1.1类型变化

  我们看一段代码:

 1 public static void main(String[] args) {
 2         int a1 = 15;
 3         double a2 = 15;
 4         int b = 2;
 5         int c = 0;
 6         System.out.println("整数运算:");
 7         System.out.println("a1 + b = " + (a1 + b));
 8         System.out.println("a1 - b = " + (a1 - b));
 9         System.out.println("a1 * b = " + (a1 * b));
10         System.out.println("a1 / b = " + (a1 / b));
11         System.out.println("a1 % b = " + (a1 % b));
12         System.out.println("浮点数运算:");
13         System.out.println("a2 + b = " + (a2 + b));
14         System.out.println("a2 - b = " + (a2 - b));
15         System.out.println("a2 * b = " + (a2 * b));
16         System.out.println("a2 / b = " + (a2 / b));
17         System.out.println("a2 % b = " + (a2 % b));
18     } 
 

运行结果为:

整数运算:
a1 + b = 17
a1 - b = 13
a1 * b = 30
a1 / b = 7
a1 % b = 1
浮点数运算:
a2 + b = 17.0
a2 - b = 13.0
a2 * b = 30.0
a2 / b = 7.5
a2 % b = 1.0  
 

我们看到,整数15/2=7,而浮点数15/7=7.5。在Java中,参与运算的2个数有浮点数时,就会自动将非浮点数变成浮点数来运算。

下面为了节省篇幅,就不再分别列出代码和结果了。

3.7.1.2被0除问题

0.0 / 0 = NaN
1.0 / 0 = Infinity
-1.0 / 0 = -Infinity
1 / 0 =
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ch03.JibenYunsuanfu.main(JibenYunsuanfu.java:16) 

我们看到,浮点数0除以0,得到NaN;正负浮点数除以0得到正负无穷大;整数除以0会抛出异常。

3.7.1.3原码反码补码

       我们知道,Java的整型和浮点型都是有范围的,如果运算结果超过范围怎么办呢?我们知道int型的最大值是214783647,假如我们+1会得到什么结果呢?结果为:

2147483647 + 1 = -2147483648

说明这个问题原因之前,得先学习原码、反码、补码的相关知识。

3.7.1.3.1原码

  我们现实生活当中,可以用正负号来表示正负数,但是计算机中只有0和1,怎么表示正负数呢?于是想出了一个办法,对于固定字长n的二进制数,把2个数划分为正负数,把最高位规定为符号位,0代表正,1代表负,剩下的二进制数对应十进制数的绝对值。例如假设字长为3,那么一共表示8个数:

这种规定叫做“原码”,即3的原码是011,-3的原码是111。看起来很完美吧,但是有2个问题:

  • 0的表示不唯一
  • 无法将减法转换为加法

0的表示不唯一一目了然,为什么不能将减法转换为加法?我们看个例子:

2 - 1 = 2 + (-1) = 010 + 101 = 111 = -3(正确结果为1)

结果错误。那么又为什么要把减法转换为加法呢?我们学习过计算机组成,知道CPU中只有加法寄存器,因为计算机中处理加法比较简单,如果要直接处理减法,需要增加逻辑部件,而且处理减法有借位问题很麻烦。因此在计算机中用原码来进行运算和存储行不通。

3.7.1.3.2反码

       还有别的办法吗?人们又发明了“反码”。反码规定:正数的反码和原码一致,负数的反码为该数对应的绝对值的原码按位取反。假设字长为3,原码反码分别如下:

反码解决了减法转换为加法的问题,但是额外需要多一个规定,就是当发生溢出时,需要对最低位加1。我们看2个例子:

1 – 1 = 1 + (-1) = 001 + 110 = 111 =-0
2 - 1 = 2 + (-1) = 010 + 110 = 1000,溢出了,去掉溢出位后需再加1即000 + 001 = 001 = 1

我们看到,结果都正确。但是还是存在2个问题:

  • 0的表示不唯一
  • 减法转加法,需要判断溢出问题
3.7.1.3.3补码

继续探讨,于是出现“补码”。补码规定正数的补码和原码一致,负数的补码为该数对应的绝对值按位取反后加1(如果溢出丢弃最高位)

我们发现0的表示唯一了。另外用补码计算减法也很简单了,直接转换即可(溢出直接丢弃最高位),我们看2个例子:

1 – 1 = 1 + (-1) = 001 + 111 = 1000 = 000 = 0
2 - 1 = 2 + (-1) = 010 + 111 = 1001 = 001 = 1

喜欢钻牛角尖的同学就会问了,为什么使用补码就可以解决这些问题呢?有什么道理吗?我就知道你会问,还好我也恶补了这段知识,下面我们来研究一下。

3.7.1.3.4补码原理

       我们知道,对于一个3位的二进制,对应的十进制为0-7,一共8个。7+1=111+000=1000,去掉溢出位,又变成000即0。我们可以说这8个数字形成了一个闭环。这其实对应数学中的一个概念:模。

  模是指一个计量系统的计数范围,例如我们熟悉的时钟,它的计数范围是0-11,模是12。计算机也可以看成一个计量机器,因为计算机的字长是定长的,即存储和处理的位数是有限的,因此它也有一个计量范围,即都存在一个“模”。对于字长3位的机器来说,计数范围是0-7,模是8。“模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。任何有模的计量器,均可化减法为加法运算。

  我们以时钟为例:当前时间是2点,逆时针拨2格变成0点。顺时针拨10格也是0点。假设逆时针叫减,顺时针叫加,那么对于模12的系统里,减2和加10的效果一样。事实上,减3和加9,减4和加8效果也一样。我们把2和10、3和9、4和8互称为补数,特点就是二者相加等于模。因此在有模的系统里,减去一个数,可以变成加上它的补数,即可以把减法变成加法。

回到3位数的二进制如下图:

《Java从入门到失业》第三章:基础语法及基本程序结构(五):基本算数运算符(1)-LMLPHP

我们很容易就知道模为8,1和7、2和6、3和5、4和4他们互为补数。列一个表:

但是问题来了,3位二进制系统里,虽然减n可以变成加n,但是由于没有负数,因此计算减法,需要先计算减数的补数,例如减1,需要计算1的补数8-1。怎么办?聪明的你一定可以想到,补数都是成对的,我们把成对的补数中的一半规定为负数是不是就可以了?例如a-1=a+(-1)=a+7,假如我们规定7的二进制111代表-1,那么在计算的时候就没有减法了。同理我们还可以规定110代表-2,101代表-3。至于100是代表4还是-4,都可以,一般我们选择代表-4。这样一来,对于3位二进制系统,表示数的范围就变成-4~3,而所有的减法就变成加法了。而且这样一来我们还惊奇的发现:

  • 所有的正数最高位都是0,负数最高位都是1
  • 所有负数的二进制都是它所对应的绝对值的二进制按位取反后+1,就是补码

到此为止,我们就搞清楚了为什么在计算中要用补码来表示负数了。

  最后,我们回到开头的例子:

2147483647 + 1 = -2147483648 

现在回答这个问题太easy了。在Java中,一个数字如果不加后缀,默认就是int型的。我们知道int型占用4个字节,则int的系统是一个模为2的系统。然后采用补码规则存储,这样最大的正数是2-1=2147483647。这个数再加1就变成2。2的补数是它自己,但是由于2的二进制最高位是1,我们习惯把它规定为负数,即-2,因此就是-2147483648。

08-29 12:02