명시적 강제변환 : 숫자 형태의 문자열 파싱
문자열에 포함된 숫자를 파싱하는 것은 '문자열 -> 숫자' 강제변환과 결과는 비슷하지만, 앞서 배운 타입변환과는 분명한 차이가 있다.
var a = "42";
var b = "42px";
Number(a); // 42
parseInt(a); // 42
Number(b); // NaN
parseInt(b); // 42
문자열로부터 숫자 값의 파싱은 비 숫자형 문자를 허용한다. 즉, 좌 -> 우 방향으로 파싱하다가 숫자 같지 않은 문자를 만나면 즉시 종료된다. 반면, 강제변환은 비 숫자형 문자를 허용하지 않기 때문에 NaN
를 내고 두 손을 들어버린다.
파싱은 강제변환의 대안이 될 수 없다. 비슷해 보여도 목적 자체가 다르다. 우측에 비 숫자형 문자가 있을지 확실하지 않거나 별로 상관없다면 문자열을 숫자로 파싱한다. 반드시 숫자여야만 하고 "42px"
같은 값은 되돌려야 한다면 문자열을 숫자로 강제변환 한다.
parseFloat()는 parseInt()의 쌍둥이로 문자열에서 부동소숫점 숫자를 추출한다.
parseInt()
는 문자열에 쓰는 함수임을 기억하자. 인자가 숫자라면 애당초 parseInt()
를 쓸 이유가 없다. 다른 타입(예 : true, function(){}, [1, 2, 3])도 마찬가지다.
인자가 비 문자열이면 제일 먼저 자동으로 문자열로 강제변환한다. 이는 일종의 감춰진 암시적 강제변환으로 프로그램에 이런 로직이 자꾸 들어가는 건 바람직하지 않다. 절대로 parseInt()
에 비 문자열 값을 넘기지 말자.
ES5이전에는 무수한 자바스크립트 프로그램의 버그를 일으킨 parseInt()
의 다른 함정이 있었는데, 두 번째 인자로 기수를 지정하지 않으면 문자열의 첫 번째 문자만 보고 마음대로 추정한다.
첫 번째 문자가 x나 X면 16진수로, 0이면 8진수로 문자열을 각각 임의로 해석하는것이다.
16진수 형태의 문자열은 그나마 별로 나타날 일이 드물다. 하지만 8진수는 흔하디 흔하다. 다음 예시를 보자.
var hour = parseInt(selectedHour.value);
var minute = parseInt(selectedMinute.value);
console.log("The time out selected was: " + hour + ":" + minute);
별 문제없어 보인다. hour를 08, minute에 09로 하여 실행해 보자. 결과는 0:0!!!? 이유는 8, 9모두 8진법에서 사용하는 숫자가 아니기 때문이다.
ES5 이전 시절의 디버깅 방법은 간단하지만 잊어버리기 쉽다. 이렇게 해야 완전히 안전하다.
var hour = parseInt(selectedHour.value, 10);
var minute = parseInt(selectedMinute.value, 10);
ES5 이후부터 parseInt()
는 제멋대로 추측하지 않는다. "0x"로 싲가할때만 16진수로 처리하고, 그 밖에는 두 번재 인자가 없으면 무조건 10진수로 처리한다. 왜 옛날부터 진작 이렇게 하지 않았는지 의문이다. 어쨌든 ES5 이전 환경에서는 항상 두 번째 인자 10을 전달해야 한다.
비 문자열 파싱
수년 전, 어던 사람이 자바스크립트를 비웃기라도 하듯 parseInt()
의 오작동 사례를 고발하여 주목을 받는 적이 있다고 한다.
parseInt(1 / 0, 19); // 18
"무한대를 정수로 파싱하면 당연히 무한대 아닌가? 근데.. 18..?" 당연한 생각인데 결과는 충격 그자체다. 이 코드만 보고 놓고 보면 자바스크립트는 미친 것이다.
누군가 용케 찾아내긴 했지만 그리 있음직한 코드는 아니다 그러나 잠시 이문제를 진지하게 다뤄보자.
먼저, 비 문자열을 parseInt()
첫 번째 인자로 넘긴 것 자체가 잘못되었다. 있을 수 없는 일이고 당연히 말썽이 생긴다. 하지만 이런 상황이 닥쳐도 친절한 자바스크립트 엔진은 비 문자열을 문자열로 최대한 강제변환 하려고 노력한다.
누군가는 이거야 말로 말이 안되는 로직 아니냐! 비 문자열을 받았다면 처리 자체를 거부해야지 라고 따질 것이다. 예외를 던져야 한다? ... 자바스크립트가 예외를 던지기 시작하면 그럼 거의 모든 줄을 try..catch
로 칭칭 감싸야한다..
아니면 NaN
으로 반환해야 할까? 그럴듯 하지만 다음 코드를 보자.
parseInt(new String("42"));
비 문자열 인자를 받았으니 실행되지 말아야 할까? String
객체 래퍼가 "42"로 언박싱되기를 바란다면, 42가 먼저 "42"가 된다음 다시 42로 파싱되어 반환되는 게 정말 이상한 일일가?
이렇게 종종 발생하는, 반은 명시적이고 반은 암시적인 강제변환이 꽤 유용하다고 생각한다. 예를들면
var a = {
num: 21,
toString: function () {
return String(this.num * 2);
},
};
parseInt(a); // 42
인자 값을 강제로 문자열로 바꾼 다음 파싱을 시작하는 parseInt()
의 로직은 상당히 일리가 있다. 쓰레기를 집어넣고 쓰레기를 돌려받았다고 해서 쓰레기통을 비난하지 말자! 쓰레기통은 그저 자기가 할 일을 충실히 다했을 뿐이다.
만약 무한대 같은 값을 넘긴다면 어떤 문자열로 변환하는 것이 최선이라 할수 있을까? 자바스크립트는 "Infinity"를 선택했다.
매 값마다 디폴트 문자열 표현형을 가지기 때문에 자바스크립트가 디버깅이나 추정이 불가한, 신비한 블랙박스가 아닌 점은 다행스러운 일이다.
그럼 두 번재 인자인 19로 넘어가자. 언뜻봐도 부자연 스럽고 일부러 꾸민 것 같다. 현존하는 어떤 자바스크립트 프로그램도 19진법을 사용하지 않는다.그래도 이 우스꽝스러운 19진법의 세계를 잠시 엿보자. 19진법 체계의 유효한 숫자는 0부터 9, a부터 i까지다.
그럼 parseInt( 1/0, 19)
는 parseInt( "Infinity", 19)
인데 어떻게 파싱되는 걸까?.. 첫 번째 문자 "I"는 19진수 18에 해당한다. 두 번재 "n"은 0-9, a-i 범위 밖의 문자이므로 파싱은 여기서 멈춘다.
그래서 결과는 18이다. 이제보니 꽤 그럴싸하다. 에러도 아니고 Infinity
도 아니다. 18이란 겨로가에 이르기까지는 자바스크립트엔 쉽게 폐기할 수 없는 중요한 여정이었다.
parseInt()
은 사실 예측 가능한 일관된 로직을 갖고 있다. 잘 사용하면 의미 있는 결과를 얻겠지만, 이상하게 사용해서 말도 안되는 결과가 나왔다고 자바스크립트를 탓하진 말자.
참고
- You Don't Know JS ( 한빛 미디어 )
'JAVASCRIPT' 카테고리의 다른 글
[JS][강제변환] 암시적 강제변환 : 개요 (0) | 2022.02.11 |
---|---|
[JS][강제변환] 명시적 강제변환 : * -> 불리언 (0) | 2022.02.10 |
[JS][강제변환] 명시적 강제변환 : ~(틸드) (0) | 2022.02.10 |
[JS][강제변환] 명시적 강제변환 : 날짜 <-> 숫자 (0) | 2022.02.10 |
[JS][강제변환] 명시적 강제변환 : 문자열 <-> 숫자 (0) | 2022.02.09 |
댓글