Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.
산술 연산자
- 숫자 연산을 하는 연산자
구분 | 연산자 | 연산 | 피연산자 타입 |
사칙 연산과 나머지 | + | 덧셈 | 정수, 실수, 복소수, 문자열 |
- | 뺄셈 | 정수, 실수, 복소수 | |
* | 곱셈 | 정수, 실수, 복소수 | |
/ | 나눗셈 | 정수, 실수, 복소수 | |
% | ✭ 나머지 | 정수 ,실수, 복소수 | |
비트연산 | & | 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으로 채워지고, 자릿수를 벗어난 비트는 버려짐.
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으로 채워지고, 자릿수를 벗어난 비트는 버려짐.
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
정수 언더플로
- 오버플로와 반대로 정수 타입이 표현할 수 있는 가장 작은 값에서 -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 |