본문 바로가기
Language/Golang

Golang (Go언어) 연산자(Operator)

by HeeWorld 2024. 10. 26.

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

 

Golang 마스코트 Gopher(고퍼)

 

산술 연산자

- 숫자 연산을 하는 연산자

구분 연산자 연산 피연산자 타입
사칙 연산과 나머지 + 덧셈 정수, 실수, 복소수, 문자열
- 뺄셈 정수, 실수, 복소수
* 곱셈 정수, 실수, 복소수
/ 나눗셈 정수, 실수, 복소수
% ✭ 나머지 정수 ,실수, 복소수
비트연산 & AND 비트 연산 정수
| OR 비트 연산 정수
^ XOR 비트 연산 정수
&^ 비트 클리어 정수
시프트 연산 << 왼쪽 시프트 정수 << 양의 정수
>> 오른쪽 시프트 정수 >> 양의 정수

 

✓ 비트연산: &(AND 연산자)

- 두 비트가 1일 때만 1

A B A&B
0 0 0
1 0 0
0 1 0
1 1 1

 

✓ 비트연산: |(OR 연산자)

- 두 비트 중 1개가 1이면 1

A B A|B
0 0 0
1 0 1
0 1 1
1 1 1

 

✓ 비트연산: ^(XOR 연산자)

- Go에서는 승수가 아닌 XOR 연산자

- 두 비트가 다를 때만 1

A B A^B
0 0 0
1 0 1
0 1 1
1 1 0

 

✓ 시프트 연산: << (왼쪽 시프트)

- 오른쪽 피연산자값 만큼 전체 비트를 왼쪽으로 밀어냄

- 비트가 이동되어 빈 자리는 0으로 채워지고, 자릿수를 벗어난 비트는 버려짐.

ex) 10 << 2 = 40

package main

import "fmt"

func main() {
	var x int8 = 4
	var y int8 = 64

	fmt.Printf("x:%08b x<<2:%08b x<<2: %d\n", x, x<<2, x<<2)
	fmt.Printf("y:%08b y<<2:%08b y<<2: %d\n", y, y<<2, y<<2)
}

// 출력

x:00000100 x<<2:00010000 x<<2: 16
y:01000000 y<<2:00000000 y<<2: 0

 

✓ 시프트 연산: << (오른쪽 시프트)

- 왼쪽 피연산자값 만큼 전체 비트를 오른쪽으로 밀어냄

- 비트가 이동되어 빈 자리는 0으로 채워지고, 자릿수를 벗어난 비트는 버려짐.

ex) 10 >> 2 = 2

package main

import "fmt"

func main() {
	var x int8 = 16
	var y int8 = -128
	var z int8 = -1
	var w uint8 = 128

	fmt.Printf("x:%08b x>>2:%08b x>>2: %d\n", x, x>>2, x>>2)
	fmt.Printf("y:%08b y>>2:%08b y>>2: %d\n", uint8(y), uint8(x>>2), y>>2)
	fmt.Printf("z:%08b z>>2:%08b z>>2: %d\n", uint8(z), uint8(z>>2), z>>2)
	fmt.Printf("w:%08b w>>2:%08b w>>2: %d\n", uint8(w), uint8(w>>2), w>>2)
}

// 결과

x:00010000 x>>2:00000100 x>>2: 4
y:10000000 y>>2:00000100 y>>2: -32
z:11111111 z>>2:11111111 z>>2: -1
w:10000000 w>>2:00100000 w>>2: 32

 

 

비교 연산자

- 양변을 비교해서 조건에 만족하면 불리언값 true, 만족하지 못할 경우 false를 반환

연산자 설명 반환값
== 같다 참이면 true
거짓이면 false
!= 다르다
< 작다
> 크다
<= 작거나 같다
>= 크거나 같다

 

- 비교 연산자는 분기문(if, switch문)과 반복문(for문)에서 주로 사용

비교연산 연산 의미 결과
2 == 2 같은가? 같아서 true
2 != 2 같지 않은가? 같아서 false
3 < 2 작은가? 3이 2보다 작지 않아 false
4.2 > 1.3 큰가? 4.2가 1.3보다 커서 true
2 <= 2 작거나 같은가? 양쪽이 같아서 true
5 >= 8  크거나 같은가? 5와 8은 다르고 5는 8보다 크지 않아서 false

 

 

정수 오버플로

- 정수가 정수 타입의 범위를 벗어난 경우 값이 비정상으로 변화하는 현상

- x가 정수 타입일 때 x < x+1을 항상 만족(true)하지 못할 수 있음.

package main

import "fmt"

func main() {
	var x int8 = 127

	fmt.Printf("%d < %d + 1: %v\n", x, x, x < x+1)
	fmt.Printf("x\t= %4d, %08b\n", x, x)
	fmt.Printf("x + 1\t= %4d, %08b\n", x+1, x+2)
	fmt.Printf("x + 2\t= %4d, %08b\n", x+2, x+2)
	fmt.Printf("x + 3\t= %4d, %08b\n", x+3, x+3)

	var y int8 = -128
	fmt.Printf("%d > %d -1: %v\n", y, y, y > y-1)
	fmt.Printf("y\t= %4d, %08b\n", y, y)
	fmt.Printf("y -1\t= %4d, %08b\n", y-1, y-1)
}


// 결과

127 < 127 + 1: false
x	=  127, 01111111
x + 1	= -128, -1111111
x + 2	= -127, -1111111
x + 3	= -126, -1111110
-128 > -128 -1: false
y	= -128, -10000000
y -1	=  127, 01111111

 

- 부호가 있는 정수에서 최상위 비트는 부호를 뜻하는 특수한 기능을 함.

- 값의 범위가 -128(1000 0000) ~ 127(0111 1111)일 때, 0111 1111에 1을 더하면,

   1000 0000이 되어 최상위 비트가 0에서 1로 바뀜.

- 값의 범위에서 가장 큰 값에 +1을 할 때 가장 작은 값으로 변화하는 현상이 오버플로

- x가 127일 때 +1을 하면, 오버플로가 일어나 128이 아닌 -128이 됨

- 따라서, 127 < 127 + 1은 false

127 + 1은 - 128, 127 +2는 -127, 127 +3은 -126

 

 

정수 언더플로

- 오버플로와 반대로 정수 타입이 표현할 수 있는 가장 작은 값에서 -1을 했을 때 가장 큰 값으로 바뀜.

- 예를 들어, int8 타입에서 -128에서 -1을 하면 -129가 아닌 127이 됨, 이를 언더플로라고 함.

- 따라서, 8비트 정수 타입에서 '-128 > -128-1'은 false가 됨.

 

 

 

실수 오차

- 컴퓨터에서 실숫값을 표현할 때 지수부와 소수부로 나눠서 표현함.

- 컴퓨터는 지수부와 소수부가 2진수 기준으로 되어 있음.

- 따라서 10진수 실수를 정확히 표현하기 어려운 문제가 있음.

package main

import "fmt"

func main() {
	var a float64 = 0.1
	var b float64 = 0.2
	var c float64 = 0.3

	fmt.Printf("%f + %f == %f : %v\n", a, b, c, a+b == c)
    fmt.Println(a + b)
}

// 결과 값

0.100000 + 0.200000 == 0.300000 : false
0.30000000000000004

 

소수부 표현식(float64)으로 0.3을 정확하게 표현할 수 없어서 가장 가까운 숫자인 0.30000000000000004를 출력함.

실수 표현식에서 0.3은 float32 타입에서 0.299999... 이나 0.30000... 으로 밖에 표현이 되지 않음.

두 값은 마지막 1비트 밖에 차이가 나지 않으며(하나는 01, 다른 하나는 10), 0.3은 그 두 값의 사이 어딘가에 존재한다고

보지만 (0.3을 표현할 수 있는 실수 타입 범위에서는 가장 작은 차이) 2진수 형태로는 표현할 방법이 없음. 

 

참고:

예를 들어, 0.375는 0.3+0.07+0.005이고, 이는 3x10^(-1) + 7x10^(-2)+ 5x10^(-3)이 됨.

컴퓨터는 2진수 체계를 쓰기 때문에,2^(-1)은 1/2=0.5, 2^(-2)는 0.25, 2^(-3)은 -0.125가 됨.

그래서 0.375를 2진수로 나타내면 1x2^(-2) + 1x2^(-3)이 됨.

0.375에서 0.001만 더한 값인 0.376을 표현하려 해도 2^(-4), 2^(-5) 등 2의 마이너스 승수값을 더해도 절대 0.376이 나오지 않음.

따라서 0.376 값은 float32 타입으로 최대한 가깝게 표현하여 0.375999987125396728515625가 됨.

실수값을 정확히 표현할 수 없기 때문에 오차가 생길 수 밖에 없음!

 

 

* Nextafer() 함수

- 해당 함수는 float64 타입 두 개를 받아 float64 타입 하나를 반환

- 이 함수 동작은 x에서 y를 향해 1비트만 조정한 값을 반환

- 만약 x가 y보다 작다면, x에서 1비트 만큼 증가시키고 그렇지 않다면 x에서 1비트 만큼 감소시킨 값을 반환 = 가장 작은 오차만큼 y를 향해 더하거나 뺌.

package main

import (
	"fmt"
	"math"
)

func equal(a, b float64) bool {
	return math.Nextafter(a, b) == b
}

func main() {
	var a float64 = 0.1
	var b float64 = 0.2
	var c float64 = 0.3

	fmt.Printf("%0.18f == %0.18f : %v\n", c, a+b, equal(a+b, c))
}

// 결과

0.299999999999999989 == 0.300000000000000044 : true

 

func equal(a, b float64) bool {

→ 실수 값 float64 타입으로 두 개를 입력 받아서(return을 불리언 타입으로 반환), 두 값이 같으면 true를 반환 다르면 false를 반환 

 

return math.Nextafter(a, b) == b

→ 값을 반환하는 함수 return(함수의 결과를 반환한다)

→ math.Nextafter는 max의 패키지 안에 있는 Nextafter라는 함수에 (a,b) 값을 넣어서 실행한 결과가 b의 값과 같냐, 같으면 true 다르면 false

 

fmt.Printf("%0.18f == %0.18f : %v\n", c, a+b, equal(a+b, c))

→ 0.18 소수점 18번째 자리까지 출력하라

→ 첫 번째는 c값을 출력하고, a+b를 출력하고, equal하여 호출한 값 (a+b), c한 결과를 출력

 

결과는 (a+b)와 c가 같다.

=> a+b는 0.3보다 살짝 큰 값이고, c는 0.3보다 살짝 작은 값 (0.3은 두 값의 사이에 있으며, 이는 1 비트 차이 밖에 안남)

=> Nextafter는 (a+b), c 두 값이 오면 앞의 값에서 뒤에 있는 값을 향해 1비트 만큼만 감

=> 결국 a+b에서 1비트 조정한 값은 c와 같음

=> Nextafer는 c값을 반환하여 equal이 true가 나옴.

 

Nextafter 함수도 오차를 무시하는 방법이며, 그 오차가 매우 작을 뿐 정확한 계산은 아님.

 

 

논리 연산자

- 불리언 피연산자를 대상으로 연산하여 결과를 true나 false로 반환

- &&,||는 피연산자가 둘이고, !는 하나

&& AND 양변이 모두 true이면, true를 반환
|| OR 양변 중 하나라도 true이면, true를 반환
! NOT true이면 false를 반환하고, false이면 true를 반환

 

5<8 && 2>=3 5<8은 true, 2>=3이 false이므로 결과는 false
5<8 && 2<=3 5<8은 true, 2<=3이 true이므로 결과는 true
5<8 || 2>-=3 5<8은 true, 2>=3이 false이므로 결과는 true
!(2<5 || 10<5) 2<5는 true이고 10<5는 false, true || false는 true, 그 후 !true를 하면 최종 결과는 false

 

 

대입 연산자

- 우변값을 좌변(메모리 공간)에 복사

- 좌변은 반드시 저장할 공간이 있는 변수가 와야 함.

- 대입 연산자는 값을 반환하지 않음

var a int 
a = 10  // 대입연산자

 

* 복수 대입 연산자

- 여러 값을 한 번에 대입할 수 있으며, 우변 개수에 맞춰서 좌변 개수도 맞춰야 함.

a, b = 3, 4

 

→ 첫 번째 우변값은 첫 번째 좌변 주소에, 두 번째 우변값은 두 번째 좌변 주소에 대입

→ a 변수에는 3이 대입되고, b 변수에는 4가 대입됨

→ 아래와 같이 복수 대입 연산자를 사용하여 두 변수의 값을 서로 바꿀 수 있음

package main

import "fmt"

func main() {
	var a int = 10
	var b int = 20

	a, b = b, a

	fmt.Println(a, b)
}

// 결과

20 10

 

 

* 복합 대입 연산자

- 대입 연산자 앞에 다른 산술 연산자를 붙여 변수의 값과 연산의 결과를 다시 변수에 대입하는 복합 대입 연산자를 쓸 수 있음.

var a = 10
a = a + 2

 

→ 변수 a를 선언하고 a값을 10으로 초기화한 후 a값에 2를 더한 결과를 다시 변수 a에 대입

→ a = a+2는 a+=2로 줄여서 사용할 수 있음 => +=를 복합 대입 연산자라고 함.

= 모든 산술 연산자는 다 복합 대입 연산자로 사용할 수 있음.

+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= 등

 

 

* 증감 연산자

- 변숫값을 1 증가하거나, 감소하는 구문

- ++: 정수 타입 변수 뒤에 붙여쓰고, 해당 변숫값을 1 증가

- --: 정수 타입 변수 뒤에 붙여쓰고, 해당 변숫값을 1 감소

var a int = 10

a = a+1
a += 1
a++

a = a-1
a -= 1
a--

 

→ a++ 는 a에 1을 더해서 a=a+1을 수행하라는 의미로 만약 변숫값에 10이 들어갔다면 a++ 후 11이 됨.

→ a-- 는 a에 1을 빼서 a=a-1을 수행하라는 의미로 만약 변숫값에 10이 들어 갔다면, a-- 후 10이 됨.

 

대입연산자/증감연산자 역시 값은 반환하지 않기 때문에, b = a++는 사용할 수 없고, Go 언어에서는 전위 증감 연산자를 지원하지 않음.

즉, ++a는 사용할 수 없고, a++만 사용 가능.

 

* b = a = c가 불가능, a = c의 값을 반환하지 않기 때문에 해당 구문을 사용할 수 없음.

 

 

연산자 우선순위

- 우선순위가 높은 연산자가 먼저 계산됨.

우선순위 연산자
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

 

ex) 3 * 4 ^ 7 << 2 +3 * 5 == 7

예시

→ 순서가 3*4를 먼저하고, 쉬프트가 먼저라서 7 << 2를 수행, 그 다음 3*5를 수행, 그리고 ^를 수행한 뒤 ==은 가장 마지막 계산이 됨.

 

근데, (((3*4) ^ (7<<2)) + (3*5)) ==7 의 방식으로 ()로 묶어 연산자를 기재하는 것이 가독성을 위해 더 좋은 코드임.

 

 

'Language > Golang' 카테고리의 다른 글

Golang (Go언어) 상수(Constant)  (0) 2024.10.30
Golang (Go언어) 함수(Function)  (2) 2024.10.30
Golang (Go언어) fmt 패키지  (3) 2024.10.26
Golang (Go언어) 변수  (1) 2024.10.24
Golang (Go 언어) 이란?  (5) 2024.10.22