[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();
numObj.toFixed(1);
numObj.toFixed(6);
(1.23e+20).toFixed(2);
(1.23e-10).toFixed(2);
2.34.toFixed(1);
2.35.toFixed(1);
-2.34.toFixed(1);
(-2.34).toFixed(1);
참조