산술 연산자

산술 연산자 (about_Arithmetic_Operators)

PowerShell에서 숫자를 계산할 때 쓰는 연산자, 산술 연산자에 대해 알아봐요. 어떤 연산자가 있고 어떤 순서로 평가되는지, 그리고 더하기·곱하기가 숫자가 아닌 문자열이나 배열·해시 테이블에도 적용된다는 점까지 차근차근 살펴볼게요.

출처: about_Arithmetic_Operators - PowerShell

본문

짧게 설명하면

이 문서는 PowerShell에서 산술을 수행하는 연산자에 대해 설명해요.

자세히 살펴보기

산술 연산자는 숫자 값을 계산해요. 더하기, 빼기, 곱하기, 나누기뿐 아니라 나눗셈의 나머지(모듈러)까지 구할 수 있죠.

더하기 연산자(+)와 곱하기 연산자(*)는 숫자뿐 아니라 문자열, 배열, 해시 테이블에도 동작해요. 더하기 연산자는 입력을 이어붙이고(concatenate), 곱하기 연산자는 입력을 지정한 횟수만큼 복사해서 돌려줘요. 심지어 하나의 산술 문장 안에서 객체 타입을 섞는 것도 가능해요. 이때 문장을 어떻게 평가할지 결정하는 건 식에서 가장 왼쪽에 있는 객체의 타입이에요.

PowerShell 2.0부터는 모든 산술 연산자가 64비트 숫자에서 동작해요.

PowerShell 3.0부터는 비트 연산을 지원하려고 -shr(오른쪽 시프트)와 -shl(왼쪽 시프트)이 추가됐어요. 비트 연산자는 정수 타입에서만 동작해요.

PowerShell이 지원하는 산술 연산자는 다음과 같아요.

  • 더하기(+) - 숫자를 더하고, 문자열·배열·해시 테이블을 이어붙여요
6 + 2                        # result = 8
"file" + "name"              # result = "filename"
@(1, "one") + @(2.0, "two")  # result = @(1, "one", 2.0, "two")
@{"one" = 1} + @{"two" = 2}  # result = @{"one" = 1; "two" = 2}
  • 빼기(-) - 숫자를 빼거나 부호를 바꿔요
6 - 2   # result = 4
- -6    # result = 6
(Get-Date).AddDays(-1) # Yesterday's date
  • 곱하기(*) - 숫자를 곱하거나 문자열·배열을 지정한 횟수만큼 복사해요
6 * 2       # result = 12
@("!") * 4  # result = @("!","!","!","!")
"!" * 3     # result = "!!!"
  • 나누기(/) - 숫자를 나눠요
6 / 2  # result = 3
  • 모듈러(%) - 나눗셈의 나머지를 돌려줘요
7 % 2  # result = 1
  • 비트 AND(-band)
5 -band 3  # result = 1
  • 비트 NOT(-bnot)
-bnot 5  # result = -6
  • 비트 OR(-bor)
5 -bor 0x03  # result = 7
  • 비트 XOR(-bxor)
5 -bxor 3   # result = 6
  • 비트를 왼쪽으로 시프트(-shl)
102 -shl 2  # result = 408
  • 비트를 오른쪽으로 시프트(-shr)
102 -shr 2  # result = 25

연산자 우선순위

PowerShell은 산술 연산자를 다음 순서로 처리해요.

우선순위 연산자 설명
1 () 괄호
2 - 음수 또는 단항 연산자
3 *, /, % 곱하기와 나누기
4 +, - 더하기와 빼기
5 -band, -bnot 비트 연산
5 -bor, -bxor 비트 연산
5 -shr, -shl 비트 연산

PowerShell은 우선순위 규칙에 따라 식을 왼쪽에서 오른쪽으로 처리해요. 다음 예시가 그 효과를 보여줘요.

3+6/3*4    # result = 11
3+6/(3*4)  # result = 3.5
(3+6)/3*4  # result = 12

PowerShell이 식을 평가하는 순서는 여러분이 써 봤던 다른 프로그래밍·스크립트 언어와 다를 수 있어요. 다음 예시는 조금 복잡한 할당 문장을 보여줘요.

$a = 0
$b = @(1,2)
$c = @(-1,-2)

$b[$a] = $c[$a++]

여기서 $a++ 표현식이 $b[$a]보다 먼저 평가돼요. $a++$c[$a++] 문장에서 사용된 뒤에 $a 값을 바꾸지만, $b[$a]에서 사용되기 전에는 적용되죠. 그래서 $b[$a]에서의 $a는 0이 아니라 1이에요. 결과적으로 이 문장은 $b[0]이 아니라 $b[1]에 값을 할당해요.

위 코드는 다음 코드와 동일해요.

$a = 0
$b = @(1,2)
$c = @(-1,-2)

$tmp = $c[$a]
$a = $a + 1
$b[$a] = $tmp

나눗셈과 반올림

나눗셈의 몫이 정수일 때 PowerShell은 가장 가까운 정수로 값을 반올림해요. 값이 .5라면 가장 가까운 짝수 정수로 반올림해요.

다음 예시는 가장 가까운 짝수 정수로 반올림하는 효과를 보여줘요.

PS> [int]( 5 / 2 )  # Result is rounded down
2

PS> [int]( 7 / 2 )  # Result is rounded up
4

다른 반올림 동작이 필요하다면 [Math] 클래스를 사용할 수 있어요.

PS> [int][Math]::Round(5 / 2,[MidpointRounding]::AwayFromZero)
3

PS> [int][Math]::Ceiling(5 / 2)
3

PS> [int][Math]::Floor(5 / 2)
2

더 자세한 내용은 Math.Round 메서드를 참고하세요.

결과에 맞춘 타입 변환

PowerShell은 정밀도를 잃지 않으면서 결과를 가장 잘 표현할 수 있는 .NET 숫자 타입을 자동으로 골라요. 예를 들어:

2 + 3.1
(2).GetType().FullName
(2 + 3.1).GetType().FullName
5.1
System.Int32
System.Double

연산 결과가 해당 타입으로는 너무 크다면 결과를 담을 수 있도록 결과의 타입을 넓혀요. 다음 예시를 볼까요.

(512MB).GetType().FullName
(512MB * 512MB).GetType().FullName
System.Int32
System.Double

결과의 타입이 항상 피연산자 중 하나와 같지는 않아요. 다음 예시에서는 음수 값을 부호 없는 정수로 캐스팅할 수 없고, 부호 없는 정수가 Int32로 캐스팅하기엔 너무 커요.

([int32]::MinValue + [uint32]::MaxValue).GetType().FullName
System.Int64

이 예시에서는 Int64가 두 타입을 모두 담을 수 있어요.

System.Decimal 타입은 예외예요. 두 피연산자 중 하나라도 Decimal 타입이면 결과 타입도 Decimal이 돼요. 그리고 Decimal 값으로는 너무 큰 결과는 오류가 나요.

PS> [decimal]::MaxValue
79228162514264337593543950335

PS> [decimal]::MaxValue + 1
RuntimeException: Value was either too large or too small for a Decimal.

정밀도 손실 가능성

결과가 타입의 범위를 넘어서면 타입 변환 때문에 정밀도를 잃을 위험이 있어요. 예를 들어 충분히 큰 [long][int]를 더하면 두 피연산자가 [double]로 변환돼요. 이 예시에서 9223372036854775807은 [long] 정수의 최댓값이에요. 여기에 값을 더하면 [long]의 범위를 넘어서게 되죠.

PS> (9223372036854775807 + 2).GetType().FullName
System.Double

결과를 [ulong]으로 캐스팅하면 피연산자가 먼저 [double]로 강제 변환됐기 때문에 정확하지 않은 결과가 나와요.

PS> [ulong](9223372036854775807 + 2)
9223372036854775808

더 큰 값을 먼저 [ulong]으로 정의하면 문제를 피하고 올바른 결과를 얻을 수 있어요.

PS> 9223372036854775807ul + 2
9223372036854775809

하지만 [ulong]의 범위를 넘어서면 결과는 [double]이 돼요.

PS> ([ulong]::MaxValue + 1).GetType().FullName
System.Double

Bigint 연산

[bigint] 숫자로 산술 연산을 하면 PowerShell은 모든 피연산자를 [bigint]로 변환해요. 그래서 정수가 아닌 값은 잘려나가요. 예를 들어 [double] 값 1.9는 [bigint]로 변환될 때 1로 잘려요.

PS> [bigint]1 / 1.9
1
PS> 1 / [bigint]1.9
1

이 동작은 다른 숫자 타입의 동작과 달라요. 이 예시에서 [int][double]로 나누면 결과가 [double]이 돼요. 1.9를 [int]로 캐스팅하면 값이 2로 반올림되죠.

PS> 1 / 1.9
0.526315789473684
PS> 1 / [int]1.9
0.5

숫자가 아닌 타입 더하기·곱하기

숫자, 문자열, 배열, 해시 테이블을 더할 수 있어요. 그리고 숫자, 문자열, 배열은 곱할 수 있어요. 다만 해시 테이블은 곱할 수 없어요.

문자열이나 배열, 해시 테이블을 더하면 요소들이 이어져요(concatenate). 배열이나 해시 테이블 같은 컬렉션을 이어붙이면 두 컬렉션의 객체를 모두 담은 새 객체가 만들어져요. 만약 같은 키를 가진 해시 테이블을 이어붙이려 하면 연산이 실패해요.

예를 들어 다음 명령은 두 배열을 만들고 더해요.

$a = 1,2,3
$b = "A","B","C"
$a + $b
1
2
3
A
B
C

서로 다른 타입의 객체 사이에서도 산술 연산을 할 수 있어요. 이때 PowerShell이 수행하는 연산은 연산에서 가장 왼쪽에 있는 객체의 Microsoft .NET 타입이 결정해요. PowerShell은 연산에 있는 모든 객체를 첫 번째 객체의 .NET 타입으로 변환하려고 시도해요. 변환이 성공하면 첫 번째 객체의 .NET 타입에 맞는 연산을 수행해요. 객체 중 하나라도 변환에 실패하면 연산이 실패해요.

다음 예시들은 서로 다른 객체 타입이 섞인 연산에서 더하기·곱하기 연산자를 어떻게 쓰는지 보여줘요.

$array = 1,2,3
$red = [ConsoleColor]::Red
$blue = [ConsoleColor]::Blue

"file" + 16      # result = "file16"
$array + 16      # result = 1,2,3,16
$array + "file"  # result = 1,2,3,"file"
$array * 2       # result = 1,2,3,1,2,3
"file" * 3       # result = "filefilefile"
$blue + 3        # result = Red
$red - 3         # result = Blue
$blue - $red     # result = -3
+ '123'          # result = 123

문장을 평가하는 데 쓰는 메서드가 가장 왼쪽 객체에 의해 정해지기 때문에, PowerShell의 더하기와 곱하기는 엄밀히 말해 교환 법칙이 성립하지 않아요. 예를 들어 (a + b)가 항상 (b + a)와 같진 않고, (ab)도 항상 (ba)와 같진 않아요.

이 원칙을 보여주는 다음 예시를 볼까요.

PS> "file" + 16
file16

PS> 16 + "file"
InvalidArgument: can't convert value "file" to type "System.Int32". Error:
"Input string wasn't in a correct format."

해시 테이블은 조금 다른 경우예요. 더해지는 해시 테이블들에 중복 키가 없다면 해시 테이블을 다른 해시 테이블에 더할 수 있어요.

다음 예시는 해시 테이블들을 서로 더하는 방법을 보여줘요.

$hash1 = @{a=1; b=2; c=3}
$hash2 = @{c1="Server01"; c2="Server02"}
$hash1 + $hash2
Name                           Value
----                           -----
c2                             Server02
a                              1
b                              2
c1                             Server01
c                              3

다음 예시는 두 해시 테이블에 중복된 키가 있기 때문에 오류가 나요.

$hash1 = @{a=1; b=2; c=3}
$hash2 = @{c1="Server01"; c="Server02"}
$hash1 + $hash2
OperationStopped:
Line |
   3 |  $hash1 + $hash2
     |  ~~~~~~~~~~~~~~~
     | Item has already been added. Key in dictionary: 'c'  Key being added: 'c'

또한 해시 테이블을 배열에 더할 수도 있어요. 이때 해시 테이블 전체가 배열의 항목 하나가 돼요.

$array1 = @(0, "Hello World", [datetime]::Now)
$hash1 = @{a=1; b=2}
$array2 = $array1 + $hash1
$array2
0
Hello World

Monday, June 12, 2017 3:05:46 PM

Key   : a
Value : 1
Name  : a

Key   : b
Value : 2
Name  : b

하지만 해시 테이블에 다른 어떤 타입도 더할 수 없어요.

$hash1 + 2
InvalidOperation: A hash table can only be added to another hash table.

더하기 연산자도 매우 유용하지만, 해시 테이블과 배열에 요소를 추가할 때는 할당 연산자를 사용하세요. 자세한 내용은 about_Assignment_Operators를 참고해요. 다음 예시들은 배열에 항목을 추가할 때 += 할당 연산자를 사용해요.

$array = @()
(0..2).ForEach{ $array += $_ }
$array
0
1
2

산술 연산자와 변수

변수와 함께 산술 연산자를 쓸 수도 있어요. 이때 연산자는 변수의 값에 적용돼요. 다음 예시들이 변수와 함께 쓰는 방법을 보여줘요.

PS> $intA = 6
PS> $intB = 4
PS> $intA + $intB
10

PS> $a = "Power"
PS> $b = "Shell"
PS> $a + $b
PowerShell

산술 연산자와 명령

보통은 숫자, 문자열, 배열이 들어 있는 식에서 산술 연산자를 써요. 그런데 명령이 반환하는 객체와 그 객체의 속성에서도 산술 연산자를 쓸 수 있어요.

다음 예시는 PowerShell 명령이 포함된 식에서 산술 연산자를 쓰는 방법을 보여줘요.

(Get-Date) + (New-TimeSpan -Day 1)

괄호 연산자는 그 순서대로 Get-Date cmdlet과 New-TimeSpan -Day 1 cmdlet 식을 평가하도록 강제해요. 두 결과는 그다음 + 연산자로 더해져요.

Get-Process | Where-Object { ($_.WS * 2) -gt 50mb }
Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
   1896      39    50968      30620   264 1,572.55   1104 explorer
  12802      78   188468      81032   753 3,676.39   5676 OUTLOOK
    660       9    36168      26956   143    12.20    988 PowerShell
    561      14     6592      28144   110 1,010.09    496 services
   3476      80    34664      26092   234 ...45.69    876 svchost
    967      30    58804      59496   416   930.97   2508 WINWORD

위 식에서는 각 프로세스의 작업 공간($_.WS)이 2로 곱해지고, 그 결과가 50mb보다 큰지 비교해서 확인해요.

비트 연산자

PowerShell은 표준 비트 연산자를 지원해요. 비트 AND(-band), 포함/배타적 비트 OR(-bor-bxor), 비트 NOT(-bnot)이죠.

PowerShell 2.0부터 모든 비트 연산자가 64비트 정수에서 동작해요.

PowerShell 3.0부터는 비트 연산을 지원하려고 -shr(오른쪽 시프트)과 -shl(왼쪽 시프트)이 도입됐어요.

PowerShell이 지원하는 비트 연산자는 다음과 같아요.

연산자 설명 결과
-band 비트 AND 10 -band 3 2
-bor 비트 OR(포함) 10 -bor 3 11
-bxor 비트 OR(배타) 10 -bxor 3 9
-bnot 비트 NOT -bnot 10 -11
-shl 왼쪽 시프트 102 -shl 2 408
-shr 오른쪽 시프트 102 -shr 1 51

비트 연산자는 값의 이진 형식에 적용돼요. 예를 들어 10의 비트 구조는 00001010(1바이트 기준)이고, 3의 비트 구조는 00000011이에요. 비트 연산자로 10과 3을 비교하면 각 바이트의 개별 비트가 비교돼요.

비트 AND 연산에서 결과 비트는 두 입력 비트가 모두 1일 때만 1이 돼요.

1010      (10)
0011      ( 3)
--------------  bAND
0010      ( 2)

비트 OR(포함) 연산에서 결과 비트는 두 입력 비트 중 하나 또는 둘 다 1일 때 1이 돼요. 결과 비트가 0이 되는 경우는 두 입력 비트가 모두 0일 때뿐이에요.

1010      (10)
0011      ( 3)
--------------  bOR (inclusive)
1011      (11)

배타적 비트 OR 연산에서 결과 비트는 입력 비트 중 하나만 1일 때 1이 돼요.

1010      (10)
0011      ( 3)
--------------  bXOR (exclusive)
1001      ( 9)

비트 NOT 연산자는 값의 2의 보수(binary complement)를 만드는 단항 연산자예요. 1 비트는 0으로, 0 비트는 1로 바뀌죠.

예를 들어 0의 2의 보수는 -1, 즉 부호 없는 정수의 최댓값(0xFFFFFFFF)이고, -1의 2의 보수는 0이에요.

-bnot 10
-11
0000 0000 0000 1010  (10)
------------------------- bNOT
1111 1111 1111 0101  (-11, 0xFFFFFFF5)

비트 왼쪽 시프트 연산에서는 모든 비트가 오른쪽 피연산자의 값인 "n" 자리만큼 왼쪽으로 이동해요. 그리고 1의 자리에는 0이 들어가요.

결과 이진 결과
21 -shl 0 21 0001 0101
21 -shl 1 42 0010 1010
21 -shl 2 84 0101 0100

비트 오른쪽 시프트 연산에서는 모든 비트가 오른쪽 피연산자가 지정한 "n" 자리만큼 오른쪽으로 이동해요. 오른쪽 시프트 연산자(-shr)는 부호 있는 값을 시프트할 때 부호 비트를 맨 왼쪽 자리에 복사해요. 부호 없는 값이라면 맨 왼쪽 위치에 0이 들어가요.

결과 이진 16진
21 -shr 0 21 00010101 0x15
21 -shr 1 10 00001010 0x0A
21 -shr 2 5 00000101 0x05
21 -shr 31 0 00000000 0x00
21 -shr 32 21 00010101 0x15
21 -shr 64 21 00010101 0x15
21 -shr 65 10 00001010 0x0A
21 -shr 66 5 00000101 0x05
[int]::MaxValue -shr 1 1073741823 00111111111111111111111111111111 0x3FFFFFFF
[int]::MinValue -shr 1 -1073741824 11000000000000000000000000000000 0xC0000000
-1 -shr 1 -1 11111111111111111111111111111111 0xFFFFFFFF
(-21 -shr 1) -11 11111111111111111111111111110101 0xFFFFFFF5
(-21 -shr 2) -6 11111111111111111111111111111010 0xFFFFFFF4

더 알아보기