바쁜 분은 이것만
- 문자열 전체가 숫자여야 하면
Number(), "12px"처럼 앞부분만 읽으려면parseInt(s, 10)/parseFloat(s) Number("")와Number(null)은 0이 나온다 — 빈 값 검사를 먼저- NaN 확인은
Number.isNaN(), 253 넘는 정수는BigInt로
문자열을 숫자로 바꾸는 Number(), parseInt(), parseFloat(), 단항 +는 빈 문자열, 단위가 붙은 값, null에서 결과가 갈립니다. Node.js v26에서 실행해 비교했어요.
1. 결과표: 같은 입력, 네 가지 변환
const inputs = ["12px", "", " 12 ", "0x1F", "1e3", "12.5", null, undefined, "1,000"];
const show = (v) => (typeof v === "string" ? JSON.stringify(v) : String(v));
console.log("입력".padEnd(10), "Number".padEnd(8), "parseInt".padEnd(9), "parseFloat".padEnd(11), "+");
for (const v of inputs) {
console.log(
show(v).padEnd(10),
String(Number(v)).padEnd(8),
String(parseInt(v)).padEnd(9),
String(parseFloat(v)).padEnd(11),
String(+v)
);
}
입력 Number parseInt parseFloat +
"12px" NaN 12 12 NaN
"" 0 NaN NaN 0
" 12 " 12 12 12 12
"0x1F" 31 31 0 31
"1e3" 1000 1 1000 1000
"12.5" 12.5 12 12.5 12.5
null 0 NaN NaN 0
undefined NaN NaN NaN NaN
"1,000" NaN 1 1 NaN
단항 +는 Number()와 같은 규칙입니다(차이는 6번의 BigInt뿐). 결국 Number 계열 vs parse 계열로 나뉘어요.
2. Number는 "전체", parseInt/parseFloat는 "앞에서부터"
- "12px":
Number는 전체가 숫자 형식이어야 해서 NaN,parseInt는 숫자가 아닌 글자(p)에서 멈추고 12를 돌려줍니다. - "" / null:
Number는 0, parse 계열은 NaN. 빈 입력창이 0으로 계산되는 버그가 여기서 나와요. - "0x1F":
parseFloat는 16진수를 몰라서0까지만 읽습니다. - "1e3":
parseInt만e에서 멈춰 1이에요. - "1,000": 콤마는 숫자로 안 봅니다. parseInt가 조용히 1을 돌려주는 쪽이 더 위험해요(7번).
3. parseInt에 radix(진법)를 안 주면
console.log(parseInt("08"));
console.log(parseInt("0x1F"));
console.log(parseInt("1F"));
console.log(parseInt("1F", 16));
console.log(parseInt("0b101"));
console.log(parseInt("101", 2));
console.log(parseInt("12", 0));
console.log(parseInt("12", 1));
console.log(parseInt("12", 37));
8
31
1
31
0
5
12
NaN
NaN
현재 엔진에서 "08"은 8입니다. 옛 엔진은 0으로 시작하면 8진수로 읽기도 했지만 ES5부터 빠졌어요. 지금 남은 문제는 다른 쪽입니다.
- "0x"로 시작하면 자동으로 16진수: 입력이
"0x1F"면 오류 대신 31이 들어가요. - "0b101"은 0:
0b/0o접두사는 모릅니다. 2진수는 radix 2로 지정해요. - radix는 2~36: 0은 10으로 취급, 1이나 37은 NaN.
arr.map(parseInt)의 NaN도 index가 radix로 들어가서 생깁니다(따로 정리한 글이 있어요).
10진수는 항상 parseInt(s, 10)으로 씁니다. 또 parseInt는 인자를 먼저 문자열로 바꿔 읽어요.
console.log(parseInt("1e3"), parseFloat("1e3"));
console.log(parseInt(0.0000005));
console.log(parseInt(1e21));
1 1000
5
1
0.0000005는 "5e-7"이 되어 5로 읽힙니다. 숫자 소수점 버리기는 Math.trunc()가 맞아요.
4. NaN 판별: isNaN vs Number.isNaN
const show = (v) => (typeof v === "string" ? JSON.stringify(v) : String(v));
const vals = [NaN, "abc", undefined, "", "12"];
for (const v of vals) {
console.log(show(v).padEnd(10), "isNaN:", String(isNaN(v)).padEnd(6), "Number.isNaN:", Number.isNaN(v));
}
const n = Number("12px");
console.log(n === NaN, Number.isNaN(n));
NaN isNaN: true Number.isNaN: true
"abc" isNaN: true Number.isNaN: false
undefined isNaN: true Number.isNaN: false
"" isNaN: false Number.isNaN: false
"12" isNaN: false Number.isNaN: false
false true
전역 isNaN()은 먼저 숫자로 바꾼 뒤 검사해서 "abc"도 true, 빈 문자열은 false예요. Number.isNaN()은 변환 없이 정말 NaN일 때만 true입니다. NaN은 자기 자신과도 달라서 === NaN은 항상 false예요.
5. 정수인지 확인: Number.isInteger
console.log(Number.isInteger(5), Number.isInteger(5.0), Number.isInteger(5.5));
console.log(Number.isInteger("5"), Number.isInteger(Number("5")));
console.log(Number.isInteger(Infinity), Number.isInteger(NaN));
console.log(Number.isSafeInteger(2 ** 53), Number.isSafeInteger(2 ** 53 - 1));
true true false
false true
false false
false true
정수·실수 구분이 없어 5.0도 정수입니다. 문자열은 변환하지 않아 "5"는 false. isSafeInteger는 다음 절의 안전 범위까지 확인해요.
6. 큰 수: MAX_SAFE_INTEGER를 넘으면 값이 바뀐다
console.log(Number.MAX_SAFE_INTEGER);
const id = "9007199254740993";
console.log(parseInt(id));
console.log(Number(id));
console.log(parseInt("12345678901234567890"));
console.log(9007199254740992 === 9007199254740993);
9007199254740991
9007199254740992
9007199254740992
12345678901234567000
true
Number는 64비트 부동소수점이라 253 - 1까지만 정수를 정확히 표현합니다. 그 위로는 가까운 값으로 바뀌어 ...993이 ...992가 되고, 다른 두 수가 같다고 나와요. 긴 주문번호·ID를 parseInt하면 조용히 다른 값이 됩니다(0.1+0.2 소수 오차와는 다른 문제). 이럴 땐 BigInt를 씁니다.
const id = "9007199254740993";
const big = BigInt(id);
console.log(big);
console.log(big + 1n);
try { console.log(big + 1); } catch (e) { console.log(e.name + ": " + e.message); }
try { BigInt("12px"); } catch (e) { console.log(e.name + ": " + e.message); }
console.log(JSON.stringify({ id: String(big) }));
try { JSON.stringify({ id: big }); } catch (e) { console.log(e.name + ": " + e.message); }
9007199254740993n
9007199254740994n
TypeError: Cannot mix BigInt and other types, use explicit conversions
SyntaxError: Cannot convert 12px to a BigInt
{"id":"9007199254740993"}
TypeError: Do not know how to serialize a BigInt
BigInt는 숫자 뒤에 n을 붙이고, 일반 숫자와 섞으면 TypeError입니다. BigInt("12px")는 에러를 내고, JSON.stringify도 못 하니 보낼 땐 문자열로 바꿔요.
1번에서 말한 +와 Number()의 차이가 여기서 나옵니다. Number(10n)은 10이지만 +10n은 TypeError: Cannot convert a BigInt value to a number예요.
7. 실전: 빈 값·콤마 처리 함수
const price = "1,000원";
console.log(Number(price.replace(/[^0-9.-]/g, "")));
const toNum = (s) => {
const t = String(s ?? "").trim();
if (t === "") return null;
const n = Number(t);
return Number.isNaN(n) ? null : n;
};
for (const v of ["12", " 12 ", "", "12px", null, "1e3"]) console.log(JSON.stringify(v), "->", toNum(v));
1000
"12" -> 12
" 12 " -> 12
"" -> null
"12px" -> null
null -> null
"1e3" -> 1000
콤마·단위는 숫자 외 문자를 지운 뒤 Number로 바꿉니다. "비어 있음"과 "0"을 구분하려면 빈 값을 먼저 거르고, 실패는 Number.isNaN으로 확인해요.
정리
| 상황 | 쓰는 법 | 주의 |
|---|---|---|
| 문자열 전체가 숫자여야 함 | Number(s) 또는 +s |
"", null은 0 |
| "12px" 앞부분 정수 | parseInt(s, 10) |
radix 항상 지정 |
| 숫자 소수점 버리기 | Math.trunc(n) |
parseInt(5e-7)은 5 |
| NaN인지 | Number.isNaN(n) |
전역 isNaN은 변환 후 검사 |
| 253 넘는 정수 | BigInt(s) |
Number와 섞기·JSON 불가 |
참고: MDN Web Docs — Number() constructor, parseInt(), parseFloat(), Unary plus (+), isNaN(), Number.isNaN(), Number.isInteger(), Number.MAX_SAFE_INTEGER, BigInt / ECMAScript Language Specification 21.1 Number Objects, 19.2.5 parseInt
