數值操作


在Scala中,所謂的運算子其實是方法,可以在類別上定義這些方法以實現相關操作。在這邊,以基本的一些型態來示範這些運算子的基本意涵。

算術操作如+、-、*、/、%,基本上與我們學過的加減乘除一樣,也是先乘除後加減,必要時加上括號表示運算的先後順序,例如這個程式碼會在主控台顯示7:
println(1 + 2 * 3)


同樣的,你也可以使用括號來指定運算順序,例如(1+2)*3這樣的方式。接下來看看這段程式碼:
println(10 / 3)

 

這會印出3而不是3.3333...,小數點之後的部份被自動消去了,這是因為10是整數,而3也是整數,運算出來的程式被自動轉換為整數 了,為了解決這個問題,可以使用下面的方法:
println(10.0 / 3)

當運算式中有不同長度的型態時,所有數值會提昇為長度最長的型態再進行運算,以上例來說,就是全部用Double來運算,結果就會是Double,同樣的,如果是1L+3,則結果會是Long。

在操作數值時,對於浮點數要留意一下,例如:
val x = 0.1
println((x + x + x) == 0.3)

很意外的,印出的結果會是false,這是因為Scala使用IEEE754浮點數演算(IEEE 754 floating-point arithmetic),其並非以小數(decimal)表示,而是以分數及指數表示(Java也是),所以:
0.5 = 1/2
0.75 = 1/2 + 1/4
0.875 = 1/2 + 1/4 + 1/8
0.1 = 1/16 + 1/32 + 1/256 + 1/512 + 1/4096 + 1/8192 + ...

所以,0.1並非是真正的0.1,而是分數模擬的結果,更多的例子,你可以參考 Some Things You Should Know About Floating-Point Arithmetic 一文。

所以避免直接對浮點數作比較操作,若你對浮點數的精確有所要求,你要使用scala.BigDecimal,例如:
val x = BigDecimal("0.1")
println((x + x + x) == 0.3)


關係操作如>、>=、<、<=、==、!=,比較的條件成立時以true表示,比較的條件不成立時 以false表示:
println("10 >  5  -> " + (10 > 5))
println("10 >= 5  -> " + (10 >= 5))
println("10 <  5  -> " + (10 < 5))
println("10 <= 5  -> " + (10 <= 5))
println("10 == 5  -> " + (10 == 5))
println("10 != 5  -> " + (10 != 5))

程式的執行如下所示:
10 >  5  -> true
10 >= 5  -> true
10 <  5  -> false
10 <= 5  -> false
10 == 5  -> false
10 != 5  -> true


在Scala中,==、!=是用來比較兩個物件內容值是否相同(這與Java不同,Java的==、!=是用來比較兩個參考是否為同一實例),例如Scala的List類別定義,若內含元素序列相同,則==傳回true:
println(List(1, 2, 3) == List(1, 2, 3))

如果你要比較兩個參考實際上是否為同一實例,可以使用eq方法或ne方法,例如以下傳回false:
println(List(1, 2, 3) eq List(1, 2, 3))

之後還會針對如何定義物件相等性作詳細介紹,不過有一點必須先注意,在Scala 中,==與!=也是個方法定義,是由 scala.Any(Scala的最頂層父類別)繼承下來,不過你不可以定義==與!=(因為它們被宣告為 final,你也不可以重新定義eq與ne,因為它們定義在 scala.AnyRef 中,也是被宣告為final),如果你要定義物件內容值是否相同,則要重新定義 equals()方法(與Java相同),因為Any中定義的==或!=方法,會呼叫equals()來檢查物件的相等性

邏輯操作如&&、||、!可用來
同時進行兩個以上的條件判斷,例如:
val number = 75
println(number > 70 && number < 80)
println(number > 80 || number < 75)
println(!(number > 80))

三段程式分別會輸出true、false與true三種狀況。&&、||有捷徑(short-circuited)運算的特性。因為&&只要有一個運算元為false,結果就會是false,所以如果&&的左運算元運算為false,就不會再去運算右運算元,直接傳回false。||則是只要有一個為true,結果就會是true,所以如果||的左運算元運算為true,就不會再去運算右運算元,直接傳回true(實際上,&&、||也是個方法,右運算元不是得運算過才可以作為引數傳入方法嗎?這是因為Scala可以使用 以名呼叫參數(By-name parameter) 的特性,來延遲或甚至不執行引數的運算)。

位元操作如&、|、^、~,用來執行數位設計上AND、OR、XOR與補數等運算,例如:
println("AND運算:")
println("0 AND 0\t\t" + (0 & 0))
println("0 AND 1\t\t" + (0 & 1))
println("1 AND 0\t\t" + (1 & 0))
println("1 AND 1\t\t" + (1 & 1))

println("\nOR運算:")
println("0 OR 0\t\t" + (0 | 0))
println("0 OR 1\t\t" + (0 | 1))
println("1 OR 0\t\t" + (1 | 0))
println("1 OR 1\t\t" + (1 | 1))

println("\nXOR運算:")
println("0 XOR 0\t\t" + (0 ^ 0))
println("0 XOR 1\t\t" + (0 ^ 1))
println("1 XOR 0\t\t" + (1 ^ 0))
println("1 XOR 1\t\t" + (1 ^ 1))

執行結果:
AND運算:
0 AND 0         0
0 AND 1         0
1 AND 0         0
1 AND 1         1
 
OR運算:
0 OR 0          0
0 OR 1          1
1 OR 0          1
1 OR 1          1
 
XOR運算:
0 XOR 0         0
0 XOR 1         1
1 XOR 0         1
1 XOR 1         0 


位元運算是逐位元運算的,例如10010001與01000001作AND運算,是一個一個位元對應運算,答案就是00000001;而補數 運算是將所有的位元0變1,1變0,例如00000001經補數運算就會變為11111110,例如下面這個程式所示:
val number: Byte = 0
println(~number)

這個程式會在主控台顯示-1,因為Byte佔記憶體一個位元組,它儲存的0在記憶體中是00000000,經補數運算就變成11111111,這在電腦中 用整數表示則是-1。

以下是個簡單的XOR字元加密例子:
val original = 'A'
println("before encoding:" + original) // 顯示A
println("after encoding:" + (original ^ 0x7).toChar) // 顯示F

將位元與1作XOR的作用其實就是位元反轉,0x7的最右邊三個位元為1,所以其實就是反 轉original的最後三個位元,同樣的,這個簡單的XOR字元加密,要解密也只要再進行相同的位元反轉就可以了。

在位元運算上,Scala還有左移(<<)右移(>>)兩個運算子,左移運算子會將所有的位元往左移指定的位 數,左邊被擠出去的位元會被丟棄,而右邊會補上0;右移運算則是相反,會將所有的位元往右移指定的位數,右邊被擠出去的位元會被丟棄,至於左邊位元補0或 補1 則視最左邊原來的位元而定,如果原來是0就補0,是1就補1,>>>運算子則是右移後一行在最左邊補上0。

可以使用左移運算來作簡單的2次方運算示範,如下所示:
val number = 1
println("2的1次: " + (number << 1))
println("2的2次: " + (number << 2))
println("2的3次: " + (number << 3))
println("2的4次: " + (number << 4))

執行結果:
2的1次: 2
2的2次: 4
2的3次: 8
2的4次: 16


實際來左移看看就知道為何可以如此運算了:
00000001 -> 1
00000010 -> 2
00000100 -> 4
00001000 -> 8

指定操作如+=、-=、*=、/=、%=、&=、|=、~=、>>=、>>>=、<<=,主要就是完成以下的工作:
運 算子 範 例 結 果
+= a += b a = a + b
-= a -= b a = a - b
*= a *= b a = a * b
/= a /= b a = a / b
%= a %= b a = a % b
&= a &= b a = a & b
|= a |= b a = a | b
^= a ^= b a = a ^ b
<<= a <<= b a = a << b
>>= a >>= b a = a >> b
>>>=a >>>= ba = a >>> b

視你所使用的物件是否為可變動的(Mutable),如果是不可變動(Immutable)物件,則上表操作方法左邊的參考名稱不能是val,而必須是var宣告,如果是可變動的,則左邊物件可以是val宣告(基本上就是看物件對上表方法是如何實作的)。