《Java從入門到失業》第三章:基礎語法及基本程式結構(五):基本算數運算子(1)

Java大失叔發表於2020-08-28

3.7運算子

      數學運算是計算機的基本用途之一,Java提供了非常豐富的運算子來支援。我們根據運算的特點和性質,把運算子劃分為幾組:基本算數運算子、自增自減運算子、關係運算子、位運算子、邏輯運算子、賦值運算子、其他運算子。下面分別介紹。

3.7.1基本算數運算子

       在Java中,採用+、-、*、/、%來表示加、減、乘、除、取餘(取模),這種運算小學就學過,無需多講,列表舉例如下:

運算

算式

結果(假設a=15,b=10)

加法

a+b

25

減法

a-b

5

乘法

a*b

150

除法

a/b

1

取餘

a%b

5

運算非常簡單,但是還是有一些問題需要注意,下面分別用例項來說明。

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的二進位制數,把2n個數劃分為正負數,把最高位規定為符號位,0代表正,1代表負,剩下的二進位制數對應十進位制數的絕對值。例如假設字長為3,那麼一共表示8個數:

十進位制

二進位制

3

011

2

010

1

001

0

000

-0

100

-1

101

-2

110

-3

111

這種規定叫做“原碼”,即3的原碼是011,-3的原碼是111。看起來很完美吧,但是有2個問題:

  • 0的表示不唯一
  • 無法將減法轉換為加法

0的表示不唯一一目瞭然,為什麼不能將減法轉換為加法?我們看個例子:

2 - 1 = 2 + (-1) = 010 + 101 = 111 = -3(正確結果為1)

結果錯誤。那麼又為什麼要把減法轉換為加法呢?我們學習過計算機組成,知道CPU中只有加法暫存器,因為計算機中處理加法比較簡單,如果要直接處理減法,需要增加邏輯部件,而且處理減法有借位問題很麻煩。因此在計算機中用原碼來進行運算和儲存行不通。

3.7.1.3.2反碼

       還有別的辦法嗎?人們又發明了“反碼”。反碼規定:正數的反碼和原碼一致,負數的反碼為該數對應的絕對值的原碼按位取反。假設字長為3,原碼反碼分別如下:

十進位制

原碼

反碼

3

011

011

2

010

010

1

001

001

0

000

000

-0

100

111

-1

101

110

-2

110

101

-3

111

100

反碼解決了減法轉換為加法的問題,但是額外需要多一個規定,就是當發生溢位時,需要對最低位加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(如果溢位丟棄最高位)

十進位制

原碼

反碼

補碼

3

011

011

011

2

010

010

010

1

001

001

001

0

000

000

000

-0

100

111

000

-1

101

110

111

-2

110

101

110

-3

111

100

101

我們發現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位數的二進位制如下圖:

我們很容易就知道模為8,1和7、2和6、3和5、4和4他們互為補數。列一個表:

減數

補數

1

7

2

6

3

5

4

4

5

3

6

2

7

1

但是問題來了,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的系統是一個模為232的系統。然後採用補碼規則儲存,這樣最大的正數是231-1=2147483647。這個數再加1就變成231。231的補數是它自己,但是由於231的二進位制最高位是1,我們習慣把它規定為負數,即-231,因此就是-2147483648。

相關文章