C C++

[C | C++] 실수 소수점 다루기

Posted by DHniyeo Blog on September 16, 2023

🐿️ 고정 소수점과 부동 소수점 (float, double)

컴퓨터에서 실수를 표현하는 방식을 크게 두 가지로 나눌 수 있다.

  1. 고정 소수점 (Fixed Point) : 소수점이 고정된 것
  2. 부동 소수점 (Floating Point) : 소수점이 움직임.

🐇 고정 소수점

예를 들어서 32Bits 중 절반 16개는 정수, 나머지 절반 16개는 소수점 부분의 숫자를 저장하는 것이다. 그렇기 때문에 비트 낭비가 있을 수 있다.

소수 부분에 많은 비트가 필요하지 않을 때, 정수 부분에 비트를 좀 더 쓸 수 있다면 메모리를 더 효율적으로 사용할 수 있을 것이다.

🐇 부동 소수점

C언어에서 사용하는 실수 자료형인 float과 double이 바로 IEEE754 표준에서 정의하는 부동 소수점을 따른다.

IEEE754을 따라서 부동 소수점이 저장될 때는 일단 수를 2진수로 변환하고 정규화식으로 표현하는데, 8.5 에 그 과정을 적용하면 아래와 같다.

![0](/assets/img/2023-09-16-[C- -C++]-실수-소수점-다루기.md/0.png)

그리고 여기서 다음 세 가지를 뽑아내는데

  • 부호 : 0 (+이면 0, -이면 1)
  • 지수 : 130 (지수는 3이지만 IEEE754표준에서는 지수 + 127 을 저장하는데, 이 127을 bias라고 함, 이 bias를 더하는 이유는 지수가 음수일 때 연산을 쉽게 하기 위해서..)
  • 가수 : 00010000000000000000000 (정규화 식에서 소수점 뒷부분. 즉, 모자라는 부분은 0으로 채움)

이 세 가지를 아래 그림과 같은 구조로 저장하여 사용한다.

![1](/assets/img/2023-09-16-[C- -C++]-실수-소수점-다루기.md/1.png)

이렇게 비트를 좀 더 효율적으로 사용하여 더 많은 수를 표현 할 수 있다.

하지만 부동 소수점에는 다음과 같은 단점이 존재한다.

  1. 정밀도
  2. 성능

정밀도가 떨어지는 이유는 컴퓨터가 이진법으로 계산하기 때문인데 어떠한 소수들은 소수점이 무한대로 뻗어나가기 때문이다.

예를 들어 0.1은 이진 정규화식으로 표현하면 무한소수가 된다.

![2](/assets/img/2023-09-16-[C- -C++]-실수-소수점-다루기.md/2.png)

보시다시피 오차를 확인할 수 있다.

C++에서는 기본적으로 이 부동 소수점을 채택하고 있으며 관련 자료형은 다음과 같다.

float(4Byte)은 유효숫자 6-7 까지 보장해준다

double(8Byte)은 유효숫자 15-16 까지 보장해준다

🐿️ 실수 출력하기

1️⃣ printf()로 출력

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>  //표준 입출력 헤더

int main(void)
{       
    double df = 3.4;
    float f = 3.4f;
    printf("df:%lf\n",df); // C95버전까지는 %f 사용했지만 C99로와서 명목상 %lf 사용. 
    printf(" f:%f\n",f);
    return 0;
}

다음은 12.3456을 소숫점 이하 2자리 까지 출력한 예이다.

1
2
3
4
5
6
7
#include <stdio.h> //표준 입출력 헤더

int main(void)
{       
    printf("%.2f\n", 12.3456);
    return 0;
}

실수 출력 포멧 %f%e%g

  • %e : 지수 표기로 출력하는 것
1
2
3
4
5
6
7
#include <stdio.h> //표준 입출력 헤더

int main(void)
{       
    printf("%e\n", 12.3456);
    return 0;
}

출력 결과

1
1.234560e+01
  • %g : 가장 간단하게 출력할 수 있는 방법으로 출력. 이 때 소숫점 이하 자리도 값이 없으면 출력하지 않는다.
1
2
3
4
5
6
7
8
9
10
#include <stdio.h> //표준 입출력 헤더

int main(void)
{
	printf("%g\n", 13.4); //13.4
	printf("%g\n", 0.00000012); //1.2e-07
	printf("%g\n", 1.300); // 1.3
	printf("%f\n", 1.300); // 1.300000
	return 0;
}

출력 결과

1
2
3
4
13.4
1.2e-07
1.3
1.300000

2️⃣ cout으로 출력

cout의 경우 변수에 맞게 자동으로 형을 결정하여 출력이 가능하다.

만약, 출력 방식을 선택 하고 싶으면 아래를 참고하면 된다.

cout으로 실수를 출력할 때, 만약 그 수가 큰 수라면 자동으로 지수 표기법으로 변경되는 문제가 생긴다. 이 때 정상적으로 출력 되도록 하려면 cout « fixed를 사용하면 된다.

  • cout << fixed : 소수점을 고정시켜 사용
  • cout.precision(n) : n자리까지 소수점을 표현 (n + 1 자리에서 반올림)

아래처럼도 사용 가능

  • cout.setf(ios::fixed) : cout << fixed와 같은 기능
  • cout.unsetf(ios::fixed) : fixed를 해제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream> //표준 입출력 헤더
using namespace std;
int main(void)
{
	ios::sync_with_stdio(false); // 입출력 스트림의 동기화 해제
	cin.tie(); cout.tie(); // flush를 적게 하기 때문에 입출력 속도를 향상시킴

	double num = 12345678.111111;

	cout << num << endl; // 그냥 출력

	cout << fixed;		// fixed 설정
	cout.precision(6);	// 고정시킬 자리 설정
	cout << num << endl;

	cout.unsetf(ios::fixed); // fixed 해제
	cout << num << endl;

	cout.setf(ios::fixed); // fixed 설정
	cout << num << endl;
}

출력 결과

1
2
3
4
1.23457e+07
12345678.111111
1.23457e+07
12345678.111111

🐿️ 소수점 올림, 내림, 반올림, 버림

🐇 헤더파일(header)

[C 언어] : math.h

[C++] : cmath

🐇 함수 설명

  • ceil : 천장을 의미하며 올림
  • floor : 바닥을 의미하며 내림
  • round : 반올림 (C++11 부터 사용 가능)
  • trunc : 버림 (C++11 부터 사용 가능)

🐇 함수 원형 및 사용법

  • [C언어] : C언어는 함수 오버로딩을 지원하지 않으므로 double 타입으로만 존재합니다.
    • ceil (올림)

      double ceil(double n);

    • floor (내림)

      double floor(double n);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	#include<stdio.h>
	#include<math.h>
	 
	int main(void){
	    //양수 
	    double n1 = 4.2;
	    double n2 = 4.2;
	    double n3 = 4.2;
	    double n4 = 4.7;
	    
	    //올림 
	    printf("%0.1lf\n", ceil(n1));
	    
	    //내림 
	    printf("%0.1lf\n", floor(n2));
	    
	    //반올림 
	    printf("%0.1lf\n", floor(n3 + 0.5));
	    printf("%0.1lf\n", floor(n4 + 0.5));    
	    
	    return 0;    
	}
1
출력 결과
1
2
3
4
	5.0
	4.0
	4.0
	5.0
  • [C++] : C++에서는 method 이름이 달라도 method를 선언할 수 있는 오버로딩을 지원합니다.
    • ceil (올림)

      float ceil (float x);

      double ceil (double x);

      long double ceil (long double x);

    • floor (내림)

      float floor (float x);

      double floor (double x);

      long double floor (long double x);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	#include<iostream>
	#include<cmath>
	 
	using namespace std;
	int main(void){
	    //음수 
	    double     n1 = -4.2;
	    float     n2 = -4.2;
	    double     n3 = -4.2;
	    float     n4 = -4.7;
	 
	    //소수점 출력.    
	    cout.setf(ios::fixed, ios::floatfield);
	 
	    //소수점 아래 1자리 까지 
	    cout.precision(1);    
	 
	    //올림 
	    cout << ceil(n1) << endl;
	    
	    //내림 
	    cout << floor(n2) << endl;
	    
	    //반올림 
	    cout << floor(n3 + 0.5) << endl;
	    cout << floor(n4 + 0.5) << endl;
	        
	    return 0;    
	}
1
출력 결과
1
2
3
4
	-4.0
	-5.0
	-4.0
	-5.0
1
2
3
4
5
6
7
8
9
10
11
12
- round (반올림)

	float round(float num);


	double round(double num);


	long double round(long double num);


	double round(T x);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	//C++ round example.
	#include<iostream>    //cout
	#include<cmath>        //round, ceil, floor
	using namespace std;
	 
	int main(void)
	{
	    double a1 = 3.2;
	    double a2 = 3.7;
	    double a3 = -3.2;
	    double a4 = -3.7;
	    cout << "round(3.2) : " << round(a1) << endl;
	    cout << "round(3.7) : " << round(a2) << endl;
	    cout << "round(-3.2) : " << round(a3) << endl;
	    cout << "round(-3.7) : " << round(a4) << endl;
	 
	    cout << endl;
	    system("pause");
	    return 0;
	}
1
출력 결과
1
2
3
4
	round(3.2) : 3
	round(3.7) : 4
	round(-3.2) : -3
	round(-3.7) : -4
1
2
3
4
5
6
7
8
9
10
11
12
- trunc (버림)

	float trunc(float num);


	double trunc(double num);


	long double trunc(long double num);


	double trunc(T num);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	//C++ trunk example.
	#include<iostream>    //cout
	#include<cmath>        //round, ceil, floor, trunc
	using namespace std;
	
	int main(void)
	{
	    double a1 = 3.2;
	    double a2 = 3.7;
	    double a3 = -3.2;
	    double a4 = -3.7;
	    cout << "trunc(3.2) : " << trunc(a1) << endl;
	    cout << "trunc(3.7) : " << trunc(a2) << endl;
	    cout << "trunc(-3.2) : " << trunc(a3) << endl;
	    cout << "trunc(-3.7) : " << trunc(a4) << endl;
	
	    cout << endl;
	    system("pause");
	    return 0;
	}
1
출력 결과
1
2
3
4
	trunc(3.2) : 3
	trunc(3.7) : 3
	trunc(-3.2) : -3
	trunc(-3.7) : -3