# 0.1 + 0.2가 0.3이 아니라고?

"0.1 + 0.2가 0.3이 아니라고?" 프론트엔드 개발을 하다 보면 한 번쯤은 마주하게 되는 순간입니다.\
JavaScript의 부동소수점 연산 이슈의 원인과 실무에서 활용할 수 있는 해결책들에 대해 정리해두어 같은 이슈를 만났을 때 당황하지 않고 해결하고자 합니다.



## 문제의 발견

```jsx
console.log(0.1 + 0.2 === 0.3); // false
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 + 0.3); // 0.6000000000000001
console.log(0.1 * 3); // 0.30000000000000004
console.log(0.3 - 0.1); // 0.19999999999999998
```

JavaScript가 수학을 못한다고 생각할 수 있지만, 결론부터 이야기하면 컴퓨터 연산의 근본적인 한계 때문입니다.



## JavaScript 64비트 숫자의 한계(부동소수점과 고정소수점)

#### 1. 이진법 표현의 한계

컴퓨터는 모든 것을 이진법으로 처리합니다. 십진법에서 간단한 0.1도 이진법으로는 무한소수가 됩니다. `0.1 (십진법) = 0.0001100110011001100... (이진법)` [JavaScript는 IEEE 754 표준에 따라 64비트로 숫자](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Numbers_and_strings#%EC%88%AB%EC%9E%90)를 저장하는데, 무한소수를 유한한 비트로 표현하다 보니 반올림 오차가 발생합니다.



#### 2. 부동소수점 구조

<figure><img src="https://2477703702-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0zNEVFEcc7Tv0qxrOi2Y%2Fuploads%2FFh51dKlBSoqwXPGD7WuF%2FIEEE_754_Double_Floating_Point_Format.svg?alt=media&#x26;token=bb5a34a0-3801-4c07-8e2d-1ea2691e50a0" alt=""><figcaption><p><a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">https://en.wikipedia.org/wiki/Double-precision_floating-point_format</a></p></figcaption></figure>



64비트 부동소수점은 다음과 같이 구성됩니다:

* **부호 비트**: 1비트 (양수/음수)
* **지수 비트**: 11비트 (범위 결정)
* **가수 비트**: 52비트 (정밀도 결정)

모든 십진수를 정확히 표현할 수는 없습니다.(Number.MAX\_SAFE\_INTEGER는 16자리, 15자리는 안전)



{% hint style="info" %}
**부동소수점**

* 넓은 범위, 하지만 근사값
* 가수 + 지수로 소수점이 "떠다님"
* Bias(64bit 기준 1023)으로 음수/양수 지수 표현

**고정소수점**

* 제한된 범위, 하지만 정확함
* 정수부 + 소수부로 소수점이 "고정"
* 정수 연산으로 정확한 계산
{% endhint %}



## 실무에서 마주하는 상황들

#### 1. 가격 계산

```jsx
const price = 19.90;
const tax = 0.08;
const total = price * (1 + tax);
console.log(total); // 21.491999999999997
console.log(`총 가격: $${total.toFixed(2)}`); // $21.49
```



#### 2. 좌표 계산

```jsx
let position = 0;
const increment = 0.1;

for (let i = 0; i < 10; i++) {
  position += increment;
}
console.log(position); // 0.9999999999999999 (예상: 1.0)
```



#### 3. 금융 계산

```jsx
const principal = 1000;
const rate = 0.05;
const time = 12;
const interest = principal * rate * time;
console.log(interest); // 599.9999999999999
```



## 해결방법?

*   소수점 자리 제한

    ```jsx
    const a = 0.1;
    const b = 0.2;
    console.log((a + b).toFixed(1)); // "0.3"
    ```
*   정수 변환

    ```jsx
    const a = 0.1;
    const b = 0.2;
    const multiplier = Math.pow(10, 1);

    const sum = (a * multiplier) + (b * multiplier); // 3

    console.log(sum / multiplier); // 0.3
    ```
* 라이브러리 사용([big.js](https://github.com/MikeMcl/big.js), [decimal.js](https://github.com/MikeMcl/decimal.js) 등)
  *   배열 기반 십진수 표현

      ```jsx
      const Big = require('big.js');

      const a = new Big(0.1);
      const b = new Big(0.2);

      const sum = a.plus(b);
      console.log(sum.toString());  // "0.3"
      ```

      ```jsx
      const Decimal = require('decimal.js');

      const a = new Decimal(0.1);
      const b = new Decimal(0.2);

      const sum = a.plus(b);
      console.log(sum.toString());  // "0.3"
      ```



## **자바스크립트만 그럴까?**

부동소수점 이슈는 JavaScript만의 버그가 아니라 근본적인 특성입니다. Java와 같은 언어들도 같은 상황에서의 문제가 발생하고 있는데요. 중요한 것은 이 한계와 쓰임을 알고 적절한 해결책을 선택하는 것 같습니다.



## 참고

* [https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Numbers\_and\_strings#숫자](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Numbers_and_strings#%EC%88%AB%EC%9E%90)
* [https://github.com/MikeMcl/big.js](https://github.com/MikeMcl/big.js)
* [https://github.com/MikeMcl/decimal.js](https://github.com/MikeMcl/decimal.js)
* [https://en.wikipedia.org/wiki/Double-precision\_floating-point\_format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format)
