2011년 9월 10일 토요일

C언어 float와 double 변수 - Float Point 처리

1. Float Point 처리 변수 float double

세상의 많은 것을 표현하기 위해 우리는 숫자 중에서 실수를 사용 한다. c에서 이것을 표현하기 위해 float double가 있다. 실제로 프로그램 하다 보다 그렇게 많이 사용되지는 않지만 수학관련 프로그램에서는 당연히 중요한 변수이다. 이 변수에서 숫자의 표현 방식은 국제 표준이 IEEE에 명시된 것으로 컴파일 되는 것이 보통이다. 컴파일러의 설계자가 별도로 다시 정할 필요도 별로 없을 것이다. 자세한 것은 IEEE-754에 규정되어 있다.“ANSI IEEE Standard for Binary Floating-Point Arithmetic”을 참고할 수 있다. 연산은 4칙 연산은 기본적으로 정수형과 같이 ‘+ - * /’로 표현할 수 있고, 수학적인 것은 함수로 제공 된다. 수학 계산에 필요한 기본적인 것은 왠 만한 컴파일러는 가지고 있다.


2. CPU 입장에서 본 float double

이 변수를 처리하기 위해 CPU에서는 어떻게 처리 할까를 보기 위해서는 CPU ALU을 보아야 한다. 현재 나와있는 대부분은 이 float point처리를 하지 못한다. 예를 들어 PC에 많이 사용하는 인텔 80X86에서도 CPU내의 모듈에서도 처리 할 수 없다. 이것은 기본적으로 CPU가 정수형 ALU을 사용하고 있는 것이 대부분이라는 것이다. 수학적으로 고속 처리는 그럼 어떻게 하는가? 이것은 DSP 처리용 CPU가 별도로 판매된다. 가장 대표적인 것이 TI TMS32 시리즈이다. DSP 처리용 CPU ALU float point 계산을 하드웨어적으로 하고 있어 고속 수학 연산이 가능하다. 그러면 인텔의 80x86 시리즈는 어떻게 처리 하는가? 이것은 2가지 방식을 생각할 수 있다.
l        float point을 정수형 ALU을 사용하여 처리하는 함수를 작성하고 이것을 호출하여 계산한다. 그런데 이것은 여러 줄의 기계어 코드가 필요하므로 처리 속도가 늦다는 단점과 많은 기계어 코드가 필요하다.
l        보조 프로세서를 사용한다. 이것을 co-processor(FPU-Floating Point Unit)라고 부른다. 이 코프로세서는 수학연산을 하드웨어 적으로 제공하므로 써 처리 속도를 높인다. 32비트 80386의 메인보드를 보면 별도로 코프로세서를 칩을 장착한 것을 확인할 수 있다. 이 말은 별도의 칩으로 나와있고 이것이 메인보드상에 결합된 것이다. 그래서 인텔에서는 이것을 하나의 칩으로 통합했는데, 이것이 80486 CPU이다. 386 486(파이프 개념 도입, FPU 한 다이에 통합)의 차이는 CPU의 비트수가 올라간 것이 아니고 32로 유지되어 있음을 알아야 한다. 가끔 486 64비트가 아닌가 착각하는 경우가 드물게 있다. 586,686 역시 64비트로 알면 안 된다. 그러나 코프로세서가 한 칩 안에 있다고 DSP 칩과 같은 것은 아니다. 이것은 분명 다른 메카니즘으로 작용하고 있다는 것을 알아야 한다. 그래서 80계열의 코프로세서를 사용하기 위해 기계어 코드가 별도로 만들어 졌다. 어셈블러를 보면 FADD,FMUL, ... F로 시작하는 기계어 코드는 CPU가 계산하는 것이 아니라 코프로세서로 넘겨 계산 하고 그 결과로 CPU로 가져와 사용한다. 물론 정수형 계산 기계어 ADD등은 기본적으로 정수형인 ALU을 통해 직접 계산된다.

컴파일러를 보면 처리 방식을 함수로 할 것인지 코프로세서로 할 것 인지를 설정할 수 있다. 물론 인텔과 쌍벽을 이루고 있었던 모토롤라의 68계열도 이와 비슷한 변화 과정을 거쳐 발전 해 왔다. 그러면 8비트 계열의 CPU MCU는 대부분 코프로세서가 없는 것이 대부분이다. 따라서 이것은 float을 위한 함수를 제공하는 경우가 있다. 그것은 대부분 32비트인 float만을 제공한다. 한번은 32비트의 float double로 표시되어 64비트 인 것으로 판단하여 문제가 발생하여 고생한 적이 있어 씁쓸하다. float double은 국제 표준으로 정해져 있기 때문에 double하면 64비트로 머리 속에 박혀 있던 것이다. 아직도 float로 표시하면 될 것을 왜 굳이 double로 표시 했는지 사기 당한 기분이다. float이라도 있으면 다행인데 이것 마저 없는 컴파일러가 더 많으므로 주의 해야 한다. 64비트 형인 double 8비트 계열에는 한번도 본적이 없다. 물론 모든 컴파일러를 사용한 것은 아니지만. 이 문제는 계산 결과의 자리수 문제와 연결된다. 다음에 언급하겠지만 32비트 float는 숫자를 나타내는 유효 자리수가 7자리 정도이고 64비트의 double 16자리 정도 밖에 안됨을 알고 있어야 한다.

float double의 표현

표현은 “ANSI IEEE Standard for Binary Floating-Point Arithmetic”에서 표현되어 있어 표준화를 이해 하면 된다.

숫자 표현

l        부호 : S +,-로 부호를 나타낸다. 1이면 이고 0이면 +이다.
l        exp : 지수부로 2진수의 지수 이다. 지수부도 부호가 있다.
l        fraction :  숫자를 나타낸다. 1보다 작은 숫자를 나타 낸다. 2**(-1) + 2**(-2) + ...
전체적은 다음과 같은 식으로 표현 한다.

 (-1) ** S * (1.0 + fraction ) * 2**exp

예를 들어 10진수 1.625 2진수로는 1.101로 나타낼 수 있다.
이것은
2**0 + [2**(-1) + 2**(-3)]fraction
= 1  + [0.5+0.125]fraction = 1.625
으로 표현할 수 있다.

값변환
Range of Floating-Point Types - Visual C++ 6.0 help
Type
Minimum value
Maximum value
float
1.175494351 E – 38
3.402823466 E + 38
double
2.2250738585072014 E – 308
1.7976931348623158 E + 308


float 표현 (single-precision)

지수부는 8비트에서 +,-을 모두 표현하려면 0x00 ~ 0xFF까지 중에서 부호를 결정하는 방식을 선택해야 한다. 이것은 정수형 처럼 2의 보수체계를 사용하지 않고 중간값을 기준으로 0으로 하고 큰쪽을 +로 한다. 이를 간단히 계산하면
 Exp – 0x7F
2의 지수로 한다.

다음과 같은 예를 생각 하면
1.0 : 0 011 1111 1000 0000 0000 0000 0000 0000
지수부에서 011 1111 1 0x7F 이므로
0x7F - 0x7F = 0
이것은 2**0 = 1이다.
이것을 전체 공식에 넣으면
(-1) ** 0 * (1.0 + 0 ) * 1  = 1.0

2번째 예에서
0.75 : 0 011 1111 0100 0000 0000 0000 0000 0000
(-1)**0 * (1.0+2**(-1)) * 2**(0x7E-0x7F)
= 1 * (1.0+0.5) * 2**(-1)
= 1 * (1.5) * 0.5
= 0.75 

그러면 실제로 숫자를 표현할 때 한계는 1.18*10**(-38) ~ 3.4*10**38 이다. 그런데 여기서 숫자 중에서 숫자 자릿수는 7자리만이 유효 하다는 한계를 인식하고 있어야 한다. 이것은 간단히 생각하면 fraction 부분이 23자리로 부터 간단히 유도할 수 있다. 이것으로 구분하는 것이 숫자의 자릿수이므로 2**23=8,388,608의 가지 수를 구별해 낼 수 있다. 8,388,6086자리 만으로 생각할 수 있다. 여기에 1+ fraction와 지수부에서 오는 숫자를 고려하면 대략 7자리 까지는 보장이 된다. 정확한 범위는 수학적으로 계산을 정확히 해야 하지만 여기서는 이 정도의 한계를 집고 넘어간다. 나의 수학적 한계가 여기까지...
따라서 다음과 같은 숫자에서 문제되는 것은
+0.235032543535 * 10**23
2350325는 유효하지만 43535은 나타낼 수 없게 된다. 만약 다음과 같이 print 한다면

float fnum;
fnum = 0.235032543535E23;
printf(“%1.12f”, fnum);

이와 같은 상황이라면 실제로 fnum의 숫자가 0.23503254E23와 같이 입력한 것과 같은 결과이며 뒤의 숫자 43535는 모두 없어지는 결과가 된다. 물론 출력은 모두 되지만 뒤의 숫자는 임의의 숫자임을 알아야 한다.

#include <stdio.h>

int main(int argc, char* argv[])
{
  float fnum;

    printf("float fnum = 0.235032543535E23;\n");
    fnum = 0.235032543535E23;
    printf("%1.12e\n", fnum);
  
return 0;
}

VisualC++6.0 컴파일 결과 :
-----------Configuration: limitreal - Win32 Debug------------
Compiling...
limitreal.cpp
D:\limitreal\limitreal.cpp(12) : warning C4305: '=' : truncation from 'const double' to 'float'
Linking...

limitreal.exe - 0 error(s), 1 warning(s)

실행 결과 :
float fnum = 0.235032543535E23;
2.350325513093e+022

기타 특수한 숫자

  0 00000000 00000000000000000000000 = 0
  1 00000000 00000000000000000000000 = -0

  0 11111111 00000000000000000000000 = Infinity
  1 11111111 00000000000000000000000 = -Infinity

  0 11111111 00000100000000000000000 = NaN
  1 11111111 00100010001001010101010 = NaN

  0 10000000 00000000000000000000000 = +1 * 2**(128-127) * 1.0 = 2
  0 10000001 10100000000000000000000 = +1 * 2**(129-127) * 1.101 = 6.5
  1 10000001 10100000000000000000000 = -1 * 2**(129-127) * 1.101 = -6.5

  0 00000001 00000000000000000000000 = +1 * 2**(1-127) * 1.0 = 2**(-126)
  0 00000000 10000000000000000000000 = +1 * 2**(-126) * 0.1 = 2**(-127)
  0 00000000 00000000000000000000001 = +1 * 2**(-126) *
                                       0.00000000000000000000001 =
                                       2**(-149)  (Smallest positive value)

double 표현 (double-precision)

float 32비트 이라면 64비트로 확장한 것이 double이다. 계산 방식은 같으나 각 숫자요소가 확장되어 있으므로 이것만 고려하면 된다.

지수부는 11비트 이므로
Exp – 0x3FF
2의 지수가 되어 다음과 같이 계산 된다.

0.75 : 0 011 1111 1110 1000 0000 0000 0000 0000 0000 ... 0000

(-1)**0 * (1.0+2**(-1)) * 2**(0x3FE-0x3FF)
= 1 * (1.0+0.5) * 2**(-1)
= 1 * (1.5) * 0.5
= 0.75 

숫자의 한계를 알기 위해 다음과 같이 프로그램 하였다.

#include <stdio.h>

int main(int argc, char* argv[])
{
  float fnum;

    printf("float fnum = 0.235032543535E23;\n");
    fnum = 0.235032543535E23;
    printf("%0.20e\n", fnum);

    printf("float fnum = 0.123456789012345E23;\n");
    fnum = 0.123456789012345E23;
    printf("%0.20e\n", fnum);
    printf("float fnum = 0.123451234512345E10;\n");
    fnum = 0.123451234512345E10;
    printf("%0.20e\n", fnum);
    printf("\n");

  double dnum;
    printf("double dnum = 0.123456789012345E23;\n");
    dnum = 0.123456789012345E23;
    printf("%0.12le\n", dnum);
    printf("double dnum = 0.123456789012345678901234567890E3;\n");
    dnum = 0.123456789012345678901234567890E3;
    printf("%.20le\n", dnum);

        return 0;
}


float fnum = 0.235032543535E23;
2.35032551309319510000e+022
float fnum = 0.123456789012345E23;
1.23456793779139080000e+022
float fnum = 0.123451234512345E10;
1.23451238400000000000e+009

double dnum = 0.123456789012345E23;
1.234567890123e+022
double dnum = 0.123456789012345678901234567890E3;
1.23456789012345680000e+002


http://babbage.cs.qc.edu

Table 1: Expanded Summary of Format Parameters
No.ParameterFormat

Single
Single
Extended

Double
Double
Extended

Quadruple?+

Extended?#
(1)p   (precision,
  apparent mantissa width in bits)
24≥ 3253≥ 6411364
(2)Decimal digits of precision
  p / log2(10)
7.22≥ 9.6315.95≥ 19.2634.0119.26
(3)Mantissa’s MS-Bithidden bitunspecifiedhidden bitunspecifiedhidden bitexplicit bit
(4)Actual mantissa width in bits23≥ 3152≥?6311264
(5)Emax+127≥ +1023+1023≥ +16383+16383+16383
(6)Emin-126≤ -1022-1022≤ -16382-16382-16382
(7)Exponent bias+127unspecified+1023unspecified+16383+16383
(8)Exponent width in bits8≥ 1111≥ 151515
(9)Sign width in bits111111
(10)Format width in bits
  (9) + (8) + (4)
32≥ 4364≥ 7912880
(11)Range Magnitude Maximum
  2Emax?+?1
3.4028E+38≥ 1.7976E+3081.7976E+308≥ 1.1897E+49321.1897E+49321.1897E+4932
(12)Range Magnitude Minimum
  2Emin
1.1754E-38≤ 2.2250E-3082.2250E-308≤?3.3621E-49323.3621E-49323.3621E-4932
(13)Range Magnitude Minimum
 (Denormalized)
??2Emin?-?(4)
1.4012E-45≤ 1.0361E-3174.9406E-324≤ 3.6451E-49516.4751E-49661.8225E-4951
(14)FORTRAN Language TypeREAL*4?REAL*8?REAL*16REAL*10
(15)C Language Typefloat?double?long doublelong double


32-bit Single Precision
Range NameSign (s)
1 [31]
Exponent (e)
8 [30-23]
Mantiss a(m)
23 [22-0]
Hexadecimal RangeRangeDecimal Range §
Quiet
-NaN
111..1111..11
:
10..01
FFFFFFFF
:
FFC00001
??
Indeterminate111..1110..00FFC00000??
Signaling
-NaN
111..1101..11
:
00..01
FFBFFFFF
:
FF800001
??
-Infinity
(Negative Overflow)
111..1100..00FF800000< -(2-2-23) × 2127≤ -3.4028235677973365E+38
Negative Normalized
-1.m × 2(e-127)
111..10
:
00..01
11..11
:
00..00
FF7FFFFF
:
80800000
-(2-2-23) × 2127
:
-2-126
-3.4028234663852886E+38
:
-1.1754943508222875E-38
Negative Denormalized
-0.m × 2(-126)
100..0011..11
:
00..01
807FFFFF
:
80000001
-(1-2-23) × 2-126
:
-2-149
(-(1+2-52) × 2-150)?*
-1.1754942106924411E-38
:
-1.4012984643248170E-45
(-7.0064923216240862E-46)?*
Negative Underflow100..0000..0080000000-2-150
:
< -0
-7.0064923216240861E-46
:
< -0
-0100..0000..0080000000-0-0
+0000..0000..000000000000
Positive Underflow000..0000..0000000000> 0
:
2-150
> 0
:
7.0064923216240861E-46
Positive Denormalized
0.m × 2(-126)
000..0000..01
:
11..11
00000001
:
007FFFFF
((1+2-52) × 2-150)?*
2-149
:
(1-2-23) × 2-126
(7.0064923216240862E-46)?*
1.4012984643248170E-45
:
1.1754942106924411E-38
Positive Normalized
1.m × 2(e-127)
000..01
:
11..10
00..00
:
11..11
00800000
:
7F7FFFFF
2-126
:
(2-2-23) × 2127
1.1754943508222875E-38
:
3.4028234663852886E+38
+Infinity
(Positive Overflow)
011..1100..007F800000> (2-2-23) × 2127≥?3.4028235677973365E+38
Signaling
+NaN
011..1100..01
:
01..11
7F800001
:
7FBFFFFF
??
Quiet
+NaN
011..1110..00
:
11..11
7FC00000
:
7FFFFFFF
??


64-bit Double Precision
Range NameSign(s)
1 [63]
Exponent (e)
11 [62-52]
Mantissa (m)
52 [51-0]
Hexadecimal RangeRangeDecimal Range §
Quiet
-NaN
111..1111..11
:
10..01
FFFFFFFFFFFFFFFF
:
FFF8000000000001
??
Indeterminate111..1110..00FFF8000000000000??
Signaling
-NaN
111..1101..11
:
00..01
FFF7FFFFFFFFFFFF
:
FFF0000000000001
??
-Infinity
(Negative Overflow)
111..1100..00FFF0000000000000< -(2-2-52) × 21023≤ -1.7976931348623158E+308
Negative Normalized
-1.m × 2(e-1023)
111..10
:
00..01
11..11
:
00..00
FFEFFFFFFFFFFFFF
:
8010000000000000
-(2-2-52) × 21023
:
-2-1022
-1.7976931348623157E+308
:
-2.2250738585072014E-308
Negative Denormalized
-0.m × 2(-1022)
100..0011..11
:
00..01
800FFFFFFFFFFFFF
:
8000000000000001
-(1-2-52) × 2-1022
:
-2-1074
(-(1+2-52) × 2-1075)?*
-2.2250738585072010E-308
:
-4.9406564584124654E-324
(-2.4703282292062328E-324)?*
Negative Underflow100..0000..008000000000000000-2-1075
:
< -0
-2.4703282292062327E-324
:
< -0
-0100..0000..008000000000000000-0-0
+0000..0000..00000000000000000000
Positive Underflow000..0000..000000000000000000> 0
:
2-1075
> 0
:
2.4703282292062327E-324
Positive Denormalized
0.m × 2(-1022)
000..0000..01
:
11..11
0000000000000001
:
000FFFFFFFFFFFFF
((1+2-52) × 2-1075)?*
2-1074
:
(1-2-52) × 2-1022
(2.4703282292062328E-324)?*
4.9406564584124654E-324
:
2.2250738585072010E-308
Positive Normalized
1.m × 2(e-1023)
000..01
:
11..10
00..00
:
11..11
0010000000000000
:
7FEFFFFFFFFFFFFF
2-1022
:
(2-2-52) × 21023
2.2250738585072014E-308
:
1.7976931348623157E+308
+Infinity
(Positive Overflow)
011..1100..007FF0000000000000> (2-2-52) × 21023≥ 1.7976931348623158E+308
Signaling
+NaN
011..1100..01
:
01..11
7FF0000000000001
:
7FF7FFFFFFFFFFFF
??
Quiet
+NaN
011..1110..00
:
11..11
7FF8000000000000
:
7FFFFFFFFFFFFFFF
??
§ Your least significant digits may differ.


3. 수학계산 함수

컴파일러는 수학 계산을 위해서는 함수를 lib로 지원하는데 일반적인 공학 계산은 할 수 있다.

l     4칙연산

4칙연산은 정수형 처럼 +,_,*,/로 표현 한다. 이것은 이미 언급했듯이 함수로 실행되거나 co-processor의 도움을 받아 실행 된다.

어떻게 연산되는지를 확인하기 다음과 같은 간단한 프로그램을 생각한다.

float f1;
float f2;

float fares;
float fsres;
float fmres;
float fdres;

void cfloat(void)
{
    fares = f1 + f2;
    fsres = f1 - f2;
    fmres = f1 * f2;
    fdres = f1 / f2;
}

PUBLIC  _f1                    ; f1
PUBLIC  _f2                    ; f2
PUBLIC  _fares                 ; fares

_BSS    SEGMENT ; 초기값 없는 section

global 변수 3개는 float 이므로 32비트 영역의 변수 공간 이다.

_f1 DD  01H DUP (?)            ; f1
_f2 DD  01H DUP (?)            ; f2
_fares DD      01H DUP (?)    ; fares
_BSS    ENDS

_TEXT   SEGMENT
_cfloat PROC NEAR                              ; cfloat, COMDAT

다음은 float의 더하기 인데 모두 코프로세서(FPU)로 전이되어 계산한다.
;  fares = f1 + f2;
00000 d9 05 00000000 fld  DWORD PTR _f2        ; f2-FPU oper 레지스터로 f2변수값을 전송한다.
00006 d8 05 00000000 fadd DWORD PTR _f1        ; f1–oper f1의 메모리 변수값과 더한다.
0000c d9 1d 00000000 fstp DWORD PTR _fares ; fares–FPU에서 더해진 값을 저장 한다. =의 실행이다.

;  fsres = f1 - f2;
00012 d9 05 00 00 00 00       fld    DWORD PTR _f1   ; f1
00018 d8 25 00 00 00 00       fsub   DWORD PTR _f2   ; f2
0001e d9 1d 00 00 00 00       fstp   DWORD PTR _fsres ; fsres

;  fmres = f1 * f2;
00024 d9 05 00 00 00 00       fld    DWORD PTR _f2   ; f2
0002a d8 0d 00 00 00 00       fmul   DWORD PTR _f1   ; f1
00030 d9 1d 00 00 00 00       fstp   DWORD PTR _fmres ; fmres

;  fdres = f1 / f2;
00036 d9 05 00 00 00 00       fld    DWORD PTR _f1   ; f1
0003c d8 35 00 00 00 00       fdiv   DWORD PTR _f2   ; f2
00042 d9 1d 00 00 00 00       fstp   DWORD PTR _fdres ; fdres

; }
00048   c3             ret    0
_cfloat ENDP                                 ; cfloat
_TEXT   ENDS

이번에는 삼각함수의 예이다.

_BSS    SEGMENT
_fres DQ       01H DUP (?)  ; double fres; - 64비트 공간을 설정 한다.
_BSS    ENDS

PUBLIC  _ffun                                 ; ffun
PUBLIC  _d0.4_3fd999999999999a      ; 함수 호출할 때 사용되어질 0.4의 값을 정의한다.
;       COMDAT _d0.4_3fd999999999999a

CONST   SEGMENT
_d0.4_3fd999999999999a DQ 03fd999999999999ar ; 0.4
CONST   ENDS

;       COMDAT _ffun
_TEXT   SEGMENT
_ffun PROC NEAR                                      ; ffun, COMDAT

;    fres = tan(0.4);

00000   dd 05 00 00 00 00  fld QWORD PTR _d0.4_3fd999999999999a
- 정수값 이라면 코드 다음에 숫자가 오지만 64비트의 double은 위와 같이 처리된다.
00006   d9 f2            fptan
00008   dd d8             fstp ST(0)
0000a   dd 1d 00 00 00 00  fstp       QWORD PTR _fres       ; fres

; }
00010   c3             ret    0
_ffun ENDP                                   ; ffun

_TEXT   ENDS

삼각함수 역시 함수가 아니라 FPU가 연산 한다.
이에 반해 FPU가 없는 경우를 보기 위해 ARM으로 컴파일 해보면

|x$dataseg|
f1
        DCFS     0.0
f2
        DCFS     0.0

cfloat
|L000000.J2.cfloat|
   STMDB    sp!,{v1-v3,lr}
   LDR      v1,[pc, #L000054-.-8]
   LDR      a2,[v1,#4]
   LDR      a1,[v1,#0]
   MOV      v2,a2
   MOV      v3,a1
   BL       _fadd

   . . .

LDMIA    sp!,{v1-v3,pc}

L000054
   DCD      |x$dataseg|

와 같이 함수 호출을 함을 알 수 있다. 이 함수를 다음 map 파일에서 확인할 수 있다.
AREA map of D:\program\ARM\Release\math.axf:

Base     Size     Type RO? Name
8000     4c       CODE RO  !!! from object file __main.o
804c     164      CODE RO  C$$code from object file fcalc.o
81b0     10       CODE RO  C$$code from object file main.o
81c0     224      CODE RO  C$$code from object file fadd.o
83e4     12c      CODE RO  C$$code from object file fmul.o
8510     1dc      CODE RO  C$$code from object file fdiv.o
86ec     e0       CODE RO  C$$code from object file dadd.o
87cc     d4       CODE RO  C$$code from object file dmul.o
88a0     144      CODE RO  C$$code from object file ddiv.o
89e4     40       CODE RO  C$$code from object file sin.o
8a24     58       CODE RO  C$$code from object file cos.o
8a7c     18c      CODE RO  C$$code from object file tan.o

CPU와 코프로세서가 결합되어 있지 않은 경우는 float point을 처리하기 위해 필연적으로 함수를 사용할 수 밖에 없다. 이것은 저속의 CPU에서는 동작 시간 및 리얼타임의 문제를 미리 검토하여 설계하여야 문제가 적어진다.

4. 삼각 함수의 계산

삼각함수를 계산하기 위해서는 각도가 존재 한다. 삼각함수를 계산하는 대부분은 래디언 각도로 인수를 설정해야 한다. 계산기에서는 보통 360도의 숫자로 사용하지만 프로그램 함수는 다르다. 다음 프로그램을 생각해 본다.

#include <stdio.h>
#include <math.h>

#define PI (double)3.1415926535

int main(int argc, char* argv[])
{
double rst;

rst = sin( 45.);
printf("sin(45.) = %lf - 360도 기준이 아님.\n", rst);

double ideg;
double deg;

ideg = 45.;
deg = ideg*(PI/180.);  // 360도 기준을 2PI로 바꾼다.
printf("sin( %lf ) = %lf\n", ideg, sin(deg));
printf("cos( %lf ) = %lf\n", ideg, cos(deg));
printf("tan( %lf ) = %lf\n", ideg, tan(deg));

return 0;
}

실행 결과 :
sin( 45. ) = 0.850904 - 360도 기준이 아님.
sin( 45.000000 ) = 0.707107
cos( 45.000000 ) = 0.707107
tan( 45.000000 ) = 1.000000

“sin( 45. ) = 0.850904”의 결과는 계산기를 눌러보면 답이 다름을 할 수 있다. 다음 줄의 결과 값이 맞는 것이다. 360도가 아니라 2*PI 기준으로 인수를 넣어야 한다. 이것은 각도에다 PI/180을 곱해야 2PI 기준으로 바꾸어 진 것이다.

5. float point 변수의 array와 포인터

정수형과 같이 개념적으로는 모든 array는 비슷한 구조를 갖는다.

float fdata[100];
float *pfdt;

여기서 array는 기본 규칙에 따르는데, 시작 주소로 부터 차례로 sizeof(float) 만큼 씩 주소가 증가하면서 배치된다.
 pfdt = fdata;
와 같이 주소값을 처리 한다.
간단히 합하는 프로그램을 작성하면 다음과 같은

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char* argv[])
{
   srand( (unsigned)time( NULL ) ); // 프로그램의 시작 시간이
// 정해져 있지 않기 때문에 rand seed로 사용한다.

   float *pfdt;
   float sum;
   int cnt;
   float calcSum(float *pfdt, int length);

    pfdt = new float[100];
   
for (cnt = 0;cnt < 100;cnt++) {
        pfdt[cnt] = (float) rand();
}

    sum = calcSum(pfdt, 100);
printf("sum=%f\n", sum);
delete [] pfdt;
return 0;
}

float calcSum(float *pfdt, int length)
{
  float fsum;

    fsum = 0.0f;
for (;length;length--) {
       fsum += *pfdt++;
}
    return fsum;
}
여기서 다음 코드를 생각하면 기본적인 틀에 의해 해석된다.
fsum += *pfdt++;
다음 2라인으로 표시할 수 있는데
fsum += *pfdt;
pfdt++;
포인터는 다른 타입의 변수 처럼 CPU에 의해 정해진 길이 주소값을 갖는 변수이고
pfdt++ pfdt <= pfdt의 주소값 + sizeof(float)로 코딩 된다.



크리에이티브 커먼즈 라이선스

댓글 없음:

댓글 쓰기