보고서/10월 업무일지

10월 4일 업무일지

박잡스 2010. 10. 4. 15:54

[c언어] 실수 표현 문제 발생 이유 or 오차 발생 이유 !


다음 코드를 작성하고 출력 결과를 확인해보자!

#include <stdio.h>

int main(void)
{
     int no = 12;
     float ave =324.1234f;

     printf(" %d + %f = %f \n", no, ave, no+ave);

     printf(" %d + %10.4f = %10.5f \n", no, ave, no+ave); //출력 자리수 조절.

    return 0;
}

실행한 결과 화면은 다음과 같다.

사용자 삽입 이미지


우리는 변수 ave에 324.1234를 할당했는데, 출력 결과에서는 324.123413으로 나온다.

소수 부분에 값이 추가 되었다.

컴파일 과정이나 실행과정에서 error 나 warning은 발견되지 않았다.



float의 문제일까?

double로 작성된 다음 예제를 살펴보자.


#include <stdio.h>

int main(void)
{
    float   pi1 = 3.141592653589793f;
    double  pi2 = 3.141592653589793;

    printf(" float  형의 pi 값 : %f\n", pi1);
    printf(" double 형의 pi 값 : %f\n", pi2);

    printf(" float  형의 pi 값 : %30.25f\n", pi1);
    printf(" double 형의 pi 값 : %30.25f\n", pi2);

    return 0;
}



float는 지원하는 자리수 보다 큰범위의 소수점 범위가 저장되어, 소수점 7번째 자리부터 잘못된 값을 출력하고 있고,

double는 float에 비해 더 큰 소수점 범위를 지원하므로 3.1315926589793 까지는 정확하나 1이 더 붙어서 출력되었다!


왜 이런 문제가 발생하는가?


컴퓨터에서 10진수를 2진수로 변환할 때 발생하는 구조적인 문제이다.




- 먼저 실수의 표현 방식 부터 확인하자.

실수는 다음과 같이 두가징 방식을 사용해 표현한다.


고정 소수점은 평소에 우리가 사용하는 방식이고,

부동 소수점은 10진수 형태로 실수를 저장할 수 없는 컴퓨터에서 2진수를 이용한 부동 소수점 방식을 통하여 실수를 저장하고 사용하게 된다.



- 실수 저장 형식은 다음과 같다.



float type은 4byte(32bit)의 공간에 실수를 저장한다.

부호(sign) 비트 : 1bit, 양수 0, 음수 1
지수(exponent) 비트 : 8bit, 지수에 127을 더해서 저장
가수(fraction, significand, manitissa) 비트 : 23bit, 가수를 저장. 이때 맨 앞의 1은 저장하지 않는다.



- 실수 1.0을 2진수로 변환하고, float type으로 저장되는 과정

부동소수점 방식 -> 1.0 * 2^0

부호 비트(1bit) : 0
지수 비트(8bit) : 0 + 127 = 127, 이진수 0111 1111
                       (현재는 지수가 0 그리고, 지수에 127을 더해서 저장)

※ 127을 더하는 이유 :  0.0345는 양수이다. 하지만, 지수로 표현하면 3.45 * 10^ -2 이므로 지수가 음수가 된다. 따라서, 8bit를 사용하는 지수부분에서 부호bit를 만드는 번거로움을 피하기 위해, 기본적으로 127을 더한다.

가수비트(23bit) : 1, 그러나 1은 저장하지 않는다.
                         (2진수 부동소수점 표현에서는 맨처음이 반드시 1이다. 따라서 1을 저장하지 않음.)
                        000 0000 0000 0000 0000 0000

따라서 1.0의 2진수 float type 표현은  0 011 1111 1000 0000 0000 0000 0000 0000

--------------------------------------------------------------------------------------
비교 : 정수 1이 4byte 공간에 저장될 때는 1byte는 부호 bit, 나머지는 데이터 저장에 쓰인다.

따라서, 정수 1은 0 000 0000 0000 0000 0000 0000 0000 0001 로 저장된다.

* 정수 -1의 2진수 형태.
첫째, 정수 1의 각 bit를 반전시킨다.        1 111 1111 1111 1111 1111 1111 1111 1110
둘째, 반전시킨 2진수 값에 1을 더한다.    1 111 1111 1111 1111 1111 1111 1111 1111
이를 2의 보수 방법이라 한다. 최상위 bit 1음 -(음수)를 의미한다.
--------------------------------------------------------------------------------------


- 실수 17.175 이진수로 변환하고, float type으로 저장되는 과정

먼저 정수부분과 소수부분 분리한다.

정수 17의 2진수 표현 : 10001

소수 0.175 이진수 표현
- 소수에 2를 곱하고 결과가 1.0을 넘으면 1 아니면 0
- 1.0이 넘었으면 1을 뺀수에 다시 2를 곱한 결과가 1을 넘으면 1, 넘지 않으면 0
- 계속 반복다하가 1.0 이 나오면 정지. 그렇지 않으면 반복

1. 0.175 * 2 = 0.35  ☞  1.0 이하 0
2. 0.35 * 2 = 0.7 ☞ 0
3. 0.7 *2 = 1.4 ☞ 1.0 넘었으므로 1
4. (1.4-1.0) * 2 = 0.8 ☞ 0
5. 0.6 * 2 = 1.2 ☞ 1
6. (1.2-1.0) *2 = 0.4 ☞ 0
7. 0.4 * 2 = 0.8 ☞ 0
8. 0.8 * 2 = 1.6 ☞ 1
9. (1.6-1.0) * 2 = 1.2 ☞ 1
10.  0.6 * 2 = 1.2 ☞ 1

11. 0.2 * 2 = 0.4 (0)
12. 0.4 * 2 = 0.8 (0)
13. 0.8 * 2 = 1.6 (1)
14. 0.6 * 2 = 1.2 (1)
15. 0.2 * 2 = 0.4 (0)
16. 0.4 * 2 = 0.8 (0)
17. 0.8 * 2 = 1.6 (1)
18. 0.6 * 2 = 1.2 (1)
19. 0.2 * 2 = 0.4 (0)
20. 0.4 * 2 = 0.8 (0)

21. 0.8 * 2 = 1.6 (1)
22. 0.6 * 2 = 1.2 (1)
23. 0.2 * 2 = 0.4 (0)
24. 0.4 * 2 = 0.8 (0)


0.175의 2진수 표현 :  0010 1001 1100 1100 1100 110 …

17.175의 2진수 표현 : 10001.0010 1001 1100 1100 1100 110 …

컴퓨터에서 실수를 저장하는 형식인 2진수 부동소수점 방식(1.xxx * 2^n)으로 변환하자.
10001.0010 1001 1100 1100 1100 110 는
1.0001 0010 1001 1100 1100 1100 110 * 2^4 으로 표현된다.

부호 1bit : 0
지수 8bit : 4 + 127 = 131 -> 1000 0011
가수 23bit : 앞에 1빼고 -> 0001 0010 1001 1100 1100 1100 110

따라서, 17.175의 float type 으로 저장된 형태는 다음과 같다.
 0 1000 0011 0001 0010 1001 1100 1100 1100 110



- 실수 0.1 을 2진수로 변환하고, float type으로 저장되는 과정

정수부분 0 : 0
소 부분 0.1 : 2진수로 변환.

1. 0.1 * 2 = 0.2 (0)
2. 0.2 * 2 = 0.4 (0)
3. 0.4 * 2 = 0.8 (0)
4. 0.8 * 2 = 1.6 (1)
5. 0.6 * 2 = 1.2 (1)
6. 0.2 * 2 = 0.4 (0)
7. 0.4 * 2 = 0.8 (0)
8. 0.8 * 2 = 1.6 (1)
9. 0.6 * 2 = 1.2 (1)
10. 0.2 * 2 = 0.4 (0)

11. 0.4 * 2 = 0.8 (0)
12. 0.8 * 2 = 1.6 (1)
13. 0.6 * 2 = 1.2 (1)
14. 0.2 * 2 = 0.4 (0)

15. 0.4 * 2 = 0.8 (0)
16. 0.8 * 2 = 1.6 (1)
17. 0.6 * 2 = 1.2 (1)
18. 0.2 * 2 = 0.4 (0)

19. 0.4 * 2 = 0.8 (0)
20. 0.8 * 2 = 1.6 (1)
21. 0.6 * 2 = 1.2 (1)
22. 0.2 * 2 = 0.4 (0)

23. 0.4 * 2 = 0.8 (0)
24. 0.8 * 2 = 1.6 (1)
25. 0.6 * 2 = 1.2 (1)
26. 0.2 * 2 = 0.4 (0)

27. 0.4 * 2 = 0.8 (0)
28. 0.8 * 2 = 1.6 (1)
29. 0.6 * 2 = 1.2 (1)
30. 0.2 * 2 = 0.4 (0)

31. 0.4 * 2 = 0.8 (0)
32. 0.8 * 2 = 1.6 (1)
33. 0.6 * 2 = 1.2 (1)
34. 0.2 * 2 = 0.4 (0)


0.0001 1001 1001 1001 1001 1001 1001 1001…

위와 같은 값을 얻었다. 위 표현을 부동 소수점 형태로 변경하면,

1.1001 1001 1001 1001 1001 1001 1001… * 2^ -4

가수는 23bit만 저장할 수 있다. 따라서 무한 반복되는 값을 사용할 수 없다.
부동 소수점 형태에서 소수점 24자리에서 반올림 하게 된다.

이때, 0.1은 내부적으로 0.1보다 큰 값으로 저장되게 된다.

이로인해 구조적인 오차문제가 발생하게 된다.

부호 1bit : 0
지수 8bit : -4 + 127 = 123 -> 0111 1011
가수 23bit : 1001 1001 1001 1001 1001 101

결국, 0.1은 다음과 같은 float type으로 저장된다.
0 0111 1011 1001 1001 1001 1001 1001 101

 

오차 없이 실수를 사용하고 싶을 땐

1. float의 가수 정밀도는 6~7 자리.

2. double의 가수 정밀도는 15~16자리이다

정밀도를 올리는 방법


//실수를 화면에 출력하는 프로그램

#include <stdio.h>

int main()
{
  float fnumber=45000.67;  //소수점 이하는 2진수로 변환이 잘 않된다.

  printf("%f\n",fnumber);
  printf("%9.4f\n",fnumber);  //9는 전체자리수 이고 4는 소수부분이다.
                                최소자리수가 9이므로 145000.67도 다표시 된다.
  printf("%e\n",fnumber);
  return 0;
}


ex)16.000 과 16은 다르게 취급된다.

16.000은 16.000까지는 정확한 수이며 그뒤의 자리부터 바뀐다는 말이며

16은 16까지가 정확한 수라고 하는 것 이다.


지금 사용하는 컴파일러 에서는 int , long 이 4바이트로 같지만 다를수도 있다.


문자형

아스키코드

1바이트내의 7비트만들 이용해서(양수) 0에서 127까지의 128개의 문자로 구성되어 있다.

후에 1비트를 포함하여 256문자로 확장하였다.(한자나 한글 등등 사용) - 확장 아스키 코드

아스키 코드 이후 세계 각국의 문자를 반영하기 위해 한 문자를 2바이트에 넣어 사용하는 unicode 가 나왔다.

(65535개) "http://unicode.org"


printf("     \"      ",fnumber);  //여기서 \" 중에 \는 메타문자라고 하여 뒤에오는 "를 있는그대로

나타내라는 말이며 \n 처럼 특정한 기능을하는 Escape sequence

로도 사용된다.