자바스크립트에서는 정수와 실수에 대해서 분리하지 않고 오로지 number 타입만 가지고 있다. 즉, 모든 숫자는 실수라고 생각하면 된다.
🎈 실수의 계산
정수값에 대한 계산은 오차가 발생하지 않는다. 아래와 같이 1 더하기 1은 항상 2가 나온다. 절대 3이 나올 수 없다.
1 + 1 === 2 // true
하지만 실수값에 대한 계산은 우리가 생각하는 값과 차이가 존재한다. 현실세계에서 아래와 같이 0.1과 0.2를 더하면 누구나 0.3이라고 대답할 것이다.
하지만 자바스크립트로 아래의 코드를 실행해보면 false가 나오는 것을 확인할 수 있다.
0.1 + 0.2 === 0.3 // false
이는 소수의 표현 방식이 우리와 다르기 때문이다.
- 컴퓨터는 2진법을 사용한다.
- 10진법의 소수 중 일부는 2진법 변환 시 무한소수가 된다.
- 자바스크립트에서 사용하는 숫자 표현 방식에서는 64비트 형식(IEEE-754)으로 숫자를 표현하는데, 이 중 52비트는 숫자, 11비트는 소수점 위치, 1비트는 부호를 저장한다.
- 무한소수는 64비트를 초과하므로 저장 공간의 한계로 인해 유한소수로 반올림되어 근사값으로 저장하게된다.
0.1과 0.2 모두 2진법 변환 시 무한소수가 되기 때문에 근사값으로 저장되어, 둘을 더했을 때 오차가 발생하게 되는 것이다.
0.1 + 0.1 === 0.2 는 true가 나온다. 그 이유는 오차가 너무 작기 때문에 반올림 결과가 같아 보일뿐, 실제로는 오차가 발생하고 있다.(자바스크립트에서는 근사값 모두를 표현하지 않기 때문에 특정 자리에서 반올림한 값을 보여준다.)
그래서 실수에 대한 계산 결과에 대한 오차를 줄이기 위한 다양한 방법이 존재한다.
아래와 같이 표현하려는 소수점 만큼의 10 * n을 곱한 후 다시 나눠주는 방식을 사용하면 자바스크립트의 실수 연산보다는 오차를 줄일 수 있다.
const round = (num = 0, digits = 0) => {
const k = Math.pow(10, digits);
const n = num * k;
return Math.round(n) / k;
};
하지만 이 또한 오차의 범위를 줄일 수 는 있지만 Math.round를 사용하기 때문에 실제 값의 소수점 자리보다 작은 자리까지만 표현할 때 오차가 발생할 수 있다.
round(0.1 + 0.2, 1); // 0.3으로 정확하게 나온다.
round(1.75, 1); // 1.7이 아니라 1.8이 나온다.
🛒 toFixed
toFixed 함수는 숫자를 digits 만큼의 소수점 이하 자리수를 정확하게 갖는 문자열로 반환해준다.
아래와 같이 10.2132의 값을 소수점 아래 두자리까지 변환 후 문자열로 반환해준다.
10.2132.toFixed(2); // 10.21
toFixed를 사용할 때 주의해야할 사항이 있다. 아래의 숫자를 toFixed를 사용해서 확인해보자.
10.666.toFixed(2);
소수점 두 자리까지 표현하기 위해서 사용했기 때문에 결과값을 10.66으로 예측하기 쉽다. 하지만 결과는 10.67이 나온다. 그 이유는 표현하려는 자리수에 반올림하기 때문이다.
1️⃣ toFixed 함수 만들기
반올림하지 않고 원하는 소수점까지 정확하게 표현하고 싶다면 위에서 작성했던 round 함수를 응용한 다음과 같은 함수를 만들 수도있다.
기존 Math.round를 사용하던 것에서 Math.floor로 변경하였다. 이제 toFixed(10.666, 2)를 사용하면 원하던 10.66을 얻을 수 있다.
const toFixed = (num = 0, digits = 0) => {
const k = Math.pow(10, digits);
const n = num * k;
const result = String(Math.floor(n) / k);
return result.padEnd(result.indexOf('.') + digits + 1, '0');
};
하지만 또다른 예외사항이 존재한다. 아래의 코드를 확인해보면 5.09라는 황당한 결과가 나오게 된다.
toFixed(5.1, 2); // 5.09
이는 앞에서 설명했던 실수 연산의 오차 때문에 발생하게 된다. 소수점 자릿수만큼의 10을 곱하는 방식의 한계이다.
2️⃣ toFixed 다른 방법으로 만들기
이 방법은 숫자를 문자열로 치환한 후 전달 된 digits 이외의 문자열을 제거하고 남은 값을 반환하는 방식이다.
const toFixed = (number, digits = 1) => {
let num = typeof number === 'number' ? String(number) : number;
const pointPos = num.indexOf('.');
if (pointPos === -1) return Number(num).toFixed(digits);
const splitNumber = num.split('.');
const rightNum = splitNumber[1].substring(0, digits);
return `${splitNumber[0]}.${rightNum}`.padEnd(pointPos + digits + 1, '0');
};
정수값이라면 기본 제공하는 toFixed를 사용해서 반환해주고, 실수값이라면 표현하려는 자리수 까지를 제외하고 제거한다.
앞선 방식에서 예외가 발생한 상황에서도 원하는 결과가 나오는 것을 확인할 수 있다.
console.log(toFixed(5.1, 2)); // 5.10
console.log(toFixed(0.1 + 0.2, 1)); // 0.3
console.log(toFixed(0.1 + 0.2, 2)); // 0.30
console.log(toFixed(1.75, 1)); // 1.7
3️⃣ 숫자 라이브러리 사용하기
지금까지 알아본 실수값의 사용에 따른 오차를 줄이기 위해서 다양한 라이브러리를 제공하고 있다. 실수 연산을 해야하는 경우 라이브러리 사용을 검토해보는 것도 좋은 방법이다.
'Javascript' 카테고리의 다른 글
[Javascript] 원시타입에 전개연산자 사용 (0) | 2025.04.27 |
---|---|
[Node] 이메일 전송 (0) | 2025.04.13 |
[Javascript] 클립보드 (0) | 2025.04.06 |
[Javascript] EyeDropper API (0) | 2025.03.15 |
비동기함수 순차 실행 (0) | 2025.01.21 |