[Go 언어] 8. IEEE 754 규약을 따르는 실수, 오차 범위에 주의

안녕하세요. 언제나 휴일, 언휴예요. 이번에는 Go 언어에서 실수를 표현할 때 사용하는 float32(32비트)와 float64(64비트)를 소개할게요.

0.1과 0.2 사이에는 몇 개의 실수가 있을까요?

여러분이 아시는 것처럼 무한 개의 실수가 존재하죠. 컴퓨터에서는 메모리에 데이터를 표현하여 모든 실수를 유한한 메모리에 표현할 수는 없어요. 실제로 컴퓨터에 실수 표현은 오차 범위를 갖고 있어요.

Go언어에서는 IEEE 754 규약에 따라 실수를 표현하고 있어요. IEEE 754 규약에서는 실수를 부호부, 지수부, 가수부로 나누어 일정 범위의 오차 범위를 갖는 실수를 표현하는 것을 약속하고 있어요.

위키 피디아 IEEE 754

Go 언어에서는  IEEE 754 규약에서의 오차(10의 -14승)를 갖습니다. 다음은 float32 의 메모리 구조예요.

[그림 1] 32비트 실수의 메모리 구조
[그림 1] 32비트 실수의 메모리 구조

먼저 실수를 표현하는 방법을 알아보기로 해요. 새로운 프로젝트를 생성한 후에 표현해 보아요.

[그림 2] LiteIDE에서 새로운 프로젝트 생성 및 소스 파일 추가
// Example 실수
package main
 
import "fmt"
 
func main() {
    var f1 float32 = 0.1 //고정 소수점 표현
    var f2 float32 = .1  //고정 소수점 표현
    var f3 float32 = 1e-1 //부동 소수점 표현
    fmt.Println(f1)
    fmt.Println(f2)
    fmt.Println(f3)
}

실수를 표현할 때는 고정 소수점 표현 혹은 부동 소수점으로 표현할 수 있어요.

  • 고정 소수점 표현
var f1 float32 = 0.1 //고정 소수점 표현
var f2 float32 = .1  //고정 소수점 표현

고정 소수점 표현은 일반적으로 실수를 표현하는 것처럼 0.1 처럼 표현할 수 있으며 만약 정수부가 없을 때는 .1 처럼 표현할 수도 있어요.

  • 부동 소수점 표현
var f3 float32 = 1e-1 //부동 소수점 표현

부동 소수점 표현은 1e-1 처럼 e와 지수를 사용합니다. 1e-1은 1곱하기 10의 -1승을 의미하며 0.1과 같은 값입니다.

[그림 3] 실수 표현 예제 실행 화면

그런데 실수를 사용할 때는 오차 범위가 있다는 것을 주의해야 합니다. 예를 들어 0.1을 10번 더한다고 정확한 1.0이 나오지 않아요. 이를 확인하기 위해 새로운 프로젝트를 생성하여 확인해 보아요.

[그림 4] LiteIDE에서 새로운 프로젝트 생성 및 소스 파일 추가
// Example 오차 범위를 갖는 실수
package main
 
import "fmt"
 
const errbound = 1e-14
func main() {
    var f1 = 0.0
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    f1 += 0.1
    fmt.Println(f1)
    fmt.Println(f1 == 1.0) //오차 범위를 갖고 있으므로 버그일 수 있음
    fmt.Println((f1 - 1.0) <= errbound)//오차 범위내에 같은 값인지 비교
}

실수 f1을 선언하면서 0.0으로 초기화한 후에 0.1씩 더하고 출력해 보세요. 예상하는 값은 0.1, 0.2, 0.3, 0.4, … 이지만 실제 확인해 보면 차이가 있다는 것을 알 수 있어요. 이는 컴퓨터에서 실수 표현은 오차 범위를 갖기 때문입니다.

var f1 = 0.0
f1 += 0.1
fmt.Println(f1)
f1 += 0.1
fmt.Println(f1)
f1 += 0.1
fmt.Println(f1)
f1 += 0.1
fmt.Println(f1)

Go 언어의 실수는 IEEE 754 규약을 따르며 오차 범위는 10의 -14승이예요. 따라서 실수 값을 갖는 변수와 비교하고자 하는 값을 뺀 후에 차이가 10의 -14승 이내에 있다면 오차 범위내에서 같은 값으로 취급할 수 있어요.

fmt.Println(f1 == 1.0) //오차 범위를 갖고 있으므로 버그일 수 있음
fmt.Println((f1 - 1.0) <= errbound)//오차 범위내에 같은 값인지 비교
[그림 5] 오차 범위를 갖는 실수 예제 실행 화면