Front-end Developer

0%

부동소수점형의 정밀도 문제와 decimal형

부동소수점형의 정밀도 문제

float형은 정밀도 문제가 있다. 아래 코드에서 num1과 num2가 다른데 같다고 취급하고 있다.

1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
float num1 = 0.099999999999f;
float num2 = 0.1f;

if(num1 == num2)
{
console.WriteLine("same");
}
}
  • 부동소수점형은 언제나 근사값이다.
  • 비트 수는 정해져 있는데 표현할 숫자가 너무 많다.
  • 예를 들어 0과 1 사이에는 무수히 많은 수가 있어서 정확하게 출력하기 어렵다.
  • 정수는 열거형이라 이러한 문제가 없다.
  • 위의 예시처럼 부동소수점에서 근접한 두 수는 같은 값이 될 수 있다.
  • 돈과 관련된 프로그램에서는 오차가 발생한다.

그럼에도 부동소수점 형을 쓰는 이유는?
CPU에서 자체적으로 지원하는 유일한 실수형으로 계산이 빠르다. 돈과 관련된 경우 아닌 경우에는 다른 곳에서 써도 크게 문제되지 않는다.


부동소수점형의 정밀도 문제를 해결하는 방법

1. 정수로 변환해서 쓰기

더하기, 빼기의 경우

  • 미국 달러의 경우 금액에 100을 곱해서 연산 (e.g. $10.10 + $0.01 = $10.11 -> 1010 + 1 = 1011)
  • 화면에 보여줄 때 100으로 나누고 반올림하여 보여준다.
  • 단, 정수가 표현할 수 있는 범위까지만 표현 가능하다.
    • 32비트 정수(0 ~ 4,294,967,295)에서 문제가 될 수 있다. 64비트 정수(0 ~ 9,223,372,036,853,775,807)는 범위가 커서 크게 문제되지 않지만, 환율계산과 같이 소수점 자리를 계산하는 경우, 소수점 9자리까지 계산해야 한다면 문제가 된다.

2. 문자열로 표현하기

  • 문자열은 무한의 길이를 가지기 때문에 숫자가 아닌 문자열로 저장한다.
  • 두 숫자를 계산할 때 문자열에서 각 자리의 문자를 숫자로 바꾼 뒤 뒤에서부터 한 자리 씩 계산한다.
  • 문제점

    • 받아올림, 받아내림이 번거롭다.
    • 만약 + 연산자를 잘못 사용하면 의도치 않은 결과를 얻게 된다. 예를 들어 두 숫자를 더한 값을 얻고 싶었는데, 컴퓨터가 문자열로 인식하여 문자열을 더해버려서 “10.01” + “0.01” -> “10.010.01”과 같은 값을 얻을 수 있다.
“10.01” + “0.01” [0] [1] [2] [3] [4]
string 1 0 . 0 1
string 0 . 0 1
int 1 0+0=0 . 0+0=0 1+1 =2
결과(string) 10.02

3. decimal 자료형

부동소수점형의 정밀도 문제를 해결하기 위해 c#에 존재하는 자료형

표현범위: ± 1.0 x 10 -28 ~ ± 7.9228 x 1028
정밀도: 28 ~ 29

  • CPU 자체에서 지원하는 형은 아니다.
  • 금융권에서 돈 계산에 쓰기 적합하다.
  • 일부 언어에도 (e.g. java - BigDecimal) 비슷한 해결책이 있다.

decimal 자료형 사용하기

1
2
3
4
5
decimal num1 = 10.1234567778797898989m; //ok
//m 키워드가 없으면 double이고, double형을 decimal에 대입하려 했기 때문에 오류 발생.
decimal num2 = 10.1234567778797898989;
decimal num3 = 10m; //ok
decimal num4 = 10; //ok

접미사 m을 사용한다.

  • 정수일 때는 묵시적 변환을 허용하기 때문에 안붙여도 된다.
  • 부동소수점일 때는 명시적 변환만 허용하기 때문에 반드시 붙여야 한다.

decimal과 다른 자료형 간의 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
decimal num1 = 10.1234567778797898989m;
decimal num2 = 10.123456777898989m;
decimal num3 = 10m;

//명시적 변환은 모두 ok
//묵시적 변환은 오류 발생
float num4 = num1; // 컴파일 오류
float num5 = (float)num1; //ok
num2 = num5; //컴파일 오류
num2 = (decimal)num5; //ok

int num6 = num3; //컴파일 오류
int num7 = (int)num3; //ok
num3 = num7; //ok

References
실무 프로그래밍 입문(C#)