leetcode를 풀면서 ~~연산자를 사용한 풀이를 보고 되게 유용한 연산자라고 생각돼 간략하게 정리해보려한다. double tilde연산자를 이해하기 위해 tilde연산자를 알아보고나서 double tilde연산자에 대해서 알아볼 계획이다.
tilde 연산자는 비트연산자로 NOT의 기능을 한다고 생각하면 된다.
아래와 같이 2진수일 때 0과 1만 뒤바꾸면 된다.
const a = 5; // 0000000000000101
console.log(~a); // 1111111111111010
// expected output: -6
const b = -3; // 1111111111111101
console.log(~b); // 0000000000000010
// expected output: 2
하지만 저것을 콘솔에 실행시켜 보면 expected output마냥 -6,2만 나오게 된다. 이를 이진수로 직접 눈으로 확인 하는 법은 없을까?
toString
메소드를 활용하면 10진수를 간단하게 2진수로 변환할 수 있다.
const a = 8
console.log(a.toString(2)) // 1000
하지만 이렇게 했을 때 한가지 문제점이 발생한다. 아래의 코드가 어떻게 실행되는지 예상해보자.
const b = -8
console.log(b.toString(2))
1111111111111111111111111111000 이렇게 1000의 보수가 나와야 할것만 같지만 -1000으로 나와버린다. 이렇게되면 not연산자를 제대로 확인할 수 없다.
이를 해결하기 위해 비트 시프트 연산자 중 >>>
를 활용할 수 있다.
>>>
연산자>>>
연산자는 >>
연산자와 비슷하지만 둘의 차이는 값이 음수 일 때 차이가 있다.
mdn의 예시로 확인해보자.
>>
연산자 예시-9 (10진수): 11111111111111111111111111110111 (2진수)
--------------------------------
-9 >> 2 (10진수): 11111111111111111111111111111101 (2진수) = -3 (10진수)
>>>
연산자 예시-9 (10진수): 11111111111111111111111111110111 (2진수)
--------------------------------
-9 >>> 2 (10진수): 00111111111111111111111111111101 (2진수) = 1073741821 (10진수)
차이는 >>
연산자는 1이라면 1로 채우면서 왼쪽으로 밀지만 >>>
연산자는 0으로 채우면서 민다.
이 특징을 활용하여 음수 역시 2진수로 변환시킬 수 있다.
이제 다시 위로 돌아가자.
아래 코드와 같이 b>>>0
를 활용해서 음수역시 2진수로 볼 수 있다.
const b = -8
console.log((b>>>0).toString(2))
//11111111111111111111111111111000
이제 tilde(~)연산자가 어떻게 되는지 확인했다.
이글의 목적이었던 double tilde(~~)연산자에 대해서 알아보자. 왜 이렇게 뻘소리가 길어졌는지...??? 본론은 간단하다!
사실 별거 없다. tilde를 2번 반복해주는 것이다.
let k = 8
이라고 했을 때 ~k
는 무엇일까? -9
이다. (why? ~
은 보수가 아닌 not이기 때문) 그럼 ~~k
는?? 그렇다 원래대로 8이다.
이 double tilde연산자가 어떻게 유용하게 사용되는지 알아보자.
숫자에 ~
연산을 하게되면서 소수점들은 버려지게된다. 즉, ~~
를 활용하면 Math.floor()
처럼 활용할 수 있다.
Math.floor()
대신에 활용하는데 장점과 단점이 있다.
속도 측면에서 ~~
가 빠르다고 한다. The Mysterious Double Tilde (~~) Operation 이 블로그에 보면 크롬, Safari,iPhone XS 각각Math.floor
, ~~
, parseInt
의 속도를 비교한 결과가 있다.
결과는 ~~
, Math.floor
, parseInt
순으로 ~~
가 가장 빠른 퍼포먼스를 보여줬다.
코드에 ~~
가 덕지덕지 있다고 상상해보자. 이해가 쉽지 않을 것이다. 복잡한 코드 또는 협업하는 과정에서는 가독성이 좋지 않기 때문에 사용하지 않는편이 좋을 것 같다.
위의 경우가 언제쓰일까 싶지만 undefined+3 => NaN
이런 상황이 자주 있었을 것이다.
예시 하나만 작성해 보자.
배열[1,1,1,2,2,3,3,3,3]을 각 숫자가 몇개인지 객체에 저장해보자.
const arr = [1,1,1,2,2,3,3,3,3]
const obj1 = {}
arr.forEach(v=>{
if(obj1[v]) obj1[v]+=1
else obj1[v]=1
})
//obj1 {1: 3, 2: 2, 3: 4}
위와같이 obj에 key값이 있는지 확인해주는 작업이 필요하다. 그렇지 않으면 NaN이 나올 것이다.
그럼 ~~
를 활용해 다시 코드를 작성해보자.
const obj2 = {}
arr.forEach(v=>obj2[v]= ~~obj2[v]+1)
//obj2 {1: 3, 2: 2, 3: 4}
undefined
가 나올 수 있는 상황을 ~~
연산자를 이용해서 간단하게 해결할 수 있다. 알고리즘 답안을 보면서 배운 연산자인 만큼 알고리즘에서 유용하게 사용할 수 있을 것 같다.
이외에도 ~~
연산자는 다양하게 활용될 수 있다고 생각된다. ~~
연산자를 알아보면서 비트쉬프트 연산자도 공부하게 됐고, ~
연산자에 대해서도 조금은 자세하게 알아볼 수 있어서 좋았다.
I appreciate the thorough explanation; it was very informative, and I gained a lot of knowledge from it. Toca Life World mod APK
잘 배우고 갑니다. 감사합니다