Skip to content

Latest commit

 

History

History
184 lines (123 loc) · 6.82 KB

Number_Floating_Point.md

File metadata and controls

184 lines (123 loc) · 6.82 KB
title date author category
0.1 + 0.2 !== 0.3 인 이유 (ieee 754)
2020-07-07 17:00:00 -0700
jinsunee
Javascript

0.1 + 0.2 !== 0.3 인 이유

image

왜 이런 결과가 나올까요?

10진수 계산에 익숙해져 있는 우리는 단번에 이해하기 어려운 결과일 것 입니다. 그렇지만 저 계산은 Javascript engine 에 의해 돌아가는 브라우저 console에서 계산이 되어진 것이기 때문에 그에 대한 좀 더 내부적인 관점에서의 해석이 필요합니다. 이러한 계산 결과는 Javascript를 포함한 C, Java 등에서도 발견할 수 있는데 결론적으로,

컴퓨터에서 숫자와 숫자를 더한다는 것컴퓨터에 저장된 숫자와 컴퓨터에 저장된 숫자를 더하는 것이므로, 저장방식에 따른 오차가 생겨 위와 같은 결과가 나온 것입니다.

이제부터 컴퓨터가 숫자를 저장하고 표현하는 방식을 알아보도록 하겠습니다.

컴퓨터의 숫자 표현 방식

  • 고정 소수점(Fixed Point) 표현 방식 : 정확성이 떨어지기 때문에 높은 정밀도가 필요없는 소규모 시스템에서 간혹 쓰인다.
  • 부동 소수점(Floating Point) 표현 방식 : 고정 소수점에 비해 추가적인 연산이 필요하므로 속도가 느리다. 그렇지만 보다 더 넒은 범위의 수를 나타낼 수 있으므로 많이 사용된다.

큰 틀은 다음과 같습니다.

  1. 이진 기수법을 사용하여 10진수 -> 2진수로 바꾼다.
  2. 고정 소수점 혹은 부동 소수점 방식을 사용하여 숫자를 저장하고 표현한다.

예를 통해서 알아보겠습니다.

10진수가 이진 기수법에 의하여 2진수로 바뀐 결과로 시작해보겠습니다.

ex) 43.6875(10) = 101011.1011(2)

image

1. 고정 소수점 (Fixed Point)

2진수 변환 -> 표현/저장

말 그대로 소수점이 고정된 상태로 표현되며 저장되는 방식입니다. image

  • 부호 : 1bit (양수: 0, 음수: 1)
  • 정수 : 15bits
  • 소수 : 16bits

위의 예를 적용해보면 아래와 같습니다. image

소수부에 할당된 비트 숫자에 따라 정밀도가 달라지지만, 표현 가능한 실수의 범위와 정밀도가 제한적이기 때문에 실질적으로 잘 사용하지 않습니다.

2. 부동 소수점 (Floating Point)

2진수 변환 -> 정규화 -> 표현/저장

2진수에서 정규화를 거쳐 유효 숫자 가수와 소수점에 대한 정보를 담은 숫자 지수, 그리고 부호를 표현하는 비트로 표현하는 방식입니다.

  1. 2진수 변환 43.6875(10) = 101011.1011(2)

  2. 정규화 2진수 -> 1.xxxx * 2^n꼴로 바꾸는 것 여기서

  • xxxxx : 유효 숫자, 가수부
  • n: bias, 지수부를 구성하는 숫자

image

  1. 표현/저장 image

    - 32bits 저장 방식: 단정도 방식
      - 부호: 1bit
      - 지수부: 8bits
      - 가수부: 23bits
    
    - 64bits 저장 방식: 배정도 방식
      - 부호: 1bit
      - 지수: 11bits
      - 가수: 52bits
    

    43.6875(10) = 101011.1011(2) 에서의 표현으로 예를 들어보겠습니다.

  • 부호: 0
  • 지수부: 1111010(2) 지수 = bias + 매직넘버(32bits의 경우 127이라고 규정) 이므로, -5 + 127 = 122(10) = 1111010(2)
  • 소수부: 10111011(2)

image

0.1 + 0.2 !== 0.3 ?

자바스크립트에서는 64bit 부동소수점으로 숫자를 표현하고 있습니다.

  • 먼저 2진수로 변환해보겠습니다.

    0.1(10) => 0.0001100110011001100110011001100110011001100110011001100110011…….(2)

    0.2(10) => 0.0011001100110011001100110011001100110011001100110011001100110…….(2)

  • 정규화 과정을 거치면

    0.1(10) => 1.1001 1001 1001 1001 1001 1001………(2) * - (2 « 3) // 2의 -4승

    0.2(10) => 1.1001 1001 1001 1001 1001 1001………(2) * - (2 « 2) // 2의 -3승

  • 위와 같이 계산한다면 최종적으로 저장되는 값은 아래와 같이 가수부분의 마지막이 반올림되는데,

    0.1(10) => 0 1111011 10011001100110011001101

    0.2(10) => 0 1111100 10011001100110011001101

이 부분이 0.1 + 0.2 !== 0.3의 원인이 됩니다.

실제로 값을 확인해봅시다!

// 0.1을 변환
// 먼저 가수부의 값을 가져와서 생략되어진 1을 추가합니다. 그리고 10진수로 변경합니다.
var mantissa = parseInt('1' + '10011001100110011001101', 2); // 13421773
var exponent = -4;

// 그리고, 1.10011001100110011001101 이 아닌
// 110011001100110011001101을 연산했으므로 -23의 지수가 추가됩니다.
exponent += -23;

mantissa * Math.pow(2, exponent);
// 7.450580596923828e-9 그리고 두 개의 값을 곱해줍니다.

var result = mantissa * exponent;
// 0.10000000149011612 의 결과값이 보입니다.
// 0.2을 변환
// 먼저 가수부의 값을 가져와서 생략되어진 1을 추가합니다. 그리고 10진수로 변경합니다.
var mantissa = parseInt('1' + '10011001100110011001101', 2); // 13421773
var exponent = -3;

// 그리고, 1.10011001100110011001101 이 아닌
// 110011001100110011001101을 연산했으므로 -23의 지수가 추가됩니다.
exponent += -23;

mantissa * Math.pow(2, exponent);
// 1.4901161193847656e-8 그리고 두 개의 값을 곱해줍니다.

var result = mantissa * exponent;
// 0.20000000298023224 의 결과값이 보입니다.
// 즉, 그대로 다시 10진수로 변환해보면 아래와 같이 0.1과 다른, 0.2와 다른 값이 출력됩니다.
var number1 = 0.10000000149011612;
var number2 = 0.20000000298023224;

// 따라서, 0.1 + 0.2 값도 다를 수 밖에 없습니다.
console.log(number1 + number2); // 0.30000000447034836

대처방안

이러한 이유로 0.1 + 0.2의 결과값은 다를 수 밖에 없습니다. 이를 유의하여 사용할때에는 소수점을 고정시키는 방법을 고려해야 합니다.

var number1 = 0.10000000149011612;
var number2 = 0.20000000298023224;
var result = (number1 + number2).toFixed(1);

console.log(result); // 0.3

https://imcts.github.io/javascript-exponent-expression/ https://gsmesie692.tistory.com/94 https://hoodymong.tistory.com/50