wellcome_공부일기

C++ | 02.05. 부동소수점 수(Floating Point Numbers) 본문

프로그래밍/C++

C++ | 02.05. 부동소수점 수(Floating Point Numbers)

ma_heroine 2020. 5. 6. 23:51

<목차>

1. 부동소수점 수(Floating Point Numbers)란?

2. 부동소수점 수 출력하기(Printing floating point numbers)

3. 부동소수점의 정밀도(Floating point precision)

4. float와 double & long double의 최대 최소 크기

5. 부동 소수점(Floating point data types) 사용 시 주의 사항

 

 

부동소수점 수(Floating Point Numbers)란?

- 정수는 전체 숫자를 세는 데는 좋지만, 때로 우리는 매우 큰 숫자, 즉 분수 성분(fractional component)을 가진 숫자를 저장이 필요할 때가 있습니다.

부동 소수점 유형 변수는 4320.0, -3.33 또는 0.01226과 같은 실제 숫자를 보유할 수 있는 변수입니다.

- 부동 소수점은 소수점이 "부동"할 수 있다는 사실 즉 소수점 전후의 변수 자릿수를 지원할 수 있다는 사실을 말합니다.


부동 소수점 데이터 유형에는 < float, double, long double > 세 가지가 있습니다.

정수와 마찬가지로 C++는 이러한 유형의 실제 크기를 정의하지 않습니다(그러나 최소 크기를 보장합니다). 현대 아키텍처에서 부동 소수점 표현은 거의 항상 IEEE 754 이진 형식을 따릅니다. 이 형식에서 float는 4바이트, double은 8이며, long double은 double(8바이트), 80비트(12바이트로 패딩하는 경우가 많다) 또는 16바이트와 같을 수 있습니다.

 

- 부동소수점 자료형(Floating point data types)

부동소수점 자료형은 항상 signed형을 가집니다.(양수와 음수 값을 취할 수 있습니다.)

 

www.learncpp.com

부동소수점을 아래와 같이 선언할 수 있습니다.

float fValue;
double dValue;
long double ldValue;

부동 소수점 리터럴(Literal)을 사용할 때는 항상 최소 하나의 소수점 자리(소수가 0인 경우에도)를 포함해야 합니다.

컴파일러에게 숫자 정수가 아닌 부동 소수점의 숫자라는 것을 알려주어야 하기 때문입니다.

int x{5}; // 5 means integer
double y{5.0}; // 5.0 is a floating point literal (no suffix means double type by default)
float z{5.0f}; // 5.0 is a floating point literal, f suffix means float type

- 기본적으로, 부동소수 리터럴은 double 타입을 기본으로 합니다

- f 접미사는 문자 그대로의 float 타입의 literal을 나타내기 위해 사용된다. . 

- 항상 리터럴 유형이 할당되거나 초기화에 사용되는 변수의 유형과 일치하는지 확인해야 합니다.

- 그렇지 않으면, 불필요한 변환이 발생하여 정밀도가 상실될 수 있습니다.

- 부동 소수점 리터럴을 사용해야 하는 곳에 정수 리터럴을 사용하면 안됩니다.

- 여기에는 부동 소수점 객체에 값을 초기화하거나 할당할 때, 부동 소수점 산술, 부동 소수점 값을 기대하는 호출 함수가 포함됩니다.

 

부동소수점 수 출력하기(Printing floating point numbers)

#include <iostream>
 
int main()
{
	std::cout << 5.0 << '\n'; //output: 5
	std::cout << 6.7f << '\n'; //output: 6.7
	std::cout << 9876543.21 << '\n'; //output: 9.87654e+06
}

- 첫번째 사례에서, std::cout이 우리가 5.0이라고 타이핑을 했음에도, 5를 출력하였습니다.

- 기본적으로 std::cout은 숫자의 분수(소수점) 부분이 0이면 이를 출력하지 않기 때문입니다.

 

- 두번째 사례에서, 우리가 의도한 바와 같이 잘 출력된 것을 확인할 수 있습니다. 

 

- 세번째 사례에서, 과학적 표기법(scientific notation)로 숫자가 출력되었는데, 이는 물리와 수학 계산 시 많이 사용하는 표기법입니다. (후에, scientific notation을 포스팅할 예정!)

 

부동소수점의 정밀도(Floating point precision)

- 분수 1/3을 생각해보면, 이 숫자의 소수점은 0.333333333333333333333333333333333)이고 3은 무한대로 나갑니다.
- 컴퓨터에서, 무한 길이의 숫자를 저장하기 위해 무한대의 메모리가 필요로 할 것입니다. 

- 하지만, 일반적으로 floatdouble, long double은 4 또는 8바이트밖에 가지고 있지 않습니다.

- 제한된 메모리때문에 부동 소수점 자리가 특정 수의 유의한 숫자만 저장할 수 있고, 추가 유의한 숫자가 손실된다는 것을 의미합니다.

- 실제로 저장되는 숫자는 원하는 숫자에 가까울 것 같지만 정확할 수 없다는 것을 의미합니다

부동 소수점 숫자의 정밀도(Floating point precision)는 정보 손실 없이 얼마나 많은 유의한 자릿수를 나타낼 수 있는지를 말합니다.

부동 소수점 번호를 출력할 때 std::cout은 기본 정밀도가 6으로 즉, 모든 부동 소수점 변수가 6자리(부동수의 최소 정밀도)까지만 유의하다고 가정하고, 따라서 그 후에는 무엇이든지 잘리게 됩니다.

#include <iostream>
int main()
{
    std::cout << 9.87654321f << '\n'; //output: 9.87654
    std::cout << 987.654321f << '\n'; //output: 987.654
    std::cout << 987654.321f << '\n'; //output: 987654
    std::cout << 9876543.21f << '\n'; //output: 9.87654e+006
    std::cout << 0.0000987654321f << '\n'; //output: 9.87654e-005
 
    return 0;
}

- 각 숫자는 6개의 유의한 숫자(significant digit)만 가지고 있습니다.
일부 경우에는 std::cout이 과학적 표기법(scientific notation)으로 출력 숫자로 전환됩니다.

부동 소수점 변수(floating point variable)가 갖는 정밀도의 자릿수는 크기와 저장되는 특정 값(일부 값은 다른 값보다 정밀도가 더 높음)에 따라 달라집니다.

- float가 double보다 2배 낮은 정밀도를 가집니다.

- float 값은 6~9자리의 정밀도를 가지며, 대부분의 플로트 값은 최소 7자리의 유의한 숫자를 가집니다.

- double 값은 15~18자리의 정밀도를 가지며, 대부분의 이중 값은 최소 16자리의 유의한 자릿수를 가집니다.

- long double은 얼마나 많은 바이트를 점유하느냐에 따라 최소 정밀도가 15, 18 또는 33의 유의 자릿수를 가집니다.

 

- 아래의 코드 예에서 확인해봅시다!

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    float f(123456789.0f);
    cout << std ::setprecision(16) << endl; 
    cout << 1.0 /3.0 << endl; //output: 0.3333333333333333
    cout << f << endl;        //output: 123456792

    double d(0.1);
    cout << d << endl; //output: 0.1
    cout << std ::setprecision(17);
    cout << d << endl; //output: 0.10000000000000001

    double d1(0.1);
    double d2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1);
    cout << std ::setprecision(17);
    cout << d1 << endl; //output: 0.10000000000000001
    cout << d2 << endl; //output: 0.99999999999999989

    return 0;
}

- <iomanip>은 입출력을 숫자를 조절하기 위해 포함되었습니다. ex. std::setprecision(#)

 

- 첫번째 초기화된 float f를 보면 10개의 유의미한 숫자(significant digit)를 가지지만, 출력을 해보면 7개 자리의 숫자가 유의미하게 출력됩니다.

- 이진수를 이용해서 컴퓨터가 저장하기 때문에 우리가 생각한 것과 다른 결과가 나옵니다. 

- std::setprecision(#)은 #자리까지의 소수점 정밀도를 가진다고 설정하는 것으로, 1.0/3.0이 0.3333333333333333으로 출력됩니다.

 

- 두번째로 초기화된 double d(0.1)은 정밀도를 17자리 숫자까지 설정하고 출력하면, 0.10000000000000001이 나옵니다.

- 이는 0.1에 가장 가까운 숫자가 0.10000000000000001라는 것을 알 수 있습니다.

 

-세번째로 초기화된 double d2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1)은 우리가 의도한 1.0이 아닌 0.99999999999999989이 출력되는데, 이는 0.1이 한번씩 더해질 때마다 오차가 누적된 결과입니다.

 

- 최근에 나오는 언어들은 double을 사용하지만, 아직까지는 사이즈가 2배가 되는데 cpu가속도 차이, 연산속도나 메모리면에서 부담이 될 수 있습니다.

- 그래서 파이썬으로 딥러닝을 할 때, 숫자를 많이 다루므로 float를 사용하는게 더 편합니다.

- 위에서 언급한바와 같이 내부적으로 부동소수점 수가 저장되는 방식에는 한계가 있기 때문에 float를 많이 사용합니다.

 

float와 double & long double의 최대 최소 크기

#include <iostream>
#include <limits>

using namespace std;

int main()
{
    float f;
    double d;
    long double ld;

    cout << sizeof(float) << endl; //output: 4
    cout << sizeof(d) << endl;     //output: 8
    cout << sizeof(ld) << endl;    //output: 16

    cout << numeric_limits<float>:: max() << endl;       //output: 3.40282e+38
    cout << numeric_limits<double >:: max() << endl;     //output: 1.79769e+308
    cout << numeric_limits<long double>:: max() << endl; //output: 1.18973e+4932

    cout << numeric_limits<float>:: min() << endl;       //output: 1.17549e-38
    cout << numeric_limits<double >:: min() << endl;     //output: 2.22507e-308
    cout << numeric_limits<long double>:: min() << endl; //output: 3.3621e-4932


    cout << numeric_limits<float>:: lowest() << endl;       //output: -3.40282e+38
    cout << numeric_limits<double >:: lowest() << endl;     //output: -1.79769e+308
    cout << numeric_limits<long double>:: lowest() << endl; //output: -1.18973e+4932
    
    return 0;
}

- <limits>를 포함하여 최대 최소 크기를 알아낼 수 있습니다.

-  min은 표현할 수 있는 가장 작은 숫자를 절대값으로 알려줍니다.

- 절대값 대신 가장 작은 음수를 알고 싶을 때 lowest를 이용합니다.

 

부동 소수점(Floating point data types) 사용 시 주의 사항

#include <iostream>

using namespace std;

int main()
{
    double zero = 0.0;
    double posinf = 5.0 / zero;
    double neginf = -5.0 / zero;
    double nan = zero / zero; 

    cout << posinf << endl; //output: inf
    cout << neginf << endl; //output: -inf
    cout << nan << endl;    //output: nan
 
    return 0;
}

- 숫자가 아닌 숫자들끼리의 실수는 나누면 안됩니다. 

- posinf는 positive infinite를, neginf는 negative infinite를, nan은 not a number를 의미합니다. 

- inf는 infinite로 무한대를 의미합니다.

- 위 문제는 std :: isnan사용해서 해결할 수 있습니다!

#include <iostream>
#include <cmath>

using namespace std;

int main()
{
    double zero = 0.0;
    double posinf = 5.0 / zero; 
    double neginf = -5.0 / zero;
    double nan = zero / zero; 

    cout << posinf << " " << std::isnan(posinf) << endl; //output: inf 0
    cout << neginf << " " << std::isnan(neginf) << endl; //output: -inf 0
    cout << posinf << " " << std::isinf(posinf) << endl; //output: inf 1
    cout << neginf << " " << std::isinf(neginf) << endl; //output: -inf 1
    cout << nan << " " << std::isnan(nan) << endl;       //output: nan 1
    cout << 1.0 << " " << std::isnan(1.0) << endl;       //output: 1 0

    return 0;

}

-  std :: isnan사용을 위해 <cmath>를 포함시켜줍니다.

-  std :: isnan은 "숫자가 아니다."라는 것을 의미하고, 1이 나온다면, 정말 숫자가 아니고, 0이 나온다면 숫자임을 알려줍니다.

 

 

 

 

 

 

 

 

* 해당 글은 홍정모님의 따라 배우는 C++과 learncpp 사이트를 공부하여 작성한 포스팅입니다.

Comments