『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트 : 6강 연산자 (2)

2021. 5. 21. 00:06개발/Golang

728x90
반응형

두번째 연산자 입니다. 이번에는 비교연산자를 알아보도록 하겠습니다.

 

1. 비교 연산자

 

  • 비교 연산자는  if문, switch문, for문에 주로 사용합니다.
  • 양변을 비교하여 만족하면 불리언값 true를, 만족하지 못할 경우 false를 반환하는 연산자입니다.
  • 몇가지 주의 점이 있습니다. 부호가 있는 정수를 사용할 때 발생하는 오버플러와 언더플로 문제, 실수 끼리의 비교입니다.

 

1-1. 정수 오버플로 & 언더플로

 

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+1)
    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-1) 
    fmt.Printf("y\t=%4d, %08b\n",y,y)
    fmt.Printf("y-1\t =%4d,%08b\n",y-1,y-1)
    
    }
    

결과값

  •  x는 int8인 127인 값입니다. 근데 결과 값이 false입니다. 그 이유는 int8의(최대값인 127에 1을 더한경우) 가장 큰값에서 가장 작은 값으로 변화하는게 오버플로입니다. 그래서 128이아니라 -128이 됩니다. 
  •  y는 int8의 -128의 값이며, 가장 작은 값입니다. 이때 -1할경우 가장 큰값으로 바뀝니다. 이것이 언더플로입니다.

      ※ 정수 타입은 값의 경계에서 오버플로와 언더플로가 발생하기 때문에 연산에 주의해야합니다.

 

1-2. float 비교 연산

 

package main

import "fmt"

func main() {

	var a float 64 = 0.1
    var b float 64 = 0.2
    var c float 64 = 0.3
    
    fmt.Printf("%f + %f == %f : %v\n", a,b,c,a+b==c)
    fmt.Println(a+b)
    
    }

 

결과값

  • 0.1+0.2 == 0.3을 수행했으나 값은 서로 같지 않습니다. 수행해보니 0.30000000000000004가 출력되었습니다. float64 표현 방식으로 생긴 오차이기때문입니다. float표현은 ==연산시 예기치 못한 오류가 발생할수 있습니다.

 

1-3. 실수 오차와 해결법

  • 컴퓨터는 10진수가 아닌 2진수로 되어있어 정확히 표현하기 어려운 문제가 있습니다.
  • float타입으로 최대한 가깝게 표현해도 오차가 발생할수 밖에 없습니다.

작은 오차 무시하기

 

package main
import "fmt"

const epsilon = 0.00001	// 매우 적은값

func equal(a,b float64) bool {
	if a>b {
    	if a - b <= epsilon {	// 작은 차이 무시
        	return true
           } else {
           	return false
		}
		
            } else {
            if b - a <= epsilon {
         	   return true
            }else{
            return false
            }
           }
         }
           
            func main() {
            	var a float64 = 0.1
                var b float64 = 0.2
                var c float64 = 0.3
                
                fmt.Printf("%0.18f + %0.18f = %0.18f\n",a,b,a+b)
                fmt.Printf("%0.18f == %0.18f: %v\n",c,a+b,equal(a+b,c))

	a = 0.0000000000004
	b = 0.0000000000002
	c = 0.0000000000007
	
	fmt.Printf("%g == %g: %v\n",c,a+b,equal(a+b,c))
                }

결과값

  • 매우 작은 상수값 ( const )를 선언하고 epsilon(0.00001)이라고 했습니다. 이 값은 무시할 오차 한계를 정의한 값입니다.
  • 소수점 18자리 까지 출력하면 0.1과 0.2가 정확하지 않다는걸 알수가 있습니다. 그래서 + 할경우 0.3아니라 오차가 발생합니다.
  • c값도 0.3이지만 0.3이 아니고 2.99999... 인걸 알수 있습니다. 이 두값의 차이가 eqsilon보다 작기 때문에 두값을 같은 값으로 간주하고 true가 출력됬습니다. 

작은오차를 없애는 더 나은 방법 :  Nextafter()함수 이용하기

 

Go 언어에서는 math 패키지에서  Nextafter() 함수를 제공합니다.

이 함수를 이용해서 실숫값 대소비교를 할 수 있습니다.

 

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 = %0.18f\n" , a,b,a+b)
    fmt.Printf("%0.18f == %0.18f: %v\n",c,a+b,equal(a+b,c))
    
 	a = 0.0000000000004
	b = 0.0000000000002
	c = 0.0000000000007
	
	fmt.Printf("%g == %g: %v\n",c,a+b,equal(a+b,c))
    
    }

결과값

  • 두 실수를 비교하는 math패키지의 Nextafter() 함수를 이용하도록 변경하고 작은 비트값 만큼 오차범위 만 인정하게 되었습니다.

※ 오차를 무시하는 방법이지, 정확한 계산은 아닙니다. 금융프로그램이라면 math/big 패키지에서 제공하는 Float 객체를 이용해야합니다.

 

작은오차를 없애는 더 나은 방법 :   Float 객체 이용하기

 

package main

import (
	"fmt"
    "math/big"
    )
    
    func main() {
    
    	a, _ := new(big.Float).SetString("0.1")
        b, _ := new(big.Float).SetString("0.2")
        c, _ := new(big.Float).SetString("0.3")
        
        d := new(big.Float).Add(a,b)
        fmt.Println(a,b,c,d)
        fmt.Println(c.Cmp(d))
        }

결과값

  • math/big 패키지의 Float객체를 생성합니다.
  • a = 0.1 ,b = 0.2, c = 0.3을 나타냅니다
  • a+b의 값을 d에 저장하고 c와 d를 비교합니다 (Cmp)
  • 비교시 c가 작을경우 -1을 c가 큰경우 1을 두값이 같은경우는 0을 출력합니다. 두값이 같기에 0을 출력합니다.

 

2. 논리 연산자

 

논리 연산자는 불리언 피연산자를 대상으로 연산해 결과로 true나 false를 반환합니다. &&, ||, ! 연산자를 제공합니다.

 

&& AND  양변이 모두 true이면 ture를 반환합니다.
|| OR  양변 중 하나라도 true이면 true를 반환합니다.
! NOT  true이면 false를 반환하고 false이면 true를 반환합니다.

 

3. 대입 연산자

대입 연산자는 우변의 값을 좌변(메모리 공간)에 복사합니다. 좌변은 반드시 저장할 공간이 있는 변수가 와야합니다.

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

 

변수 a를 선언하고 대입 연산자를 통해서 a( 가리키는 메모리 공간 )에 10을 복사합니다. 

 

[ 틀린 예 ]
var a int
var b int
a = b = 10	// 오류 발생 대입연산자는 결과를 반환하지 않습니다.

[ 올바른 예 ]

var a int
var b int
a = 10
a = b 		// 두줄로 나타내야지 됩니다.

 

복수 대입 연산자

우변의 개수와 좌변의 개수를 맞춰야합니다.

a , b = 3 , 4

첫째 3은 a 좌변주소에, 둘째 4는 b 좌변주소에 대입이 됩니다. 

 

복합 대입 연산자

var a = 10
a = a + 2

변수 a를 선언하고 10으로 값을 초기화 합니다. 그리고 a+2를 대입했습니다 . a = a+2는 a+=2로 쓸수 있습니다. +=를 복합 대입 연산자라고 합니다.

 

모든 산술 연산자는 다 복합 대입 연산자로 쓸수 있습니다. 즉, +=, -=,*=,/=,%=,&=,|=,^=,<<=,>>= 등이 가능합니다.

 

증감 연산자

변수값 1을 증가하거나 1감소하는 구문은 자주 사용되어 증감문을 제공합니다. ++와 --을  두종류를 제공합니다.

 

var a int 10

// 아래의 세 구문은 모두 a값을 증가시킵니다.
a = a + 1	//a의 값을 1 증가시킵니다.
a += 1	//a의 값을 1 증가시킵니다.
a++	//a의 값을 1 증가시킵니다.


// 아래의 세 구문은 모두 a값을 감소시킵니다.
a = a - 1	//a의 값을 1 감소시킵니다.
a -= 1	//a의 값을 1 감소시킵니다.
a--	//a의 값을 1 감소시킵니다.

※ Go언어는 전위 증감연산자를 지원하지 않습니다. 즉 ++a는 사용하지못하고 a++만 가능합니다.

 

그 외 연산자

 

연산자 설명
[] 배열 요소에 접근할 때 사용합니다.
. 구조체나 패키지 요소에 접근할 때 사용합니다.
& 변수의 메모리 주솟값을 반환합니다.
* 포인터 변수가 가리키는 메모리 주소에 접근합니다.
... 슬라이스 요소들에 접근하거나 가변 인수를 만들 때 사용합니다.
: 배열의 일부분을 집어올때 사용합니다.
<- 채널에서 값을 빼거나 넣을 때 사용합니다.

※ 그 외의 연산자는 다른 강에서 배울 예정

 

연산자 우선순위

순위가 높은 연산자가 먼저 계산됩니다. 우선순위가 같으면 좌측부터 우측으로 연산됩니다.

 

728x90
반응형