요약 : 10->2진법 변환 한계로 소수 표현이 제대로 안되어서 연산 후 결과를 비트연산하게되면 다른값이 나올 수 있다.
가끔 소수점 단위 연산을 해야하는 경우가 있다. 예를 들어 0.1초마다 주위를 스캔한다던지, 무게를 0.25초마다 측정한다던지 하는 경우 말이다. 이런 경우 특별한 조치를 취해주지 않는다면 오류가 발생할 가능성이 있다.
예를 들어 이 사진의 경우 1.1 + 0.1이 1.2와 동일한지를 계산하는 코드이다. 상식대로라면 동일하다는 결과가 나와야 하지만 결과는 보다시피 거짓이 나오게 된다.
어디가 문제일까?
컴퓨터는 숫자를 저장할 때 바이트 단위로 수를 집어넣는다. 실수를 저장할때는 주로 float, double을 사용하는데, float는 4바이트, double은 8바이트라는 차이점이 있을 뿐이다.
이 바이트를 자세히 뜯어보자.
우리가 이 문제를 해결하기 위해선 1바이트가 8비트로 구성이 되고, 여기에 수를 집어넣을 때 2진법을 사용한다는 점에 주목해볼 필요가 있다.
숫자 3의 경우, 1바이트에 표기하게 되면 0000 0011로 표현할 수 있다.
4의 경우에는 0000 0100이 된다. 만약 8을 표현하고 싶다면? 0000 1000이 된다.
즉, 2의 지수단위로 수를 표현할 수 있는 값이 늘어난다.
따라서 4바이트의 경우 2의 32승 - 1. 즉, 2,147,483,647까지 표현이 가능하다는 의미이다.
실수의 경우에는 이야기가 조금 달라진다. 가령 23.17을 float값으로 저장을 한다고 해보자.
float는 4바이트로 구성되어 있고, 4바이트는 32비트이다.
실수의 경우에는 부호비트 1비트, 지수부 8비트, 가수부 23비트로 구성된다.
먼저 23.17을 이진수로 변환을 해보자. 23을 이진수로 변환하게 되면 10111이 된다.
0.17의 경우 이진수로 표현하게 되면 0.0010110011... 이런식으로 된다.
이제 둘을 합하게 되면 10111.0010110011...이 된다.
이제 이진수를 정규화 할 차례이다. 1의 자리만을 남기고 나머지를 소수화 시키는 과정이다.
10111.0010110011을 정규화 하게 되면,
1.01110010110011 X 2^4가 된다.
정규화 과정에서 지수가 4가 나왔기에, 편향값 127을 더해주면 131이라는 수가 튀어나오게 된다.
이 131또한 이진수 변환을 하면 1000 0011이 된다.
마지막으로 정규화 시킨 이진수의 소수점 아래 부분을 23비트로 맞춰주면
01110010110011001100110이 된다.
최종적으로 모든 비트를 합치게 되면
부호 비트 : 0 (양수)
지수부 : 1000 0011
가수부 : 0111 0010 1100 1100 1100 110
따라서 23.17을 float로 변환하게 되면 0100 0001 1011 1001 0110 0110 0110 0110이 된다.
그래서 왜 저런 문제가 발생하는가?
0.125같은 경우에는 이진수로 딱 떨어지지만, 0.1같은 경우에는 이진수로 깔끔하게 정리되지 않고 1100 1100... 이런식으로 계속해서 순환하게 되는 문제가 있다. 방금 위에서 이진법으로 변환한 23.17도 같은 케이스이다. 무한히 반복되는 소수를 23부분에서 절사한 셈이다. 그 자른 부분이 오차가 된다.
해결 방안
1. 소수에 100이나 1000같은 수를 곱해서 정수로 저장했다가 마지막에 다시 소수로 변환해준다.
2. 소수를 어쩔수 없이 써야한다면 반올림 문법을 이용한다. Math.pow( ), Math.ceil()이나 roundUp()함수를 이용하면 된다.
3. 소수를 더 정확히 저장하고 싶다면 double 자료형을 사용해주면 된다.
'Programming > C C# C++' 카테고리의 다른 글
C#) 캐스팅, 박싱, 언박싱 (0) | 2024.11.14 |
---|---|
C#) 값 형식과 참조 형식 (0) | 2024.11.13 |
C#) Null과 Void의 차이 (0) | 2024.11.13 |
C#) 문자열 입력 시 변수를 추가하는 방법 (0) | 2024.11.08 |
C#과 C++에서의 for, foreach, for루프 사용법 (0) | 2024.10.04 |