[React] 소수점을 포함한 숫자에 1000단위 콤마 찍기

윤후·2022년 9월 18일
0

React

목록 보기
8/18

계산기를 만들면서 결과값이나 입력값에 대해서 1000단위로 ,를 찍어야하는 기능이 필요했다. 기능자체는 간단했지만, 소수점과 같이 사용하면서 문제가 생기게 되었다. 이부분을 보완하면서 사용했던 기록을 남겨두려한다.

방법

이를 찾아보다 정규표현식과 JS의 replace함수를 사용하여 이를 찾고 바꾸는 방식으로 코드를 사용하였다. 먼저 들어오는 값을 문자열로 변환하고, replace함수를 사용해서 해당값을 찾고 이후 내가 지정해둔 값으로 변경해주면 되는 것이다. 나는 특정값을 찾은 후 ,를 넣어줄 것이기 때문에 아래와 같이 사용해 주었다.

value.toString().replace([regexp] ',');

이후 정규식의 표현[regexp]을 찾아야했다. 이 정규식을 찾기 위해서는 2가지 방법이 존재했다.

  1. 정규식 리터럴(/로 감싸는)을 사용하는 방법
  2. RegExp 객체의 생성자 함수를 사용하는 방법.

1번의 방법을 사용해서 구현해보자.

정규식 만들기

정규식 패턴은 \\안에서 작성하면 되겠다. 1000단위 숫자에 콤마를 사용하려고 하는 것이므로 아래와 같은 조건을 통해 찾을 수 있겠다.

(앞 = 문자 존재),(뒤 = 문자열 3글자)

따라서 특수문자를 사용하여 조건을 만족하는 정규식을 만들면 되겠다.

특수문자 조건

  1. 앞 = 문자 존재(시작이 경계가 아닌 부분 찾기) : \B
  • 사용 특수문자
    • \B : 단어 경계가 아닌 부분에 대응
  • 여기서 경계는 1234에서 1 앞 과 4 뒤를 의미한다. 즉, 시작과 끝이다.
  • 우리가 구분자를 삽입할 부분앞에는 무조건 문자가 하나 이상 있어야 하므로 해당 조건을 넣어준다.
  1. 뒤 = 문자열 3글자 : (\d{3})
  • 사용 특수문자
    • (x) : 'x'에 대응되고, 그것을 기억
    • \d : 숫자 문자에 대응 [0-9]와 동일
    • {n} : 앞 표현식이 n번 나타나는 부분에 대응
  • 즉, (\d{3})는 숫자가 3번 나타나는 부분에 대응되는 것을 기억해라. 라는 의미
  • 하지만, 이 경우 3333와 같이 3글자 이상인 모든케이스에 적용된다.
  1. 뒤 = 문자열 3글자 초과는 안됨! : (\d{3})+(?!\d)
  • 사용 특수문자
      • : 앞의 표현식이 1회 이상 연속으로 반복되는 부분과 대응
    • x(?!y) : 'x'뒤에 'y'가 없는경우에만 'x'에 일치
    • \d : 숫자 문자에 대응 [0-9]와 동일
  • 즉, (?!\d)는 뒤에 더이상 숫자가 없는 경우 라는 의미
  • 뒤 = (\d{3})+(?!\d)는 숫자가 3번만 나타나는 부분을 의미
  1. 앞 + 뒤 조건 모두 만족 : \B(?=(\d{3})+(?!\d))
  • 사용 특수문자
    • x(?=y) : 오직 'y'가 뒤따라오는 'x'에만 대응
  • 앞(1번)과 뒤(3번)의 조건을 합쳐준다.
  1. 이후 플래그를 사용하여 검색의 범위를 설정해준다.
  • 정규식은 플래그를 설정하여 검색조건을 달리할 수 있다.

    • g : 전역검색
    • i : 대소문자 구분 없는 검색
    • m : 다중행(multi-line) 검색
    • s : .에 개행 문자도 매칭(ES2018)
    • u : 유니코드; 패턴을 유니코드 코드 포인트의 나열로 취급합니다.
  • 해당 문자열에 대한 전역적인 검색을 한후, 1000단위의 콤마를 찍어야 하므로 플래그 g를 사용해준다.

value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');

정규표현식 기본

소숫점을 갖고 있는 숫자에 적용하기

문제는 여기서 발생한다. 소숫점을 가지고 있는 숫자에서 소수점 이하의 자리에도 ,가 찍히게 되는걸 볼 수 있었다. 생각보다 이 문제는 간단하게 해결할 수 있었다.

현재 소숫점을 기준으로 판별할 수 있는 것은 .이다. 이 .를 기준으로 split함수를 사용하여 값을 나눈후 .앞에 숫자에만 해당 함수를 적용해주면 되겠다 싶었고, 코드를 만들 수 있게 되었다.

export function AddComma(value: string | number) {
  if (typeof value === "number") {
    let splitValue = value.toString().split(".");
    splitValue[0] = splitValue[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return splitValue.join(".");
  } else {
    let splitValue = value.split(".");
    splitValue[0] = splitValue[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return splitValue.join(".");
  }
}

TypeScript를 사용하고 있었기 때문에 문자열과 숫자의 입력을 받을 경우로 나누어 값을 반환할 수 있도록 하였다.

오류

value.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');

위와 같은 방식으로 정규식을 작성할 수도 있지만, ios나 safari에서는 크로스브라우징 이슈가 생길 수도 있다고 한다.

이유를 찾아보니, 정규식에 사용된 lookbehind 패턴 때문이라고 한다.

정규식에는 두가지 알고리즘이 존재한다고 한다.

  • Deterministic Finite Automaton (DFA): 문자열의 문자를 한번만 확인한다.
  • Nondeterministic Finite Automaton (NFA): 최적의 일치를 찾을 때 까지 여러번 확인한다.

여기에서 자바스크립트는 NFA 알고리즘을 사용하고 있고 이 알고리즘의 동작으로 인해 Catastrophic Backtracking 가 일어날 수 있다고 한다.

regexp-catastrophic-backtracking

오랜 시간 실행됨에 있어서 js 엔진이 중단되는 현상.
일반적인 증상으로 특정 문자열의 경우 CPU를 100% 사용하여 중단된다고 한다.때문에 화면에 아무것또 뜨지 않았던 것.

해결법으로는..

  1. 조합의 수를 줄여서 순서대로 찾아 나가는 방식
  2. 역 추적을 방지하는 lookahead 패턴으로 개발

두번째 방식을 권장한다고 한다.

정규식의 전방 탐색(Lookahead)과 후방 탐색(Lookbehind) 패턴에 대해 간단히 알아보면...
전방 탐색은 작성한 패턴에 일치하는 영역이 존재하여도 그 값이 제외되어서 나오는 패턴이고
후방탐색은 전방 탐색이 앞에 있는 문자열을 탐색하는 거라면 후방탐색은 뒤에 있는 문자열을 탐색하는 것이라고 한다.

즉, 처음에 수정한 정규식에서 (?<!.\d*) 이 부분이 lookbehind에 해당하는 것이었다.

정규표현식 - 전후방 탐색
해결법

Rederence
정규식
정규식 크로스브라우징 이슈

profile
궁금한걸 찾아보고 공부해 정리해두는 블로그입니다.

0개의 댓글