[CS] 부동소수점 (Floating Point) [1]

HeeJun·2024년 1월 23일
0

Operating System

목록 보기
9/9

오늘의 주제는 부동 소수점이다.

우리는 프로그래밍 언어의 자료형을 보다보면 실수 자료형에서는 "유효자릿수"의 이야기가 항상 나온다. 또한 실수 자료형은 정수 자료형 보다 더욱 넓은 범위의 수를 저장할 수 있는데, 각각의 이유를 알아보고 정리한 내용을 다룰 예정이다.

부동 소수점

  • 부동소수점은 실수를 표현할 때 소수점의 위치가 고정되어 있지 않고 둥둥 떠다니는 실수 표현 방식

왜 부동 소수점을 사용하는지 의문이 생길 수 있다. 그 이유는 고정 소수점과 비교해보면 알 수 있다.

고정 소수점 방식은 말 그대로 소수점이 고정된 위치에 존재하는 것이다. 고정 소수점 방식으로 123.456을 표현한다고 가정하면 정수부인 123 소수부인 456 두 부분으로 나누어서 표현한다. 결국 우리에게 주어진 한정된 비트에 정수와 소수를 나누어서 저장한다면 그 범위는 매우 한정적이게 된다.

반대로 부동 소수점은 정수부 소수부가 아닌 지수부 가수부로 나눠 가수부에는 수 자체를 나타내는 123456이 저장되고 지수부에는 소수점의 위치를 나타내는 3을 저장한다. 이렇게 부동 소수점 방식을 사용하면 고정 소수점 보다 훨씬 넓은 범위의 수를 표현 할 수 있다.

물론 정수형과 다르게 항상 소수점의 위치를 파악하기 위해 지수에 대한 연산이 필요해 속도가 조금 느리다는 단점이 있다.

우리가 사용하는 대부분의 프로그래밍 언어에서 실수 자료형은 Float와 Double 두 가지가 있다.

Float 형의 경우 부호 1Bit 지수부 8Bit 가수부 23Bit 로 구성되어 있다.

Double 형의 경우 부호 1Bit 지수부 11Bit 가수부 52Bit 로 구성되어 있다.

부동 소수점은 10진수를 2진수로 변환한 후 정규화를 거쳐 가수 * 밑^지수 형태로 표현한다.

글로만 보면 이해가 힘드니, 예시도 함께 보자.

2진수 1001.001(2) 를 정규화 한 결과는 1.001001E3 으로 표현된다. (E 는 지수 표기법으로 여기서는 2 진수기 때문에 2 와 같다고 보면 된다.)

> (정규화란 가수의 첫 번째 수가 밑수보다 작은 한 자리 자연수가 되도록 소수점을 이동해 가수 * 밑^지수 형태로 만드는 행위)

이렇게 정규화 한 2진수에서 가수의 정수부의 1 은 항상 존재하기 때문에 제외하고 소수점 아래의 001001 은 가수부 23Bit 에 저장하고, 지수 3은 2진수로 변환 후 지수부 8Bit 에 저장한다.

컴퓨터는 모든 정보를 0 과 1 즉 2진수로 표현한다. 이러한 컴퓨터의 정보 표현 방식과 부동 소수점 방식으로 표현하는 실수는 항상 오차를 발생시킬 위험이 있다.

10진수 0.1 을 예시로 들어보자.

10진수 0.1 을 2진수로 표현하면 다음과 같이 0011(2) 가 무한히 반복되는 무한 소수가 된다.

0.1(10) = 0.00011001100110011.....(2)

이를 정규화 하고 그 결과를 저장하려면 가수부도 지수부도 넘칠 것이다.

고정 소수점 방식이든, 부동 소수점 방식이든 수를 표현할 수 있는 메모리가 무한하지 않기 때문에 특정 소수점 이하는 반올림, 올림, 내림, 버림 등을 진행하고 저장되기 때문에 실제 10진수와 동일한 수가 아니라 근사한 값이 저장되게 된다.

그렇기 때문에 컴퓨터에서 특정 실수는 온전히 저장되지 않고 근사한 값으로 저장되고 이는 연산에 있어 다음과 같이 오차를 발생시킨다.

Java

int a = 0.1;
int b = 1.1;

System.out.println(a + b == 1.2 ? "true" : "false")

Result : false

실수 연산은 언제나 오차가 발생할 수 있다는 것을 인지하고, 사용해야 한다.

연산 오차가 발생하면 안되는 금융권이나, 군용 소프트웨어는 조금 더 주의 할 필요가 있다. (만약...실수 연산으로 인해 내 돈이 사라지거나, 0.1의 오차로 미사일이 발사가 늦어진다면, 아마 엄청 큰일이 날 것이다.)

실수의 연산 오차를 방지하기 위해, 크게는 두 가지 방법이 있다.

1. JAVA 를 예로 들면 BigDecimal, BigInteger 와 같은 객체를 사용할 수 있다.

> BigDecimal 같은 경우 대체로 문자열 형태의 수로 초기화 된다.
> 즉, 10진수 그 형태 그대로 저장한다. 2진수로 변환해서 저장하는 과정에서 수가 손실 될 수 있는 기존 실수 자료형과 다르게 정밀하게 수를 표현할 수 있다.
> 하지만 느린 속도와 복잡한 사용법은 단점이다.

2. 이건 모든 언어에서 통용되는 것이다. 실수를 정수로 변환 후 저장한다.

> 0.1 달러를 저장해야 된다면. 10 센트로 변환해서 저장하는 것이다.
> 즉, 실수를 저장할 때 정수가 되도록 10을 곱해주고, 나중에 다시 나눠주면 되는 것이다.

이제 부동 소수점에 대해 대략적인 파악이 끝났으니, 유효자리 수와 실수 자료형의 범위에 대해 알아보자.

다양한 언어를 학습할 때 실수 자료형을 보면 항상 유효자릿수에 대한 언급이 있다.

대표적으로 Java 의 경우 float 의 경우 약 6 ~ 7자리, double 의 경우 약 15자리로 많이 알려져 있다.

많은 사람들이 의문을 갖는 것이 유효자리는 소수점 아래의 수를 의미하는 것인가? 아니면 전체 자리 수를 의미하는 것인가? 또한 어떤 방식으로 결정되는가? 이러한 의문점에 대해 하나씩 해결해 보자

일단 유효자리수의 의미는 아래와 같다.

유효자리 수 = 수의 정확도와 정밀도에 영향을 주는 수의 개수

  • 예를 들어 사과 522개가 존재한다는 것을 정확하게 전달하기 위해서는 5, 2, 2 3개의 수가 필요하다.
    이 3개의 수가 유효자리의 개수이다.

  • 실수 또한 마찬가지다. 사과 522.5 개가 있다는 것을 정확하게 전달하기 위해서는 5, 2, 2, 5 4개의 수가 필요하다.
    즉, 4개의 수가 유효자리의 개수라는 것이다.

  • 유효자리 수를 세기 위해서는 몇가지 규칙이 필요하다.
    • 정수 뒤에 붙는 0의 개수 또한 유효자리 수에 포함된다. EX) 54000 : 유효자리 수 5개(5, 4, 0, 0, 0)
    • 소수점을 가진 수에서 0이 아닌 마지막 수의 뒤에 나오는 0은 포함된다. EX) 54.0 : 유효자리 수 3개 (5, 4, 0) 0.0100 : 유효자리 수 3개 (1, 0, 0)
    • 소수점 아래의 0이 아닌 두 수 사이의 0은 포함한다. EX) 0.1004 : 유효자리 수 4개 (1, 0, 0, 4)
    • 소수점 아래 유효 숫자 이전에 나온 0은 제외한다. EX) 0.000012 : 유효자리 수 2개 (1, 2)

대략 유효자리 수가 의미하는 것이 무엇인지, 또한 어떻게 셀 수 있는지 알아봤다면 이제 실수 자료형의 유효자리 수가 어떻게 정해지는지 알아보자

Float 형을 예로 설명해보자.

  • Float 형에서 가수가 저장되는 부분의 Bit 수는 23개다. 즉, 부동 소수점 방식에서 암묵적으로 존재하는 맨앞 1비트를 포함하면 손실 없이 저장 가능한 가장 큰 수는 1.11111....1111E23(2)[소수점 아래 23Bit] 이다.

  • 즉, 위의 2진수를 10진수로 변환하면 16,777,215(= 2^24 - 1) 가 나오고, 이 수는 float 형이 정확하게 표현 할 수 있는 수중 가장 큰 수 이다.
  • 자리 수를 구하기 위해 상용로그를 취한 뒤 1을 더하면 8자리가 나온다. 하지만 8자리의 수 중 1600만은 20% 정도 뿐이 안되기에 안정적으로 7자리라고 하는 것 같다.
    • 위 2진수에서 지수가 커지든 가수가 커지든 23 Bit 최대 값에서 뒤에 0이 하나 더 붙는 순간 수에 영향을 주는 유효수가 늘어나고 23Bit 에 전부 저장할 수 없다. 즉, 유효 수 중 일부가 잘리게 되고 수의 손실이 일어난다.
    • 만약 지수가 24 라면 분명 원래의 10진수는 16,777,215 보다는 큰 수 였을 것이다. 하지만 2진수로 변환했을 때 가수부에 저장됬어야 할 비트는 23Bit 보다 많았을 것이다. 당연히 23Bit 를 넘어가는 그 아래의 수들은 저장되지 않았을 것이고, 수의 손실이 일어났을 것이다.
    • 지수가 음수라도 똑같을 것이다. 지수가 작아지든 가수가 커지든, 23 Bit 최대 값에서 뒤에 0이 하나 더 붙는 순간에 바로 수의 손실이 발생할 것이다.

  • 사실 소수는 10진수에서 유효 수를 논하는 것이 조금 어리석다. 왜냐하면 소수는 2진수로 변환할 때 꽤 상당 수가 무한 소수의 형태로 변환되기 때문에 23Bit 내에 온전히 저장될 수 있는 소수의 양이 적다.
    • 0.1(10) 만 봐도 2진수로 변환하면 0.00011001100110011.....(2) 가 된다.

결론적으로 중요한 포인트는 3 가지

  • 실수의 유효자리 수는 일종의 가이드 라인 수준
  • 23 Bit 의 가수부 내에 손실 없이 정규화 된 2진수의 모든 가수(암묵적으로 생략하는 최상위 비트 1개를 제외하고)를 저장할 수 있는지 여부를 생각하는 것
  • 언제든 실수의 저장과 연산은 오차를 가진 근사치일 수 있다는 것을 생각하는 것

처음 계획보다 글이 조금 길어졌다. 부동 소수점의 범위에 대한 내용은 다음 글에서 다시 작성할 예정이다. 부동 소수점에 대한 내용은 스스로 공부하면서 파악한 내용을 위주로 작성한 내용이니 오류가 있을 수 있다. 오류를 찾아내거나 본인이 알고 있는 것과 다르다고 생각되면 언제든지 댓글로 알려주면 확인하고 오류가 맞다면 수정을 하도록 하겠다.

또한 몇 가지 가설을 세우고 코드로 그 결과를 출력한 내역은 따로 캡쳐를 해두지 않아, 다음에 조금 더 깊이 공부를 한 후 내용을 수정할 때 첨부하도록 할 예정이다.

profile
내가 작성한 코드 한 줄로 누군가를 편하게

0개의 댓글