1. 문제 발견
코딩 테스트 문제 하나를 풀고 있다.
큰 숫자를 9로 나눈 나머지를 구하는 문제
로직은 간단하다. 금방 통과할 줄 알았다.
그런데 일부 케이스가 계속 실패한다.
예상: 모든 테스트 통과
실제: 특정 케이스만 실패
코드를 확인해도 문제가 보이지 않는다.
계산기로 직접 확인 → 내 답이 맞다 → Javascript가 틀린 값을 낸다.
2. 문제 재현
const num = 9007199254740992;
console.log(num % 9); // 예상: 1, 실제: 0
계산기로 확인해보면 분명 1이 나와야 한다.
하지만 Javascript는 0을 반환한다.
다른 숫자들도 테스트해봤다.
console.log(9007199254740993 % 9); // 예상: 2, 실제: 1
console.log(9007199254740994 % 9); // 예상: 3, 실제: 2
모든 결과가 1씩 작게 나왔다.
3. 원인 분석
3.1 Number.MAX_SAFE_INTEGER
Javascript의 Number 타입은 IEEE 754 부동소수점을 사용한다.
정수를 안전하게 표현할 수 있는 최댓값이 정해져 있다.
console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991 (2^53 - 1)
이 값을 넘어가면 정밀도 문제가 발생한다.
3.2 정밀도 손실
const large = 9007199254740992; // 2^53
console.log(large); // 9007199254740992
console.log(large + 1); // 9007199254740992 (!)
console.log(large + 2); // 9007199254740994
MAX_SAFE_INTEGER를 넘으면 1씩 증가하지 않는다.
2씩 건너뛰면서 표현된다.
그래서 나머지 연산도 잘못된 결과를 낸다.
왜 이런 일이 생기나
Javascript의 Number는 IEEE 754 배정밀도 부동소수점이다.
64비트를 이렇게 나눠 쓴다.
1비트: 부호
11비트: 지수
52비트: 가수 (실제 숫자 값)
정수를 표현할 때는 가수부 52비트 + 암묵적 1비트를 사용한다.
그래서 2^53 - 1까지는 모든 정수를 정확히 표현할 수 있다.
하지만 2^53부터는 가수부로 표현할 수 없는 정수가 생긴다.
// 2^53 = 9007199254740992는 표현 가능
// 2^53 + 1 = 9007199254740993는 표현 불가능
// 가장 가까운 표현 가능한 숫자인 2^53으로 반올림됨
console.log(9007199254740992 + 1 === 9007199254740992); // true
* 2^53 이후로는 2 간격으로만 표현된다.
* 2^54 이후로는 4 간격, 2^55 이후로는 8 간격이다.
표현할 수 없는 숫자는 가장 가까운 값으로 자동 반올림된다.
부동소수점의 가수부 한계 때문에 큰 정수는 일부만 표현 가능하다.
3.3 왜 9로 나눈 나머지에서 문제가 됐나
코딩 테스트에서 큰 숫자를 9로 나눈 나머지를 구하는 문제였다.
입력값이 2^53을 넘어가는 케이스가 있었다.
처음에는 로직 문제인 줄 알았는데 자료형 한계였다.
Javascript의 Number 타입은 2^53 - 1까지만 정확하게 표현할 수 있다.
4. 해결 방법
4.1 BigInt 사용
BigInt는 임의 정밀도 정수를 다룰 수 있다.
const num = 9007199254740992n; // 끝에 n을 붙인다
console.log(num % 9n); // 1n (올바른 결과)
연산자 양쪽 모두 BigInt여야 한다.
const num = 9007199254740992n;
console.log(num % 9); // Error: Cannot mix BigInt and other types
console.log(num % 9n); // 1n (정상)
4.2 문자열로 입력받는 경우
코딩 테스트에서는 큰 숫자를 문자열로 받는 경우가 많다.
const input = "9007199254740992";
const num = BigInt(input);
console.log(num % 9n); // 1n
결과를 Number로 변환할 때도 주의한다.
const result = num % 9n;
console.log(Number(result)); // 1 (안전한 범위이므로 변환 가능)
4.3 주의사항
BigInt는 부동소수점 연산을 지원하지 않는다.
const big = 10n;
console.log(big / 3n); // 3n (소수점 버림)
console.log(big / 3); // Error
JSON 직렬화도 지원하지 않는다.
JSON.stringify({ value: 10n }); // Error
필요하다면 직접 변환 로직을 작성해야 한다.
BigInt는 정수 연산만 지원하며, Number와 혼용할 수 없다.
5. 정리
Javascript의 Number는 부동소수점이라 큰 정수를 다룰 때 정밀도 문제가 생긴다.
2^53 - 1을 넘어가면 모든 정수를 정확히 표현할 수 없다.
핵심:
- Number.MAX_SAFE_INTEGER는 9007199254740991
- 이 값을 넘으면 정밀도 손실 발생
- 큰 정수 연산에는 BigInt 사용
- BigInt는 끝에 n을 붙여 표기
- 양쪽 모두 BigInt여야 연산 가능
코딩 테스트에서 큰 숫자를 다룬다면 BigInt를 먼저 고려한다.
특히 나머지 연산이나 정확한 정수 계산이 필요한 경우 필수다.
입력 범위를 항상 확인하고, 2^53을 넘을 가능성이 있다면 BigInt를 사용한다.