02.06 - 부동 소수점 숫자 (floating point numbers)
정수(integer)도 매우 좋지만 때때로 매우 큰 숫자나 소수점이 있는 숫자를 저장해야 하는 경우도 있다. 부동 소수점(floating point) 자료형 변수는 4320.0, -3.33, 0.01226과 같은 실수를 저장하는 변수다. 부동 소수점은 소수점이 "부동(float)" 할 수 있다는 것을 의미한다. 즉, 소수점 앞과 뒤에 있는 자릿수를 지원한다.
C++에는 float
, double
및 long double
과 같은 부동 소수점 자료형이 있다. 정수와 마찬가지로 C++은 이러한 자료형의 크기를 지정하지 정의하지 않았다. 현대 아키텍처에서 부동 소수점 표현은 거의 항상 IEEE 754 바이너리 형식을 따른다. 이 형식에서 float은 4 byte이고 double은 8이며 long double은 8, 12, 16 byte 중 하나다.
float 자료형은 항상 부호가 있다. (signed)
Category |
Type |
Minimum Size |
Typical Size |
floating point |
float |
4 bytes |
4 bytes |
|
double |
8 bytes |
8 bytes |
|
long double |
8 bytes |
8, 12, or 16 bytes |
다음은 부동 소수점 숫자에 대한 몇가지 정의다.
float fValue;
double dValue;
long double dValue2;
부동 소수점 숫자 리터럴을 사용할 때는 정수값과 구분할 수 있도록 소수점 이하 자리를 포함하는 것이 일반적이다.
int x(5);
double y(5.0);
float z(5.0f);
f접미사는 부동 소수점 숫자의 리터럴을 나타내는 데 사용한다.
정밀도와 범위 (Precision and range)
분수 1/3을 생각해보자. 이 숫자의 10진수 표현은 0.3333333333...으로 3이 무한대로 나온다. 무한 길이의 숫자를 저장하려면 무한한 메모리가 필요하지만 부동 소수점 자료형은 일반적으로 4 or 8 byte다. 결국, 부동 소수점 숫자는 특정 유효 자릿수까지만 저장하고 나머지는 손실된다. 부동 소수점의 정밀도(precision)는 정보 손실 없이 나타낼 수 있는 유의한 자릿수를 정의한다.
부동 소수점 숫자를 출력할 때 std::cout의 기본 정밀도는 6이다. 즉, 모든 부동 소수점 숫자는 6자리까지만 유의하다고 가정하여 이후는 잘라낸다.
#include <iostream>
int main()
{
float f;
f = 9.87654321f;
std::cout << f << std::endl;
f = 987.654321f;
std::cout << f << std::endl;
f = 987654.321f;
std::cout << f << std::endl;
f = 9876543.21f;
std::cout << f << std::endl;
f = 0.0000987654321f;
std::cout << f << std::endl;
return 0;
}
output:
9.87654
987.654
987654
9.87654e+006
9.87654e-005
각각 6자리 숫자다.
그러나 <iomanip>
헤더 파일에 정의된 std:setprecision()
함수를 사용해서 cout에서 출력되는 기본 정밀도를 재정의(override)할 수 있다.
#include <iostream>
#include <iomanip>
int main()
{
std::cout << std::setprecision(16);
float f = 3.33333333333333333333333333333333333333f;
std::cout << f << std::endl;
double d = 3.3333333333333333333333333333333333333;
std::cout << d << std::endl;
return 0;
}
output:
3.333333253860474
3.333333333333334
정밀도를 16자리로 설정했으므로 위의 각 숫자는 16자리로 출력된다. 하지만 그 숫자들이 16자리까지 모두 정확하지는 않다.
부동 소수점 자료형의 변수가 가지는 정밀도는 자릿수 크기('float
은 double
보다 작다.)와 저장되는 특정 값에 따라 달라진다. float
값의 정밀도는 6 ~ 9 자리다. double
값의 정밀도는 15 ~ 18 자리로 16. long double
은 얼마나 많은 바이트를 차지하느냐에 따라 최소 15, 18 또는 33의 자리 정밀도를 가진다.
정밀도 문제는 단지 소수점 이하의 숫자에만 영향을 미치는 것이 아니라, 너무 많은 유효 숫자에도 영향을 미친다. 큰 숫자를 고려해 보자.
#include <iostream>
#include <iomanip>
int main()
{
float f(123456789.0f);
std::cout << std::setprecision(9);
std::cout << f << std::endl;
return 0;
}
Output:
123456792
123456792는 123456789보다 크다. 값 123456789.0은 10자리 숫자이지만 float
은 일반적으로 7자리 정밀도를 가지기 때문에 정밀도를 잃고, 예상하지 못한 결과가 출력한다.
따라서 변수가 보유 할 수가 있는 것보다 더 정밀도가 필요한 부동 소수점 숫자를 사용할 때는 주의해야 한다.
Size |
Range |
Precision |
4 bytes |
±1.18 x 10-38 to ±3.4 x 1038 |
6-9 significant digits, typically 7 |
8 bytes |
±2.23 x 10-308 to ±1.80 x 10308 |
15-18 significant digits, typically 16 |
80-bits (12 bytes) |
±3.36 x 10-4932 to ±1.18 x 104932 |
18-21 significant digits |
16 bytes |
±3.36 x 10-4932 to ±1.18 x 104932 |
33-36 significant digits |
반올림 오류 (Rounding errors)
부동 소수점 숫자가 까다로운 이유 중 하나는 binary(2진수: 데이터 저장 방법)와 10진법(사람이 생각하는 방식) 간의 차이가 크기 때문이다. 1/10을 생각해보자. 소수로, 이것은 0.1이며 0.1은 쉽게 표현할 수 있는 숫자로 생각한다. 그러나 binaray(2진수)에서 0.1은 0.00011001100110011…와 같은 무한 시퀀스로 표현된다. 이 때문에 부동 소수점 숫자에 0.1을 지정하면 정밀도 문제가 발생한다.
#include <iostream>
#include <iomanip>
int main()
{
double d(0.1);
std::cout << d << std::endl;
std::cout << std::setprecision(17);
std::cout << d << std::endl;
return 0;
}
This outputs:
0.1
0.10000000000000001 (: rounding error)
정밀도를 17로 설정했다. 메모리 제한으로 인해 근사치를 잘라내야 했기 때문에 그 결과 수치는 정확하게 0.1이 아니다. 이를 반올림 오류(rounding error)라고 한다.
반올림 오류(rounding error)는 예상치 못한 결과를 초래하기도 한다.
#include <iostream>
#include <iomanip>
int main()
{
std::cout << std::setprecision(17);
double d1(1.0);
std::cout << d1 << std::endl;
double d2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1);
std::cout << d2 << std::endl;
}
This outputs:
1
0.99999999999999989 (: rounding error)
NaN and Inf
부동 소수점 숫자에는 특별한 두 가지 숫자가 있다. 첫 번째는 무한대를 나타내는 Inf
다. 두 번째는 숫자가 아님을 나타내는 NaN
이다.
#include <iostream>
int main()
{
double zero = 0.0;
double posinf = 5.0 / zero;
std::cout << posinf << std::endl;
double neginf = -5.0 / zero;
std::cout << neginf << std::endl;
double nan = zero / zero;
std::cout << nan << std::endl;
return 0;
}
This outputs:
1.#INF
-1.#INF
1.#IND
INF은 무한대를 의미하고 IND는 중간을 의미한다. Inf 및 NaN 출력 결과는 플랫폼마다 다르므로 결과가 다를 수 있다.
이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/25-floating-point-numbers/ 입니다.