부동소수점 (Floating Point)

avatar

toFixed()

The toFixed() method of Number values formats this number using fixed-point notation

toFixed() 메서드는 Number 값을 고정 소수점 표기법을 사용해 반환한다.

흔히 자바스크립트에서 반올림을 할 때 사용하는 toFixed() 메서드이다.
다음과 같이 사용할 수 있다.

// Try it
toFixed(digits);
 
function financial(x) {
  return Number.parseFloat(x).toFixed(2);
}
 
console.log(financial(123.456));
// Expected output: "123.46"
 
console.log(financial(0.004));
// Expected output: "0.00"
 
console.log(financial("1.23e+5"));
// Expected output: "123000.00"

MDN Web Docs 발췌
> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed (opens in a new tab)

우리가 반올림을 할때 자주 사용하는 toFixed() 메서드이다.

스코어링 시스템을 구현하던 중 다음과 같은 문제가 발생했다.

const score = 123.455;
console.log(score.toFixed(2));
// 예상 값: 123.46
// 실제 값: 123.45

이를 이해하기 위해서는 부동소수점에 대해 알아야한다.

부동소수점 (Floating Point)

부동소수점은 실수를 컴퓨터에서 표현하는 방식이다.
소수점을 고정하지 않고 근사하게 표현하는 방식으로 유효숫자를 나타내는 가수부와 소수점의 위치를 나타내는 지수부로 나누어 표현한다.

//정수부
123 = 1111011
 
//소수부
0.455 * 2 = 0.91
0.91 * 2 = 1.82
0.82 * 2 = 1.64
0.64 * 2 = 1.28
0.28 * 2 = 0.56
...
 
//결과의 소수부를 이어붙이면
01110100011110101110000...
//무한소수가 된다.

1111011.0111010001111010111000010100011110101110000101

이런 이진수의 표현 한계로 인해 정확하게 표현할 수없고 근사값으로 표현하게 된다.

IEEE 754에 따른 부동소수점 표현 방식

1111011.01110100011110101110000
 
이를 정규화하면
 
1.11101101110100011110101110000 × 2^6
 
부호는 양수이므로 0
지수부는 6 에 32bit의 Bias인 127을 더한 133, 이를 2진수로 변환하면 10000101
가수부는 11101101110100011110101 (23bit)
부호(1bit)지수부(8bit)가수부(23bit)
01000010111101101110100011110101

부동소수점 01000010111101101110100011110101 으로 표기할 수 있다.

이를 다시 10진수로 변환하면

//부호는 0이므로 양수
 
//지수부
10000101(2) = 133
133 - 127 = 6
2^6
 
//가수부
1.11101101110100011110101 × 2^6
1111011.01110100011110101
 
//결과
123.45499420166015625
//반올림하면
123.45

정확한 값이 필요한 경우

Math.round()

const score = 123.455;
console.log(Math.round(score * 100) / 100);
// 123.46

Math.round를 사용하면 정확한 값을 얻을 수 있는 것처럼 보이지만

const score = 1.005;
console.log(Math.round(score * 100) / 100);
// 예상 값: 1.01
// 실제 값: 1

역시 부동소수점의 표현으로 인해 100.5는 100.49999999999999 로 표현된다

Number.Epsilon

이런 작은 오차를 해결하기 위해 Number.Epsilon을 사용할 수 있다.
Epsilon은 1과, 1보다 큰 수중에 가장 작은 차이의 부동소수점이다.
즉 부동소수점에서 표현할 수 있는 가장 작은 양수를 나타낸다.

console.log(Number.EPSILON);
// 2.220446049250313e-16
const score = 1.005;
console.log(Math.round((score + Number.EPSILON) * 100) / 100);
// 1.01