오늘 가져온 밈짤은 이것입니다.
벌써 어질어질하죠? 하나씩 풀어서 해석해보겠습니다.
NaN
은 숫자가 아님을 의미하는 Number 내장 객체의 프로퍼티입니다.
그런데 숫자가 아님을 의미하지만 typeof
연산자를 통해 타입을 확인하면 number
타입으로 나오게 됩니다.
이 이유는 NaN
이 숫자 데이터로 취급되기 때문입니다.
즉, 숫자 연산을 수행했지만 그 결과를 숫자로 나타낼 수 없는 경우에 NaN
을 띄우는거죠. 따라서 NaN
은 숫자가 아님이 아닌, '숫자로 표현할 수 없는 수'가 더 정확한 표현이고 number 타입으로 취급되는 것 입니다.
9999_9999_9999_9999 is 1_0000_0000_0000_0000
9999조 9999억 9999만 9999
는 1경
으로 취급됩니다.
자바스크립트는 double-precision floating-point(배정도 부동 소수점)
를 사용합니다. 이때 9999조~라는 숫자는 double-precision floating-point
로 표현할 수 있는 값의 범위를 넘어서기 때문에 double-precision floating-point
로 표현할 수 있는 가장가까운 숫자인 1경으로 반올림되는 것 입니다.
바로 뒤에 나올 문제가 똑같이 배정도 부동 소수점 연산에 대한 이야기를 다룹니다. 더 자세한 내용은 다음 문제도 함께 참조해주세요.
이 문제도 위에서 말한 배정도 부동 소수점 방식
과 긴밀한 연관이 있습니다.
0.1
과 같은 수는 우리가 일상에서 사용하는 10진법
수 입니다. 그리고 컴퓨터는 연산에 전기 신호가 흘렀다 꺼졌다를 나타내는 1
과 0
두 가지 숫자만을 사용하는 2진법
을 사용합니다. 이때 0.1
이라는 10진수는 2진수로 변환하는 과정에서 다음과 같은 무한 소수가 됩니다.
0.000110011001100110011...
인간의 뇌와는 달리 컴퓨터는 정해진 자원(메모리) 내에서 데이터를 표시하고 연산하기 때문에 위와같은 무한 소수를 모두 표시할 수 없습니다. 따라서 표현할 수 있는 자신의 범위 내에서 반올림
하여 표시하게 됩니다.
실제로 toString
을 이용해서 0.1
을 2진수로 나타내면 아래와 같은 유한 소수가 됩니다.
이런 반올림 과정에서 미세한 차이가 발생하게 되고, 그 결과로 0.1 + 0.2 == 0.3 is false
와 같은 문제가 발생하게 되는 것 입니다.
자바스크립트 밈에 껴있긴하지만, 배정도 방식을 이용하는 프로그래밍 언어(대표적으로 자바)들에서 공통적으로 발생하는 문제입니다.
실제로 0.1 + 0.2와 같은 연산이 필요하다면 소수점 자리를 자르는 toFixed()
메소드를 활용하면 문제를 해결할 수 있습니다.
Math.max()
는 인수에서 가장 큰 수를, Math.min()
은 가장 작은 수를 반환하는 메소드입니다.
근데 인수를 전달하지 않을경우 max()는 -Infinity
를 반환하고 min()은 Infinity
를 반환합니다. 말이 안되는 것 같지 않나요?
그 이유는 두 메소드의 내부 로직에 숨겨져있습니다.
Math.max()
는 초기값으로 -Infinity
를 두고 비교를 합니다. -Infinity
가 거의 모든 수보다 작기 때문입니다. 그래서 인수로 아무것도 주지않으면 초기값인 -Infinity
가 그대로 반환되기 때문에 위와같은 결과가 나타나는 것 입니다.
Math.min()
도 마찬가지로 반대입니다. 더 작은 수를 반환할 것이므로 초기값으로 Infinity
를 두고 비교를 하게 되고, 인수가 없으면 초기값을 그대로 반환하게 됩니다.
+ 연산자
는 피연산자의 자료형에 따라 아래와 같은 역할을 수행합니다.
먼저, [] + []
은 배열과 배열의 +연산을 합니다. 그리고 배열은 자바스크립트에서 객체
의 한 종류죠. 따라서 + 연산 과정에서 빈 배열을 문자열로 변환하고 문자열 연결을 하게 됩니다.
이때 빈 배열을 문자열로 변경하면 빈 문자열''
이 됩니다. 따라서 결과적으로는 '' + ''
가 되므로, 결과도 ''
가 됩니다.
[] + {}
도 마찬가지로 둘 다 객체(배열과 객체)이므로 문자열 변환 후 연결을 수행하게 됩니다.
앞의 빈 배열은 전과 마찬가지로 빈 문자열''
로 변화합니다. 그리고 문제는 뒤의 빈 객체{}
인데 이것은 toString()
의 동작과 관련이 있습니다.
toString()은 모든 객체에 존재하는 메소드이며, 해당 객체가 문자열로 표현되었을 때의 예상값을 출력해줍니다.
이때 값이 없으면 해당 타입의 기본형을 출력해줍니다. 여기서 빈 객체의 기본형은 [object Object]
이므로 '' + [object Object]
가 되어서 '[object Object]'
라는 결과가 나오게 되는 것 입니다.
마지막으로 {} + []
는 조금 복잡합니다. 먼저 앞의 {}
는 빈 객체가 아닌 빈 블록으로 해석됩니다. 즉, {} + []
이 식은 + []
와 다를 것이 없습니다. 그리고 뒤에 오는 +[]
는 빈 문자열이 될 것같지만 아닙니다. []
를 갖는 + 단항 연산자가 됩니다.
예전에 바나나 밈을 소개하면서 단항 연산자를 소개했는데요. 간략히 말하자면 피연산자를 숫자로 변환하게 됩니다.
그리고 빈 배열은 숫자로 강제 변환하면 0
이 되기 때문에 {} + []
는 0
이 되는 것 입니다.
추가적으로
{} + {}
도 있는데요. 앞의{}
는 마찬가지로 빈 블록으로 해석되고 뒤의+{}
는 단항 연산자가 되어 강제 변환이 이루어지는데 빈 객체의 강제 변환은[object Object]
입니다. 따라서 숫자로 나타낼 수 없는 연산이 이루어지기 때문에 결과는NaN
이 됩니다.
ture + true + true === 3
는 간단합니다.
자바스크립트에서 true
는 1
로 취급됩니다. while(1)
이 무한루프를 만드는 것을 생각해보면 쉽습니다. 따라서 true
가 연산 과정에서 1
로 취급되어 3
이라는 결과가 나타나게 됩니다. 반면, false
는 0
으로 취급됩니다.
방금도 언급했지만 true
는 1로 취급된다고했죠? 따라서 당연히 true - true
는 0
이 됩니다.
true
는 1
로 취급됩니다. 1은 Truthy 한 값
으로 간주되기 때문이죠. 이것이 방금 위의 두 문제의 핵심이었습니다.
이번 문제의 해답은 ==
과 ===
의 동작에 있습니다.
==
과 ===
는 비교 연산자 입니다. 예전에 비교 연산자포스트에서도 다뤘었는데요.
==
는 양쪽의 자료형을 동일하게 만든 후 비교합니다. 따라서 true == 1
에서는 어느 한쪽을 boolean이나 number로 만들어서 비교합니다. 그 결과는 당연히 둘이 똑같겠죠. true는 1이고, 1은 true로 취급되니까요.
반면, ===
는 자료형과 값을 검사해서 둘 다 일치하지 않으면 false를 뱉게 됩니다. 그래서 true와 1이 내부적으로 동일하게 취급되더라도 true
는 boolean
이고, 1
은 number
이므로 false
라는 결과를 나타내게 됩니다.
이번 문제에도 단항 연산자
와 강제 변환이 사용됩니다.
! + []
는 !
과 +[]
로 나뉩니다. 그리고 위에서 말했듯이 +[]
는 0
이고요. 따라서 !0
이 되는데 여기서 0은 false
로 간주되고, 논리 부정이므로 true
가 됩니다.
위 결과에 따라서 현재 식은 true + [] + ![]
이 됩니다.
true + []
를 계산할 차례입니다. + 연산자
에 따라 true는 true 그대로 쓰이고, 빈 배열은 빈 문자열''
로 변환됩니다. 따라서 ture + []
의 결과는 'true'
가 됩니다.
이제 식은 'true' + ![]
입니다. 앞의 피연산자가 문자열이므로 이번에는 연결을 수행합니다. 'true'
는 잠시 내버려두고 뒤의 ![]
를 확인합시다. 자바스크립트에서 빈 배열은 Truthy한 값으로 간주됩니다. 즉 ![]
은 !true
라는 것이죠. 따라서 ![]
의 결과는 false
가 되고 문자열 연결을 수행하기 때문에 결과적으로는 'truefalse'
라는 결과가 나오게 됩니다.
(!+[]+[]+![]).length === 'truefalse'.length
자, 그러면 이제 아시겠죠? 'truefalse'
는 9글자니까 (!+[]+[]+![]).length
의 결과는 당연히 9
가 나오게 됩니다.
9+"1"
은 간단하죠? 두 피연산자 중 하나가 문자열이면 연결을 수행합니다. 따라서 '91'
입니다.
91 - '1'
는 - 연산자
의 동작에 따릅니다. 좌우항 피연산자의 자료형에 따라 다른 동작을 하는 +
와는 다르게 -
는 숫자 연산만을 수행합니다. 따라서 좌우항을 모두 숫자로 변환한뒤에 연산을 하게 됩니다. 그래서 91 - 1
의 결과를 보여주는 것이죠.
그래서 코테 등에서 문자열 -> 숫자 변환을 메소드 사용하는 대신 '123' - 0와 같이 간단하게 줄일 수 있습니다.
이 내용은 다른 유명한 밈이 있었어서 다뤘던 적이 있었으므로 넘어가겠습니다.