티스토리 뷰

[Javascript] 소수점 자리수 처리하기

소수점 8자리 수끼리 합산을 했는데, 소수점 14자리가 나왔습니다.

모든 수가 그런것이 아니라, 어느 시점에서 그렇게 변하였는데, 간단히 예제를 보자면:

35.27387426 "+" 1.79806217 "=" 37.07193643
37.07193643 "+" 12.00537839 "=" 49.07731482
49.07731482 "+" 0.66331486 "=" 49.74062968
49.74062968 "+" 9.04520268 "=" 58.78583236
58.78583236 "+" 1.20602704 "=" 59.9918594
59.9918594 "+" 1.20602702 "=" 61.19788642
61.19788642 "+" 0.60301351 "=" 61.80089993
61.80089993 "+" 2.14995101 "=" 63.95085094
63.95085094 "+" 1.18407028 "=" 65.13492122
65.13492122 "+" 1.18407028 "=" 66.3189915
66.3189915 "+" 13.83537715 "=" 80.15436865
80.15436865 "+" 0.59203514 "=" 80.74640378999999
80.74640379 "+" 9.85853479 "=" 90.60493858000001
90.60493858 "+" 9.39506142 "=" 100

위에서 보면 아래쪽 부분에서 부터 8자리끼리 합산하게 되면 소수점이 변경되게 되는데요. 이것은 버그가 아닙니다. 일반적으로 알려진 이슈인데요. 실수 값을 계산할 때 고정된 정밀도를 가지고 계산하기 때문입니다.

그러니까 왜?

자세히 설명하자면, IEEE 754 표준 2진 부동 소수점을 계산하는 모든 프로그래밍 언어들에게서 나타나는 이슈인데요. 자바스크립트는 64-bits 부동 소수점 표현법을 사용하는데, 이것은 Java의 double 과 같아요. 이 문제의 요점은 숫자가 2의 제곱으로 표현된다는 부분에 있어요. 분모가 2의 제곱이 아닌 유리수 (0.1 1/10과 같은)는 정확하게 표현이 될 수 없기 때문예요.

0.1 을 표준 binary64 형식으로 다음과 같이 정확하게 표현할 수 있어요.

  • 10진법으로 0.1000000000000000055511151231257827021181583404541015625
  • C99 hexfloat notation 으로 0x1.999999999999ap-4

반대로, 유리수 0.1 과 동일한 1/10 은 다음과 같이 정확히 표현할 수 있어요.

  • 10진법으로 0.1
  • C99 hexfloat noation 으로 0x1.99999999999999...p-4

이 설명을 쉽게 이해하려면, 다음과 같은 코드를 보시면 이해가 되실 거예요.

> 0.1 + 0.2 == 0.3
< false
> 0.1 + 0.2 === 0.3
< false

이해가 잘 안가신다구요? 그럼 다음의 예제를 보시면 좀 더 이해하기 쉬우실 거예요.

> 1/10 + 2/10 == 3/10
< false
> 1/8 + 2/8 == 3/8
< true

그러면 이해하셨다고 하고, 해결법을 찾아보니 다음과 같이 간단하게 처리가 되었어요.

> 80.74640378999999.toFixed(8)
< "80.74640379"

Number.prototype.toFixed()

toFixed() 메서드는 고정 소수점 표현법을 사용하여 숫자 형식을 지정해요.

문법

numObj.toFixed([digits])

인자들

digits 부가적임. 숫자의 소수점을 표현하고 싶은 자리수. 0 ~ 20까지 가능하고요, 이 값을 비워놓으면 0으로 처리해요.

리턴값

고정 소수점으로 표현된 String을 줘요.

예외처리

RangeError digits 가 너무 크거나 작을 때.

TypeError 이 메서드를 호출한 객체가 Number의 객체가 아닐 때.

설명

toFixed() 는 지수 표기법을 사용하지 않고 소수점 뒤에 정확한 자리수를 가지는 숫자 객체의 문자열을 리턴해요. 필요한 경우에 숫자는 반올림이 되고, 소수 부분에는 필요에 따라서 0으로 채워져 지정된 길이가 되요. 숫자 객체가 1e + 21 보다 큰 경우에 이 메서드는 단순히 Number.prototype.toString() 을 호출하고 지수 표기법의 문자열을 리턴해요.

예제들

var numObj = 12345.6789;

numObj.toFixed();       // Returns '12346': 소수점 전체를 반올림
numObj.toFixed(1);      // Returns '12345.7': 반올림
numObj.toFixed(6);      // Returns '12345.678900': 모자란 부분은 0으로 채움
(1.23e+20).toFixed(2);  // Returns '123000000000000000000.00'
(1.23e-10).toFixed(2);  // Returns '0.00'
2.34.toFixed(1);        // Returns '2.3'
2.35.toFixed(1);        // Returns '2.4'. 이 경우에 반올림됨을 기억하세요.
-2.34.toFixed(1);       // Returns -2.3 (연산자 우선순위 때문에 음수 리터럴은 문자열을 리턴하지 않아요.)
(-2.34).toFixed(1);     // Returns '-2.3' (괄호를 사용하지 않는다면...)

참조