1. 0.1 + 0.2 === 0.3가 false인 이유
자바스크립트를 포함한 여러 프로그래밍 언어들에서 동일한 문제가 발생한다. 이는 프로그래밍 언어와는 달리 컴퓨터는 2진법만을 사용하기 때문이다. 자바스크립트에서는 숫자를 다룰 때, '64비트 IEEE 754 배정도(double-precision) 부동소수점 숫자 형식'에 따라서 숫자를 컴퓨터가 이해할 수 있는 2진법으로 변환한 후, 그 결과를 다시 10진법을 바꾸어서 전달한다.
이 때 컴퓨터의 메모리는 무한하지 않은 것에 비해, 부동소수점은 무한한 숫자이기 때문에 컴퓨터는 이를 100% 정확하게 표현할 수가 없다. 따라서 컴퓨터는 무한한 숫자와 가장 가까운 근사치를 대신 돌려주게 된다. 조금 더 살펴보자면.. 10진법은 분모의 소인수가 2 또는 5로 이루어진 분수만이 유한소수가 된다.(딱 떨어지는 소수) 1/10 = 0.1, 2 / 5 = 0.4 인 것처럼. 반면 2진법은 분모가 2의 거듭제곱일 때에만 유한소수로 표현이 가능하고, 아닐 경우에는 무한 소수(소수점 아래가 무한히 반복되는..)가 된다.
따라서 0.1은 1/10이고, 10은 소인수가 1, 2, 5, 10이므로 10진법에서는 유한소수가 되지만, 10이 2의 거듭제곱은 아니기 때문에 2진법에서는 무한 소수가 될 수 밖에 없다. 그리고 컴퓨터는 메모리가 무한대이지 않으므로 이 무한 소수를 가장 가까운 근사치로 변환하는 것이다. 그리고 2진법 근사치 값을 다시 10진법으로 변환하기 때문에 이 때의 10진법 숫자는 근사치를 10진법으로 바꾼 것이 된다.
한 마디로, 숫자가 컴퓨터로 들어갈 땐 유한 소수인 10진법이지만, 컴퓨터가 이를 받아 2진법으로 변환하는 과정에서 무한 소수이기 때문에 근사치로 변환이 되고, 근사치를 다시 10진법으로 변환하기 때문에 원래의 결과값과 다른 값이 나온다.
# 해결책
문제의 원인을 알았다면 해결은 어떻게 할 수 있을까? 다른 프로그래밍 언어와 달리 자바스크립트는 숫자형 데이터 타입이 하나뿐이라, 여러 다른 방식을 취해야 한다.
- 솔루션 1 : mathjs 라이브러리에서
math.fraction
메소드를 이용하기. - 솔루션 2 :
Number.prototype.toFixed()
메소드 사용하기.toFixed([digits])
는 0 이상 20 이하의 숫자를 인자로 전달받아, 숫자를digits
만큼의 소수점 이하 자리수를 갖는 숫자를 '문자열로 변환'해서 반환한다. 자세한 것은 MDN 문서 참조
또는 숫자 간 비교를 할 때에는 Number.EPSILON
메소드를 사용할 수도 있다. Number.EPSILON
은 자바스크립트에 내장된 특별한 숫자형 상수로, 매우 작은 값(약 2.22e-16)을 의미하고, 일반적으로 숫자 두 개를 구별하는 기준으로 사용한다. 아래 예제와 같은 방식으로 활용할 수 있다.
let n = 0;
while (true) {
n += 0.1;
if (Math.abs(n - 0.3) < Number.EPSILON) break;
}
console.log(`Stopped at ${n}`);
n이 0.3이 됐을 때, Math.abs(0.3 - 0.3)
은 정확히 0은 아니지만 0과 아주 가까운 근사치의 값이된다. 그 근사치의 값이 또 아주 작은 값인 Number.EPSILON
보다 작을 경우에는 반복을 그만두도록 함으로써 숫자 비교를 할 수 있다.
- 참고 1 : 부동소수점 실수 탐구 in Javascript
- 참고 2 : The Floating Point Guide
- 참고 3 : 책 '러닝 자바스크립트'
2. no new line at the end of file
바닐라코딩 1주차 async 과제를 우선 마치고 gitlab을 이용해서 내 개인 branch에 push한 후 Merge Request를 했는데, ken님이 'no new line at the end of file' 문구가 사라지도록 해달라고 하셔서 찾아봤다.
위 문구가 나오는 이유는 파일의 맨 끝에 공백 한 줄(\n
)이 없어서라고 한다. 없으면 없는거지 왜 저런 주의 문구를 띄우는 것일까? 그 이유는 POSIX(서로 다른 UNIX의 공통 API를 정리한 인터페이스 규격)에서 line이 아래와 같이 정의되어 있기 때문이다.
3.206 Line
A sequence of zero or more non- characters plus a terminating character.
따라서 newline character로 끝나지 않는 줄은 실제 줄로 인식이 되지 않는다고 한다.
- 결론 : 모든 코드의 마지막 줄 밑에 개행을 추가하자!
3. 배열에서 중복을 제거하는 방법
1) Set()
Set은 매개변수로 iterable한 객체를 전달받을 수 있고, 그 요소는 모두 새롭게 생성된 set에 추가가 된다. (매개변수를 명시하지 않으면 빈 set이 생성) 그리고 set은 삽입 순서대로 요소를 순회할 수 있고, 하나의 set 내 값은 한 번만 나타날 수 있다. 즉, 어떤 값은 해당 set 콜렉션 내에서 유일하다.
따라서 아래처럼 new Set(targetArr)
을 이용해서 중복을 제거할 대상 배열을 인자로 넣어준 후, 리턴된 새로운 set을 Array.from()
을 이용하여 배열로 다시 변환시켜주면 된다.
let arr = [1,1,1,1,3,5,4,1,3,2,4,1,]
let answer = new Set(arr);
console.log(answer); // Set(5) { 1, 3, 5, 4, 2}
console.log(Array.from(answer)); // [1, 3, 5, 4, 2]
2) filter와 indexOf
filter와 indexOf를 사용해서도 가능하다. 중복 제거하고자 하는 대상 배열에 filter를 사용하고, 콜백 함수 내에서 순회하는 item
를 indexOf 메소드에 넣는다. indexOf는 해당 item이 여러 차례 등장할 경우, 가장 최초의 index만을 리턴하므로, 반복되는 각 item
중 첫 item만 값이 true가 되므로 결국 중복된 요소들은 제거되고 하나씩만 남게 된다.
let arr = [1,1,1,1,3,5,4,1,3,2,4,1,]
let answer = arr.filter((item,index) => arr.indexOf(item) === index);
console.log(answer) // [ 1, 3, 5, 4, 2 ]
'Javascript 공부 > TIL' 카테고리의 다른 글
200114(화) : var, let, const 차이 (0) | 2020.01.14 |
---|---|
200110(금) : 프로토타입 (0) | 2020.01.10 |
200108(수) : 프로토타입, 객체지향 프로그래밍 (0) | 2020.01.09 |
200106(월) : 비동기, 고차함수, 일급객체, V8 엔진 등 (0) | 2020.01.07 |
191120(수) : response 순서대로 가져오기 등 (0) | 2019.11.20 |
댓글