算術演算子

しばらくPowerShell Coreの関数について取り上げてきた。これで変数、配列、ハッシュテーブル、列挙、制御構文、関数について取り上げたことになる。PowerShell Coreに関して最低限知るべきことはひととおり網羅したといった感じだ。これからはもう少し詳しくPowerShell Coreの機能を取り上げていく。今回は算術演算子だ。

これまでPowerShell Coreで使用できる算術演算子について、ほとんど説明もなくなんとなく使ってきた。

算術演算子というのは+、-、*、/のように算術時に使う記号のことだ。算術演算子は小学校の算数で習う四則演算(足し算[加算]、引き算[減算]、かけ算[乗算]、割り算[除算])が基本なので、説明しなくてもある程度はわかるだろう。

PowerShell Coreでは数値に対する四則演算のみならず、文字列や配列、ハッシュテーブル、オブジェクトといった対象に対しても四則演算を行える。

まずは、そうした演算を行うために利用する基本的な算術演算子とその意味を次にまとめておく。

算術演算子 対象 内容
+ 数値 数値の加算
+ 文字列 文字列の結合
+ 配列 配列の結合
+ ハッシュテーブル ハッシュテービルの結合
- 数値 数値を負数とする
- 数値 数値の減算
* 数値 数値の乗算
* 文字列と数値 文字列を指定した回数複製
* 文字列と配列 配列を指定した回数複製
/ 数値 数値の除算
% 数値 数値の剰余算
-band 整数 ビットAND
-bnot 整数 ビットNOT
-bor 整数 ビットOR
-bxor 整数 ビットXOR
-shl 整数 指定した数分左へビットシフト演算
-shr 整数 指定した数分右へビットシフト演算

数値に対する算術演算は算数の四則演算と同じだ。対象が文字列や配列、ハッシュテーブルになってくると意味がちょっとばかり違ってくる。また、プログラミング言語特有の演算としてビット演算がある点も覚えておいてほしい。

算術評価の優先順位

四則演算の評価順序は算数と同じだ。括弧で括った対象で、もっとも深くネストされた括弧の中から評価が行われる。負数にする-が次に優先で、そのあとに乗算と除算が評価され、最後に加算と減算が評価される。表にまとめると次のようになる。

優先順位 演算子 内容
1 ( ) 括弧
2 - 単項演算子としての-。負数とする
3 * / % 乗算、除算、剰余算
4 + - 加算、減算

括弧、乗算、除算、加算、減算の優先順位を確認する動作例は次のとおり。

括弧、乗算、除算、加算、減算の優先順位を確認する動作サンプル

PS /Users/daichi> 1+2
3
PS /Users/daichi> 1+2/2*3
4
PS /Users/daichi> 1+2/(2*3)
1.33333333333333
PS /Users/daichi> (1+2)/2*3
4.5
PS /Users/daichi>

最近主流で人気のあるプログラミング言語は多くがこれと同じ方式を取っているほか、算数や数学でのルールとも同じなのでこのあたりは、すぐに飲み込めるんだろう。

算術結果の丸め込み

整数を除算するなどして結果が実数になった場合、その結果をさらに整数にキャストすることがある。この場合、PowerShell Coreは次のように整数として近い方へ切り上げや切り捨てを実施する。

キャストによる丸め込み

PS /Users/daichi> [int](1/2)
0
PS /Users/daichi> [int](2/3)
1
PS /Users/daichi>

型の自動変換

PowerShell Coreでは算術演算の結果として型が自動的に変換されることがある。たとえば次のサンプルを見てもらうと、算術演算の結果として型が変わっているものがあることがわかると思う。

算術演算の結果としての型の変換

PS /Users/daichi> (1).GetType().FullName
System.Int32
PS /Users/daichi> (1.2).GetType().FullName
System.Double
PS /Users/daichi> ("str").GetType().FullName
System.String
PS /Users/daichi> (1GB).GetType().FullName
System.Int32
PS /Users/daichi> (1GB * 1GB).GetType().FullName
System.Double
PS /Users/daichi> ([int32]::minvalue + [uint32]::maxvalue).GetType().FullName
System.Int64
PS /Users/daichi>

インクリメント演算子++およびデクリメント演算子—と優先順位

プログラミング言語としては算術演算と代入、さらにインクリメント演算子++やデクリメント演算子—が使われた場合の優先順位が気になるところだ。インクリメント演算子++やデクリメント演算子—は指定された変数の値を1つ増やす、1つ減らすという演算子だが、その評価のタイミングに注意が必要だ。

たとえば次の例を見てみよう。

代入と後置形式インクリメント演算子

PS /Users/daichi> $a = 0
PS /Users/daichi> $b = @(1,2)
PS /Users/daichi> $c = @(-1,-2)
PS /Users/daichi>
PS /Users/daichi> $b[$a] = $c[$a++]
PS /Users/daichi>
PS /Users/daichi> $a
1
PS /Users/daichi> $b
1
-1
PS /Users/daichi> $c
-1
-2
PS /Users/daichi>

$b[$a] = $c[$a++]がどのように評価されるのかに注目してほしい。特に$aの中身だ。この記述は最終的に次のような評価になる。

  • $b[1] = $c[0]


$a++は後置形式のインクリメント演算子なので、$c[$a++]は$c[$a]として評価されたあとで$a = $a + 1が実行される。そのあとで$b[$a]が評価されるので$b[1]ということになる。これが前置形式のインクリメント演算子になると次のようになる。

代入と前置形式インクリメント演算子

PS /Users/daichi> $a = 0
PS /Users/daichi> $b = @(1,2)
PS /Users/daichi> $c = @(-1,-2)
PS /Users/daichi>
PS /Users/daichi> $b[$a] = $c[++$a]
PS /Users/daichi>
PS /Users/daichi> $a
1
PS /Users/daichi> $b
1
-2
PS /Users/daichi> $c
-1
-2
PS /Users/daichi>

今度は$b[$a] = $c[++$a]の部分だ。++$aは$a++と違って優先的に評価が行われるので、ここは$a = $a + 1が実行されたあとで$c[$a]となり、$c[1]ということになり。$b[$a]も当然$b[1]となる。つまりこの式は次のような評価になる。

  • $b[1] = $c[1]


インクリメント演算子やデクリメント演算子は繰り返し構文などでよく使われる演算子なので、その動作は理解しておきたい。

数値、文字列、配列、ハッシュテーブルの混在演算

PowerShell Coreでは型の異なるデータであっても特定の組み合わせは算術演算が可能になっている。

たとえば文字列に数値を加算すれば文字列連結として機能するし、配列に対する数値や文字列の加算は配列の末尾への要素追加として機能する。文字列に対して数値を乗算すれば、指定した数値の回数分だけ文字列をコピー連結するという意味になる。

文字列、数値、配列の演算サンプル

PS /Users/daichi> $array = 1,2,3
PS /Users/daichi>
PS /Users/daichi> "str" + 1
str1
PS /Users/daichi> $array + 1
1
2
3
1
PS /Users/daichi> $array + "str"
1
2
3
str
PS /Users/daichi> $array * 2
1
2
3
1
2
3
PS /Users/daichi> "str" * 3
strstrstr
PS /Users/daichi>

ただし、順序が重要だ。文字列に対して数値を加算しようとすれば、数値が文字列として結合される。しかし、逆に数値に対して文字列を加算しようとすると次のようにエラーになる。

文字列に数値を加算(結合)はできるが、数値に文字列を加算はできない

PS /Users/daichi> "str" + 1
str1
PS /Users/daichi> 1  + "str"
Cannot convert value "str" to type "System.Int32". Error: "Input string was not in a correct format."
At line:1 char:1
+ 1 + "str"
+ ~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastFromStringToInteger

PS /Users/daichi>

ハッシュテーブル同士も加算が可能だ。

たとえば次のようにハッシュテーブルを加算すると双方のすべての要素を含んだハッシュテーブルが生成される。

ハッシュテーブルの加算

PS /Users/daichi> $hash1 = @{a=1; b=2; c=3}
PS /Users/daichi> $hash2 = @{c1="str1"; c2="str2"}
PS /Users/daichi> $hash1 + $hash2

Name                           Value
----                           -----
c1                             str1
c                              3
a                              1
b                              2
c2                             str2


PS /Users/daichi>

ただし、ハッシュテーブルは加算されるそれぞれのハッシュテーブルがすべて異なる鍵を持っているということが前提となる。同じ鍵がひとつでも存在していると、次のように加算はエラーとなる。

ハッシュテーブルの加算はすべての鍵が異なる場合にのみ機能する

PS /Users/daichi> $hash1 = @{a=1; b=2; c=3}
PS /Users/daichi> $hash2 = @{c="str"; c1="str1"}
PS /Users/daichi> $hash1 + $hash2
Item has already been added.  Key in dictionary: c  Key being added: c
At line:1 char:1
+ $hash1 + $hash2
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException

PS /Users/daichi>

また次のようにハッシュテーブルに数値を加算しようとしたり、配列を加算しようとしてもエラーになる。

ハッシュテーブルには数値や配列を加算することはできない

PS /Users/daichi> $array = 1,2,3
PS /Users/daichi> $hash1 = @{a=1; b=2; c=3}
PS /Users/daichi> $hash1 + $array
A hash table can only be added to another hash table.
At line:1 char:1
+ $hash1 + $array
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : AddHashTableToNonHashTable

PS /Users/daichi>
PS /Users/daichi> $hash1 + 1
A hash table can only be added to another hash table.
At line:1 char:1
+ $hash1 + 1
+ ~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : AddHashTableToNonHashTable

PS /Users/daichi>

逆に、配列に対しては数値や亜種テーブルを加算することができる。たとえば次のように配列にハッシュテーブルを加算した場合、加算されたハッシュテーブルは配列の最後の要素となる。

配列には数値やハッシュテーブルを加算することができる

PS /Users/daichi> $array = 1,2,3
PS /Users/daichi> $hash1 = @{a=1; b=2; c=3}
PS /Users/daichi> $array + $hash1
1
2
3

Name                           Value
----                           -----
c                              3
a                              1
b                              2


PS /Users/daichi>

ただし、ハッシュテーブルや配列に対する加算は意味的に混乱するような気がするので、次のように+=を使って明示的に要素を追加する形でデータを加えていく方が書き方としては推奨されているようだ。

+=で配列に要素を追加しているサンプル

PS /Users/daichi> $array = @()
PS /Users/daichi> (0..9).foreach{ $array += $_ }
PS /Users/daichi> $array
0
1
2
3
4
5
6
7
8
9
PS /Users/daichi>

ビット演算

PowerShell Coreにはビット演算(シフト、AND、OR、XOR、NOT)を行うための演算子がひととおり用意されている(-shl、-shr、-band、-bor、-bxor、-bnot)。この演算子は数値を2進数で表現した場合にビットデータを移動させたり、ビット単位でAND、OR、XOR、NOTの演算を行うというもので、ビット演算の基本となるものだ。

ビット演算のサンプル

PS /Users/daichi> 1
1
PS /Users/daichi> 1 -shl 0
1
PS /Users/daichi> 1 -shl 1
2
PS /Users/daichi> 1 -shl 2
4
PS /Users/daichi> 1 -shl 3
8
PS /Users/daichi> 1 -shl 4
16
PS /Users/daichi> 1 -shl 5
32
PS /Users/daichi> 1 -shl 6
64
PS /Users/daichi> 1 -shl 7
128
PS /Users/daichi> 1 -shl 8
256
PS /Users/daichi> 1 -shl 9
512
PS /Users/daichi> 1 -shl 10
1024
PS /Users/daichi> 1 -shl 11
2048
PS /Users/daichi> 1 -shl 12
4096
PS /Users/daichi>

ただ、PowerShell Coreを使うような用途ではビット演算はそれほど必要にならない。よくわからなければこの演算はわからないままでもよいだろう。

ビット演算に興味がある場合、ブール代数を習ったことがあるなら思い出してもらうとわかりやすいかもしれない。ブール代数を知らない場合、2進数の演算を行うビット演算というものがあり、それぞれこういったルールになっている、ということを調べて覚えてしまえばよい。

オブジェクトの算術演算

PowerShell Coreではオブジェクトに対して算術演算を適用することができる。どういった結果になるかはオブジェクトごとに異なるのでなんとも言えない。

たとえば日付データであれば次のような加算が実施される。

日付オブジェクトの加算

PS /Users/daichi> Get-Date

2019年3月15日 金曜日 10:41:46


PS /Users/daichi> New-TimeSpan -Days 1


Days              : 1
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 864000000000
TotalDays         : 1
TotalHours        : 24
TotalMinutes      : 1440
TotalSeconds      : 86400
TotalMilliseconds : 86400000



PS /Users/daichi> (Get-Date) + (New-TimeSpan -Days 1)

2019年3月16日 土曜日 10:42:06


PS /Users/daichi>

よく使うオブジェクトに関してはどのような四則演算が可能か調べてみるのはよいかもしれない。

こんな感じでPowerShell Coreではさまざまな対象を算術演算で操作できるようになっている。算術演算を行ってエラーが発生する場合、そもそも同じ型のデータを演算しようとしているか確認するとよい。強力なので別の型でも演算できてしまうが、基本的には同じ型で演算するものだと考えておくとそういった問題は避けられるだろう。

参考資料