본문 바로가기
  • soldonii's devlog
Javascript 공부/TIL

200109(목) : 부동소수점, git 경고메세지, 배열 중복 제거 등

by soldonii 2020. 1. 9.

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보다 작을 경우에는 반복을 그만두도록 함으로써 숫자 비교를 할 수 있다.

 

 

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 ]

댓글