EveryDay.DevUp

[Unity] 컴퓨터의 수 표현 ( 정수, 실수 [ 고정 소수점, 부동 소수점 ] ) 본문

R&D

[Unity] 컴퓨터의 수 표현 ( 정수, 실수 [ 고정 소수점, 부동 소수점 ] )

EveryDay.DevUp 2020. 5. 29. 22:05

컴퓨터에서 처리하는 수의 표현을 알지 못하면, 실무에서 의도치 않은 실수를 할 때가 있다.

가령 공격력을 계산할 때 여러가지 float의 합과 곱으로 처리할 경우 실제 의도했던 결과 값과 다른 값을 받을 수도 있으며, if 문으로 실수 비교를 할때 true 일 것이라고 생각했던 값이 false로 나오는 경우도 있다.

private void Start()
	{
		float testValue = 0.0f;

		for( int i = 0; i < 10; i++ )
		{
			testValue += 0.3f;
		}

		Debug.Log( "result float: " + testValue );
		Debug.Log( "result double: " + (double)testValue );
		Debug.Log( "result int: " + (int)testValue );
	}
    
   // result float: 3
   // result double: 2.99999976158142
   // result int : 2

▶ 예시 코드를 보면 testValue에 0.3을 10번 더 했기 때문에 3이 나와야 할 것 같다. float를 Console에 표시하면 3으로도 나온다. 하지만 double로 타입 캐스팅을 하면 값이 2.99999...로 나오고 심지어 int로 타입 캐스팅을 하면 2가 나온다.

( 의도 했던 값은 3이였지만, 실제 값은 2.999, int 형은 2가 나온 상황 )

▶ 변수 하나만 이렇게 오차가 나면 다행이지만, 만약 나온 결과 값을 또 다른 곳에서 곱하고 더하고 하면 점점 오차의 범위가 커질 것이다.

▣ 컴퓨터에서 수를 어떻게 표현하길래 이런 상황이 나온 것일까?

컴퓨터의 수 표현은 크게 정수와 실수로 나눌 수 있다.

정수는 -1, 1, 2, 3, 4... 을 얘기하고 실수는 -0.1, 0.2, 0.3, 1.3 ...과 같은 수를 이야기한다.

컴퓨터의 수 표현 방식을 이해하기 전에 사람의 수 표현 방식을 이해해야만 하는데 사람들은 일반적으로 10진법을 사용하여 수를 표현한다.

※ 진법이란 사람 사이의 약속과 같은 것으로 물체의 수, 크기와 같은 것을 표현한 수를 사람들이 이해할 수 있는 문자로 표현하는 방식이다.

10진법은 0~9까지의 문자를 사용하여 세상의 모든 수를 표현하는 것을 얘기한다. 

0~9까지의 문자로 모든 숫자를 표현하는데에는 자릿수의 개념을 이해해야만 한다.

예를들어, '125' 이라는 숫자가 있을 때 우리는 문자 하나하나를 떼서 1 2 5 이라고 읽지않고 '백이십'이라고 읽으면서 수의 크기를 알 수 있다. 이는 사람들 사이의 약속으로써 뒤에서 부터 세번째 자리는 100 둘째자리는 20 첫째자리는 5라는 것으로 배웠기 때문이다. 

'125'를 10진법의 자리수로 표현 하면 1 ×10^2, 2×10^1, 5×10^0 이 된다.

10진법에서 10^n 으로 표현되는 것은 125를 10으로 나눠보면 알 수 있다.

'125을 100으로 나눳을 때 나머지는 '25'가 되고 '25'를 10으로 나눴을 때 나머지는 5가되기 때문에 125를 10의 n 승으로 나타낼 수 있다.

사람의 10진법을 이해했다면, 컴퓨터의 수 표현을 이해해볼 차례이다. 

※ 컴퓨터가 사람과 같은 10진법을 이해할 수 있다면 좋겠지만, 컴퓨터는 전기 신호로 이루어진 기계이기 때문에 전기 신호가 끊어졌다 켜졌다 두 가지 상태만을 이해할 수 있다. 끊어졌다 켜졌다는 0,1이라는 문자로 대체하여 표현할 수 있고 이를 진법으로 나타내면 2진법이 된다. 2진법은 1과 0으로 세상의 모든 수를 표현하고자하는 표현 방식이다.

사람이 이해하는 10진법과 컴퓨터가 이해하는 2진법 두 가지 진법사이에서 컴퓨터에게 사람이 명령을 내리기 위해서는 10진법을 2진법으로 변환해야할 필요가 있다. 

2진법 변환 방식은 정수와 실수가 나누어져 처리되는데 먼저 10진법 정수를 2진법으로 표현하는 방식에 대해 설명하고자 한다.

▶ 정수 '125'를 2진법으로 변환하는 방식은 수를 2로 계속 나누어가면서 0이 나올때까지 나온 나머지를 아래부터 위로 적으면 된다.

125/2 = 62 나머지 1
62/2 = 31 나머지 0
31/2= 15 나머지 1
15/2=7 나머지 1
7/2=3 나머지 1
3/2=1 나머지 1
1/2=0 나머지 1

2진수 변환값은 1111101이 된다. 이 값이 맞는지 검증을 해보면 (오른쪽 끝자리 부터 2^0 으로 시작해 자리수가 증가할때마다 2^1, 2^2....으로 곱하면된다.)

1×1= 1
0×2= 0
1×4= 4
1×8= 8
1×16=16
1×32=32
1×64=64

값을 모두 더하면 125가 나옴을 알 수 있다.

정수를 2진법으로 표현하는 방법을 알았으니, 다음은 실수를 표현하는 방법에 대해 알아보고자 한다.

10진법 실수 125.2를 2진법으로 변환하는 방법은 정수와 소수를 나누어 계산하는 것이다. 

실수는 정수와 소수를 나누어 변환할 수 있는데, 125는 정수로 위의 10진법 정수를 2진법으로 변환하는 방법을 사용하면 된다. 
소수 0.2를 2진법으로 표현하는 방법은 2를 계속 곱해나가면서 정수가 나오는 부분을 1로 소수만 나오는 경우를 0으로 기록하는 것이다.

0.2×2=0.4 => 0
0.4×2=0.8 => 0
0.8×2=1.6 => 1
0.6×2=1.2 => 1
0.2×2=0.4 => 0
0.4×2=0.8 => 0

'0011'이라는 패턴이 반복되는것을 알 수 있다. 이 값은 0.001100110011... 무한히 반복되고 0.0011을 10진법으로 다시 변환하면 0×1/2 + 0×1/4 + 1×1/8 + 1×1/16 = 3/16 = 0.1875가 나오게된다.

만약 0.00110011로 계산하면 1/8 + 1/16+ 1/128 + 1/256 = 0.19921875로 4자리만 계산한 것보가 8자리를 계산했을 때 0.2에 좀 더 근사치가 됨을 알 수 있다.

실수는 컴퓨터에서 정확치 않은 근사값으로 계산되기 때문에 코딩을 할때 실수 처리에 대해서는 주의를 해야한다.

▶ 프로그래밍 언어에서 자료형은 수로 표현되는 값의 범위라고 할 수 있는데, 사람들이 알기 쉽게 추상화된 예약어로 대체되는 것이다.

예를 들어 bool 은 1, 0을 표현하는 것이고 string은 char[]로 char또한 언어마다 다르지만 -128~127까지 수를 표현하겠단 의미이다.

자료형은 할당된 메모리의 크기 만큼 bit에 0,1을 기록하게되고 할당된 bit의 개수에 따라 표현할 수 있는 수의 한계가 결정된다

예를 들어 정수를 char(1바이트)에 메모리를 할당해서 사용한다고 할 때 1바이트는 8비트임으로 2^8 의 수까지 원래 표현이 가능하다.

하지만 가장 앞에 비트를 부호 비트(양수와 음수) 로 사용하면서 실제로는 2^7 개의 숫자만 표현이 가는해진다. 만약 부호가 없는 unsigined char를 사용하면 2^8 까지 가능하다.

실수는 정수보다 표현할 수 있는 수의 한계가 더 작아지는데 소수를 표현하기 위해 특별한 방법을 사용하기 때문이다.

먼저 실수를 컴퓨터에서 표현하는 방식에는 고정 소수점 방식부동 소수점 방식이있는데 대부분 부동 소수점 방식을 사용한다고 보면된다.

고정 소수점 방식은 비트를 정수 표현을 할 수 있는 부분과 소수를 표현하는 부분을 구분지어서 사용하는 것으로 가령 8비트가 주어졌을 때 앞에 4비트는 정수를 표현 뒤의 4비트는 소수를 표현하는 것이다.

이 방식은 계산 속도가 빠르지만 표현할 수 있는 수가 제한된다는 단점을 가지고 있다

부동 소수점 방식은 실수를 정규화하여 사용하는데 실수 중 정수 한자리 1을 제외한 모든 수를 소수로 변경하는 것이다.

예를 들어 110.1100이 있다면 1.101100×2^2 으로 값을 변경하는 것입니다. 그러면 이제 옮겨진 자리수 만큼의 표현이 필요해지게 된다.

이를 위해 부동소수점방식은 특정 비트를 나누어서 의미를 부여한다.

가장 첫번째 자리는 부호(양수,음수)를 표현하고 그 뒤에는 지수를 표현하고, 그 뒤에는 가수를 넣는다.

지수 표현을 위해서는 초과 표현이라는 것을 사용하는데

예를 들어 지수 표현에 3비트를 사용한다면 원래 승의 +3을 더해서 사용하는 것이다.

3비트를 사용할 경우 -3에서 3까지의 지수 승을 표현할 수 있는데, 여기에 3을 더하면 0~6까지 지수 승을 나타낼 수 있고 이는 양수이기때문에 컴퓨터에서 단순하고 빠르게 대소 비교가 가능해진다.

실제로 위의 110.1100( 10진법 6.75 )을 8비트에 표현하면

노멀라이즈를 해서 1.101100 × 2^2 지수의 3비트 초과표현으로 +3, 2^(2+3) = 2^5

8비트에 표현하면 0 101 1101 이 된다.

계산이 맞는지 확인하면 먼저 첫번째 비트가 0임으로 양수 101은 5인데 3비트 초과표현임으로 -3 을 하면 2 1101은 1.101로 최종계산은 1.101 × 2의2승 = 110.1이되고 10진법으로는 6.5가 나오게된다.

원래 값 보다 작게 나온 이유는 표현할 수 있는 비트의 수가 실제 값보다 작기 때문이다.

실제로 프로그래밍 언어에는 최소 4바이트(32bit)를 사용하기 때문에 오차는 좀 더 작아진다.

그리고 어떻게 bit를 사용할지는 IEEE 754에서 표준을 정해두었다.

▶ 서론에서도 말했지만, 실수는 근사값으로 표현되기 때문에 항상 오차가 있을 수 있음을 알고 코드를 작성해야 한다. 정밀한 값이 필요한 경우에는 float 대신 int로 계산한 후 백분율, 만분율과 같은 것으로 가장 나중에 나눠서 사용하는 것을 추천한다.