[완료]float 에서 값이 정확하게 나오지 않네요.

bartsesang의 이미지

#include
#include

char InputOnlyFloat (char* input, float* value);
float Square (float, int);
char CheckNum (char value);

int main (int argc, char* acgv[])
{
int a, b, c;

float solve = NULL;
char ch_solve[10], ch_flag;

printf("aX^2 + bX + C = 0 의 해를 구한다.\n다음 값을 입력하시오.\n");

while(-1){
printf("a : ");
scanf("%s", &ch_solve);

ch_flag = InputOnlyFloat(ch_solve, &solve);
// 입력 문자의 숫자 여부와 숫자를 구한다. 문자의 경우 NULL이다.

if (ch_flag)
printf("%f\n", solve);
else
printf("NULL ERROR! - character\n");
// 입력 문자에 문자가 포함되었다.
}

getch();
return 0;
}

char InputOnlyFloat (char* input, float* value)
{
int in_count = 0;
int in_len = 0, int_len = 0, dec_len = 0, sign = 1;
char point = NULL, check_num;
*value = 0;

if(input[0] == '-')
{
sign = -1;
in_len++;
in_count++;
}
// 음수 여부 판단.

while (input[in_len] && !point)
{
check_num = CheckNum (input[in_len]);
// input[in_len]가 숫자인지 확인(문자면 NULL).

if (check_num)
{
in_len++;
int_len++;
}
// 숫자이다.
else
{
if (input[in_len] == '.')
{
point = 1;
in_len++;
}
// 소수점 여부 확인.
else
return NULL;
// 숫자 이외의 문자이다.
}
}
// 정수부분의 확인.

while (input[in_len])
{
check_num = CheckNum (input[in_len]);

if (check_num)
{
in_len++;
dec_len++;
}
else
return NULL;
}

for (; int_len > 0; int_len--, in_count++)
*value += Square(10, int_len - 1) * (input[in_count] - '0');
// 정수 부분 계산.

if (input[in_count] == '.')
{
in_count++;
// 소수점 문자에서 소수부분의 숫자로 넘어간다.

for (int dec_count = 1; dec_count <= dec_len; dec_count++, in_count++)
*value += Square(10, -dec_count) * (input[in_count] - '0');
// 소수점 부분 계산.
}

*value *= sign;
// 부호를 정한다.

return 1;
// 입력값은 숫자이다.
}

char CheckNum (char value)
// 문자의 값이 숫자인지 확인한다.
{
if (value < '0' || value > '9')
return NULL;
else
return value;
}

float Square (float base, int exp)
// 밑과 지수를 받아 제곱한 값을 리턴한다.
// 지수를 소수로 받게되면 제곱근의 경우를 생각해야 하므로 제외.
{
int count;
float value;

if (exp == 0)
return 1;
// 지수가 0이면 제곱은 1이다.

if (exp > 0)
{
value = base;
for (count = 2; count <= exp; count++)
value *= base;
}
// 지수가 양.

if (exp < 0)
{
value = base;
for (count = 2; count <= -exp; count++)
value *= base;
value = 1 / value;
}
// 지수가 음이면 제곱한 값의 역수를 취한다.

return value;
}

아직 완성하지 못한 프로그램이지만, 중간에 값이 정확한지 살펴봤는데 이상한 결과가 나옵니다. 지금까지 되어 있는 소스는 입력 문자열이 숫자로 되어있다면, float형으로 변환하는 과정입니다.(다른 문자가 들어올 때 에러를 방지) 하지만 문제는 21.3, 45.6 등과 같은 임의의 값을 넣을 때 21.299999 이렇게 표현되는데 디버거를 사용하여 살펴볼 때 디버거 상에도 값은 21.3으로 표현되고 있습니다.

--- printf.c --------------------------------------------------------------------------------------
printf:
00401470 push ebp
00401471 mov ebp,esp
00401473 sub esp,0Ch
00401476 push ebx
00401477 push esi
00401478 push edi
00401479 lea eax,[ebp+0Ch]
0040147C mov dword ptr [arglist],eax
0040147F cmp dword ptr [format],0
00401483 jne printf+33h (004014a3)
00401485 push offset string "format != NULL" (00427058)
0040148A push 0
0040148C push 36h
0040148E push offset string "printf.c" (00427068)
00401493 push 2
00401495 call _CrtDbgReport (00402ed0)
0040149A add esp,14h
0040149D cmp eax,1
004014A0 jne printf+33h (004014a3)
004014A2 int 3
004014A3 xor ecx,ecx
004014A5 test ecx,ecx
004014A7 jne printf+0Fh (0040147f)
004014A9 push offset __iob+20h (00429a88)
004014AE call _stbuf (00403570)
004014B3 add esp,4
004014B6 mov dword ptr [buffing],eax
004014B9 mov edx,dword ptr [arglist]
004014BC push edx
004014BD mov eax,dword ptr [format]
004014C0 push eax
004014C1 push offset __iob+20h (00429a88)
004014C6 call _output (00403790)
004014CB add esp,0Ch
004014CE mov dword ptr [retval],eax
004014D1 push offset __iob+20h (00429a88)
004014D6 mov ecx,dword ptr [buffing]
004014D9 push ecx
004014DA call _ftbuf (004036d0) <이 부분>
004014DF add esp,8
004014E2 mov eax,dword ptr [retval]
004014E5 pop edi
004014E6 pop esi
004014E7 pop ebx
004014E8 mov esp,ebp
004014EA pop ebp
004014EB ret
--- No source file --------------------------------------------------------------------------------

24: if (ch_flag)
004117D4 movsx ecx,byte ptr [ebp-20h]
004117D8 test ecx,ecx
004117DA je main+84h (004117f4)
25: printf("%f\n", solve);
004117DC fld dword ptr [ebp-10h]
004117DF sub esp,8
004117E2 fstp qword ptr [esp]
004117E5 push offset string "a^b = %f\n\n" (00428070)
004117EA call printf (00401470) <이 부분>
004117EF add esp,0Ch
26: else
004117F2 jmp main+91h (00411801)
27: printf("NULL ERROR! - character\n");
004117F4 push offset string "NULL ERROR! - character\n" (004280ac)
004117F9 call printf (00401470)
004117FE add esp,4

대충 짐작 가는 것은 부동소수점은 근사값이라는 것입니다. 하지만 여기서도 이해가 되지 않는 것이 유효범위 내에 있으며, 혹시나 하고 정수부분과 소수부분을 나누어 계산 후 더해도 같은 결과가 나온다는 것입니다. 이부분에대해 속 시원한 해답을 얻고 싶습니다.

geneven의 이미지

정상인것 같습니다.

어떤 시스템을 사용하신건지 모르겠는데 제 컴에서 테스트 할 경우 21.3은 처리하지 못하는것 같네요.

SoulreaveR의 이미지

우리의 그분은 애석하게도 0.1은 제대로 표현하지 못하죠 (묵념)

김일목의 이미지

float 값이나 double 값은 bit사용상 정확한 표현을 못하는 것으로 알고 있습니다.
그래서 항상 값의 비교를 할때 오차 범위를 정해주는 걸로 알고 있어요...
http://palgong.knu.ac.kr/~eleca/oz/1st_example.html
참고 하세요~

alee의 이미지

10진수 0.1을 2진수로 바꾸면,

0.1=1/10
=1/16+1/32+1/256+1/512+1/4096+···
=1/2^4+1/2^5+1/2^8+1/2^12+···
=0.000110010001···

이런식으로 무한소수가 됩니다.
따라서 컴퓨터에 이를 저장하기 위해서는 적당한 자리수에서 잘라낼 수 밖에 없습니다.
예를 들어서 소수점 아래 12자리에서 잘라낸 다음 저장했다면 실제로 저장된 값은,

0.000110010001
=1/2^4+1/2^5+1/2^8+1/2^12
=401/4096
=0.0979004

과 같이 0.1이 아니라 0.0979···가 됩니다. 따라서 저장하는 자리수를 아무리 높이도
우리가 2진수를 사용하는 이상 float형 변수에서는 0.1을 정확하게 표기할 수 없습니다.

10진수에서 정확하게 적을 수 있는 수는 1/10^n의 합으로 나타낼 수 있는 수인 데 반해
2진수에서 정확하게 적을 수 있는 수는 1/2^n의 합으로 나타낼 수 있는 수입니다.
따라서 float형 변수에서는 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875··· 등의
숫자가 아닌 이상 항상 근사값이 사용됩니다.

bartsesang의 이미지

요 몇 일 동안 계속 이걸 붙잡고 여러 소스를 짜봤습니다. 윗 분들 답변이 도움이 크게 됐네요. 제가 알게 된 것은 float나 double같은 경우 부동소수점 방식입니다. [부호] [지수] [가수]의 형태로 구성되지요. 그런데 오차가 생기는 범위는 [가수] 범위입니다. 만약 정수 형태의 데이터 같은 경우는 어느 범위까지는 정확한 값이 입력될 것입니다. 자릿수가 일정하니깐요. 하지만 소수점 같은 경우는 2^(음수)로 표현되는데 윗분 설명처럼 2^(음수)으로 딱 떨어지는 경우가 아니면 (제가 구해본) 모든 결과가 무한 소수형태로 떨어집니다.

간단하게 2진수를 10진수로 변환하는 경우를 생각해봅니다. 0.1, 0.2, 0.3, 0.4, 0.5, ...., 0.9를 2진수로 표현하면 0.5의 경우만 0.1(binary)로 표현되고 나머지는 반복되는 무한소수의 형태입니다. 이것이 가수부분에서 잘려버리고, 다시 10진수로 환산했을 때 오차가 생기는 것입니다.

대신 좋은 점도 있습니다. long int와 float를 비교하면 모두 32bit의 데이터형이지만 표현 범위는 엄청나게 차이난다는 것입니다.

이런 것은 C언어를 배우는 거의 앞부분에서 다루는 내용이지만 이런 기초적인 부분을 그려러니하고 그냥 지나친듯 합니다. 아직 걸음마 단계지만 갈길이 너무 멀다고 느껴지네요.

특히 한참 고민하다가 어제 float형에 대해 기술한 웹상 강의노트를 보고 충격받았습니다. 고민했던 내용이 단 몇 쪽의 문서에 다있는 것입니다. float형에서 지수부분이 왜 저 값으로 표현되는가 2~3일간 의문을 품고 고민하고 있었는데 바이어스된 표현이라는 것도 그 개념도 처음 알았습니다. 아직 갈길이 멉니다. ㅎㅎ

하나를 알면 열이 궁금해요.. 털썩... OTL

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.