• 2.4.1 부동소수점수(floating point numbers) 종류
  • 2.4.2 limits 헤더를 이용한 부동소수점수 한계치 확인
  • 2.4.3 과학적 표기법(scientific notation)
  • 2.4.4 Floating error
  • 2.4.5 inf and nan

2.4.1 부동소수점수(floating point numbers) 종류

 

부동소수점수는 float, double, long double로 총 3가지가 c++에 존재한다. 부동소수점이라고 불리는 이유는 과학적 표기법(scientific notation)에 의해 주어진 수의 소수점 위치가 이동하기 때문이며, 부동은 움직이지 않는다는 의미가 아닌 떠다닌다의 의미로 받아들이면 된다.

 

float 자료형은 딥러닝에서 자주 쓰고 기본 실수 계산은 double을 선호하게 된다. 딥러닝이 아닌 일반 수치해석적 과학 계산에서는 double을 사용하는 것을 선호한다.

 

float-point numbers(32 bit)

 

float-point numbers(64 bit)

 

부동소수점수는 부호와 지수, 가수를 통해 구현이 된다. 이러한 표현 방식 때문에 실제 값과 입력 값 차이의 오차가 생기는 한계가 생긴다.


2.4.2 limits 헤더를 이용한 부동소수점수 한계치 확인

 

sizeof 연산자를 이용하면 각 자료형에 대한 크기를 알 수 있다. 이전과 동일하게 limits 헤더를 이용하면 해당 자료형의 한계치를 확인할 수 있다. lowest 함수를 사용해야 가장 작은 음수를 확인할 수 있다.

 

#include <iostream>
#include <limits>

int main()
{
	using namespace std;

	cout << sizeof(float)			<< endl; // 4 bytes
	cout << sizeof(double)		<< endl; // 8 bytes
	cout << sizeof(long double)	<< endl; // 8 bytes

	cout << numeric_limits<float>::max()		<< endl; // min() lowest()
	cout << numeric_limits<double>::max()		<< endl;
	cout << numeric_limits<long double>::max()	<< endl;

	return 0;
}

2.4.3 과학적 표기법(scientific notation)

 

지수 표현을 활용하여 과학적 표기법을 쓸 수 있다. 지수 표현의 경우 과학 계산에서 상수로 쓸만한 값들에 대해 쓰는 것 이외에는 볼 수가 없지만 실무에서의 규칙은 모든 회사가 다르기 때문에 그에 맞게 사용하면 된다.

 

#include <iostream>

int main()
{
	using namespace std;

	// scientific notation
	cout << 3.14		<< endl;
	cout << 31.4e-1	<< endl;
	cout << 31.4e-2	<< endl;
	cout << 31.4e1		<< endl;
	cout << 31.4e2		<< endl;

	return 0;
}

 

e-1은 1/10을 의미하며, e-2은 1/100을 의미한다. +에 대해서는 역수를 취하면 된다. 또한 +는 생략이 가능하다.


2.4.4 Floating error

 

float 자료형이 아닌 double 자료형도 흔히 사용하는 이유는 바로 floating error 때문이다. 메모리를 많이 사용하면 더 세밀한 실수를 다룰 수 있는데, float 자료형을 쓸 경우 소수점 아래 6번째 자리부터 신뢰하기 힘들어진다. double 자료형도 소수점 아래 16번째 자리부터 신뢰하기 힘들어지는데 일반적으로 10자리를 넘어가는 자리수에 대해서는 고려하지 않는다. 또한 콘솔 창에서 표기된 수는 setprecision으로 정확히 보는 게 아닌 이상 믿을 수 없다.

 

#include <iostream>
#include <iomanip> // 입출력 조작

int main()
{
	using namespace std;

	cout << std::setprecision(25);
	cout << 1.0f / 3.0f	<< endl;
	cout << 1.0 / 3.0	<< endl;

	float f1 = 123456789.0f; // 10 significant digits
	cout << f1 << endl;

	double d1 = 1.0;
	cout << d1 << endl;
	cout << std::setprecision(30);
	cout << d1 << endl;

	double d2 = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
	cout << d2 << endl;

	return 0;
}

 

위의 코드를 실행하면 여러가지 의미 있는 결과들을 볼 수 있다. 특히 d1과 d2의 결과는 기억해둘 필요가 있다.


2.4.5 inf and nan

 

0으로 나누는 것을 수학에서 정의하지 않지만 코드를 작성하면서 간혹 0으로 나누는 일을 수행할 수 있다. 0으로 나누는 경우 그 수는 inf로 표기되는데, 무한대라고 보면 된다. 또한 수가 아닌 경우엔 not a number의 약자인 nan으로 표기된다. cmath 헤더에는 인자로 들어오는 수가 inf인지 nan인지 체크해주는 함수가 있다.

 

#include <iostream>
#include <cmath>

int main()
{
	using namespace std;

	double zero	= 0.0;
	double posinf	= 5.0 / zero;
	double neginf	= -5.0 / zero;
	double nan	= zero / zero;

	cout << posinf	<< endl;
	cout << neginf	<< endl;
	cout << nan	<< endl; // -nan(ind) indeterminate
	cout << posinf	<< " " << std::isinf(posinf) << endl;
	cout << neginf	<< " " << std::isinf(neginf) << endl;
	cout << nan	<< " " << std::isnan(nan) << endl;
	cout << 1.0	<< " " << std::isnan(1.0) << endl;

	return 0;
}

'Programming Language > C++' 카테고리의 다른 글

Section 2.6. char 자료형  (0) 2021.10.18
Section 2.5. Boolean 자료형  (0) 2021.10.15
Section 2.3. void  (0) 2021.10.12
Section 2.2. 정수형  (0) 2021.10.11
Section 2.1. 기본 자료형  (0) 2021.09.29
Part1.기초에서 학습할 내용
→ 알고리즘의 설계와 분석을 학습

 알고리즘을 분석할 때 필요한 기본 개념 학습

 

  • 1.0 개요
  • 1.1 알고리즘
  • 1.2 기술로서의 알고리즘

1.0 개요

 

알고리즘이 고속 하드웨어, 그래픽 기반의 사용자 인터페이스, 객체 지향 시스템, 네트워크 등과 같이 중요한 컴퓨터 기술 중 하나임을 확인한다.

 

알고리즘의 후보해는 많지만 대부분이 문제의 해가 아니다. 문제의 해 혹은 "최상의" 해를 찾는 일은 대단히 도전적인 일이다.


1.1 알고리즘

 

알고리즘(Algorithm)은 어떤 입력을 어떤 출력으로 변환하는 잘 정의된 일련의 계산 절차를 말한다. 또한 알고리즘을 잘 정의된 계산 문제를 풀기 위한 도구로도 볼 수 있다.

 

알고리즘이 모든 입력 사례에 대해 항상 올바른 출력을 내고 종료할 경우 이를 타당하다(correct)고 하며, 그 타당한 알고리즘이 주어진 계산 문제를 푼다(solve)고 말한다. 타당하지 않은 알고리즘은 모든 또는 일부 입력 사례에서 종료하지 않거나 잘못된 답을 도출하며 종료할 것이다. 타당하지 않은 알고리즘일지라도 오류의 비율을 조절할 수 있다면 유용할 때가 있다.(소수 찾기 알고리즘)

 

어떤 문제를 알고리즘으로 푸는가

convex hull 찾기, 최단 경로 찾기, FFT(Fast Fourier Transform), 위상 정렬(Topological sorting) 등을 푼다.

 

자료구조

자료구조는 자료를 편리하게 접근하고 변경하기 위해 자료를 저장하거나 조작하는 방법을 말한다. 완벽한 자료구조는 없으니 각 자료구조의 장점과 한계를 잘 아는 것이 중요하다.

 

어려운 문제들

알고리즘의 효율성에 관한 일반적인 척도는 속도, 즉 알고리즘이 결과를 내는데 걸리는 시간이다. 최적해가 알려지지 않은 문제도 많은데, 그 중 하나가 NP-완비 문제이다.

 

병렬성

프로세서의 클럭 속도는 오랫동안 계속 증가했지만 이제는 물리적 한계로 인해 클럭 속도를 계속 증가시키기 어려워졌다. 바로 클럭 속도 증가에 따른 전력 밀도 증가와 발열 때문이다. 이러한 한계를 극복하고자 클럭 속도를 증가시키는 것이 아닌 여러 계산을 동시에 처리하도록 멀티 코어 컴퓨터가 등장하게 된다. 이는 일종의 병렬 컴퓨터(parallel computer)이며, 멀티 코어 컴퓨터로부터 최고의 성능을 얻으려면 알고리즘을 설계할 때 병렬성을 염두에 두어야 한다.


1.2 기술로서의 알고리즘

 

컴퓨터가 무한히 빠르고 메모리 비용이 들지 않아도 알고리즘 학습은 필요하다. 어떤 기법이든 가장 쉽게 구현할 수 있는 것을 가장 자주 사용할 것이다. 컴퓨터는 무한히 빠를 수 없으며 메모리 비용 또한 저렴해지더라도 전혀 안들 수는 없다. 그러므로 자원을 효율적으로 사용해야 하며, 시간과 공간 측면에서 효율적인 알고리즘이 필요하다.

 

효율성

동일한 문제를 해결하기 위한 알고리즘이 효율성 면에서 극적으로 다를 수 있다. 이런 차이는 HW나 SW로 인한 차이보다 훨씬 더 심각할 수 있다.

 

컴퓨터 A가 컴퓨터 B보다 1000배 빠르게 명령어를 수행한다고 가정하자. 컴퓨터 A에는 삽입 정렬을 수행하고 컴퓨터 B에는 병합 정렬을 수행한다. 입력 값은 같으며, 출력 결과도 동일하게 나와야 한다. 삽입 정렬의 소요 시간은 $c_1n^2$이며, 병합 정렬의 소요 시간은 $c_2n\log_2n$이다. $n$은 정렬할 수열의 길이이며, 일반적으로 $c_1<c_2$의 관계를 가진다. $n=1000000$인 경우 각 컴퓨터가 명령을 수행하는데 걸리는 시간은 다음과 같다.

 

$$ time_A = {c_1 1000000 \times 1000000 \over 1000} = 1,000,000,000 $$

$$ time_B = c_2 1000000 \times \log_{2}1000000 = 100,000,000 $$

 

결과적으로 빠른 컴퓨터를 사용하면서도 적절한 알고리즘을 사용하지 못하여 최종 소요 시간이 늘어나는 것을 볼 수가 있다.

 

알고리즘과 다른 기술들

앞서 보인 예는 HW처럼 알고리즘도 하나의 기술임을 보여준다. 전체 시스템의 성능은 빠른 HW뿐만 아니라 얼마나 효율적인 알고리즘을 선택하느냐에 따라서도 결정된다.

 

동시대에 다음과 같은 발전된 다른 기술들만큼 컴퓨터 분야에서 알고리즘은 매우 중요하다.

발전된 컴퓨터 구조와 제조 기술
사용하기 쉽고 직관적인 GUI
객체 지향 시스템
통합적인 웹 기술
빠른 유무선 네트워킹

 

"알고리즘에 대한 지식과 기술을 얼마나 알차게 학습했느냐"가 숙련된 프로그래머와 초보자를 구분하는 기준이 될 수 있다. 알고리즘에 대한 별다른 지식 없이도 최신 컴퓨터 기술을 통해 어떤 일을 할 수 있을지도 모른다. 하지만 알고리즘에 대한 훌륭한 배경 지식을 쌓으면 훨씬 더 많은 일을 할 수 있을 것이다.

  • 2.3.1 무치형(Void)

2.3.1 무치형(Void)

 

흔히 void라고 말하는 무치형 타입은 함수에서 반환값이 없거나 매개변수가 없을 경우 사용하게 된다. 하지만 매개변수가 없는 함수의 경우 void를 굳이 기입하지 않아도 되며, 기입하는 관습은 옛 관습이니 굳이 지키지 않아도 된다. 실무에서 그 방식으로 작성한다면 따르도록 하자.

 

// 매개변수 void 넣는 것은 옛날 방식
void my_function(void)
{

}

int main()
{
	//void는 메모리를 차지하지 않기 떄문에 선언할 수 없다.
	//void my_void;
	int i = 123;
	float f = 123.456f;

	void* my_void;

	// 데이터 타입이 다르고 사이즈가 달라도 해당 데이터의 주소를 표현하는 데이터의 크기는 동일하다.
	my_void = (void*)&i;
	my_void = (void*)&f;

	return 0;
}

 

void는 메모리를 차지하지 않기 때문에 선언할 수 없다. 하지만 포인터로는 활용이 가능한데, 데이터 타입이 다르고 사이즈가 달라도 데이터에 대한 주소를 표현하는 데이터의 크기는 언제나 동일하므로 가능하다. 추후 포인터에서 다루게 되면 더 자세히 포스팅하도록 하겠다.

  • 2.2.1 정수형(Integers) 종류
  • 2.2.2 limits 헤더를 이용한 정수형 한계치 확인
  • 2.2.3 overflow
  • 2.2.4 고정 너비 정수(Fixed-width integers)

2.2.1 정수형(Integers) 종류

 

정수형애서 정수는 우리가 흔히 알고 있는 수학에서의 정수를 의미한다. C++에서 정수를 나타낼 수 있는 방법이 여러가지가 있는데, 그 종류를 한번 알아보도록 하자.

 

#include <iostream>

int main()
{
	using namespace std;

	cout << sizeof(short) << endl;		// 2 bytes = 2 * 8 bits = 16 bits
	cout << sizeof(int) << endl;		// 4 bytes = 4 * 8 bits = 32 bits
	cout << sizeof(long) << endl;		// 4 bytes = 4 * 8 bits = 32 bits
	cout << sizeof(long long) << endl;	// 8 bytes = 8 * 8 bits = 64 bits

	return 0;
}

 

코드에서는 4가지에 대해서 작성되어 있지만 char 또한 정수형으로 취급한다. 네트워크 계열을 다루는 사람들은 char 자료형을 1 byte 정수 자료형으로 사용한다. 그 외 정수 자료형은 short, int, long, long long이 있으며, int 자료형과 long 자료형은 같은 크기의 메모리를 소비한다. 메모리를 많이 사용할수록 더 넓은 범위의 수를 다룰 수 있게 된다.

 

수를 표현할 때 부호(sign)을 표현하기 위해 1 bit를 사용한다. 양수는 0으로 표현하며 음수는 1로 나타낸다. 자료형 앞에 unsigned 키워드를 추가하면 부호를 표현하기 위한 bit마저도 수를 표현하기 위해 사용하게 된다. 이때 음수는 표현할 수 없게 되니 주의하자. 또한 정수형끼리 나누기를 수행해서 소수점이 나오게 되고 그 수를 정수형에 저장하게 되면 소수점 아래의 수는 버림이 된다.

 


2.2.2 limits 헤더를 이용한 정수형 한계치 확인

 

limits 헤더를 이용하면 자료형의 수치적 한계치를 확인할 수 있다. 앞의 코드를 통해 short 자료형은 2 bytes(16 bits)임을 알 수 있다. 하나의 bit를 부호로 사용하면 총 15 bits를 수를 표현하는 메모리로 사용할 수 있다.

 

#include <iostream>
#include <cmath>
#include <limits>

int main()
{
	using namespace std;
	
	// 32767
	cout << pow(2, sizeof(short) * 8 - 1/*부호 표현에 의한 -1*/) - 1/*0에 대한 -1*/<< endl;

	cout << numeric_limits<short>::max() << endl;		// 32767
	cout << numeric_limits<short>::min() << endl;		// -32768
	cout << numeric_limits<short>::lowest() << endl;	// -32768

	return 0;
}

 

절대값으로 보면 양수가 음수보다 1이 작은 것을 알 수 있다. 부족한 부분은 0을 표현하기 위해 사용이 된다.

 


2.2.3 overflow

 

앞에서 언급하였듯이 메모리의 크기에 따라 표현할 수 있는 수의 한계치가 존재한다. 만약 해당 한계치를 뛰어넘는 수를 저장하면 어떻게 될까?

 

#include <iostream>

int main()
{
	using namespace std;

	short s = 32767;
	cout << "short max : " << s << endl;
	s = s + 1; // 32768
	cout << "short max + 1 (overflow) : " << s << endl;

	s = -32768;
	cout << "short min : " << s << endl;
	s = s - 1; // -32769
	cout << "short min - 1 (overflow) : " << s << endl;
	
	unsigned int iii = -1;
	cout << iii << endl; // overflow

	return 0;
}

 

short 자료형의 최대값은 32767이다. 32767을 저장하고 1을 더하게 되면 32768이 아닌 -32768이 되면서 최소값이 나온다. 이처럼 한계치를 뛰어넘어서 수의 법칙과는 다르게 결과값을 가지는 것을 overflow라고 한다. short 자료형의 최소값에 -1을 할 경우 최대값인 32767을 결과값으로 가진다.

 


2.2.4 고정 너비 정수(Fixed-width integers)

 

C++ 11부터는 어떤 플랫폼이던지 고정 너비 정수를 쓸 수 있도록 설정하는 방법이 생겼다. #include <cstdint>를 통해 설정하는 것이 원칙이지만 iostream을 include할 경우 별도로 include할 필요가 없다.

 

#include <iostream>

int main()
{
	using namespace std;

	int16_t i(5);
	int8_t myint = 65;

	cout << myint << endl;

	int_fast8_t fi(5);
	int_least64_t fl(5);
}

+ Recent posts