본문 바로가기
JAVASCRIPT

[JS][강제변환] 명시적 강제변환 : 숫자 형태의 문자열 파싱

by KBS 2022. 2. 10.
728x90

명시적 강제변환 : 숫자 형태의 문자열 파싱

문자열에 포함된 숫자를 파싱하는 것은 '문자열 -> 숫자' 강제변환과 결과는 비슷하지만, 앞서 배운 타입변환과는 분명한 차이가 있다.

 

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 ( 한빛 미디어 )
728x90

댓글