ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 실수
    Computer Science 2021. 3. 3. 15:40

    간단한 실수 연산 예제를 만들어보자.

    >>> a = 0.1
    >>> result = 0.0
    >>> for i in range(100):
    >>>    result += a

    이 코드를 보면 a가 0.01이고 이를 100번 더했으니 1이 나오기를 예측할 수 있다. 하지만 결과는 예상과 다르다.

    >>> result
    1.0000000000000007

    앞의 방식과 계산 방식이 같은 예제를 하나 더 만들어 보자. 대신 이번에는 0.072443을 100번 더할 계획이다.

    >>> a = 0.072443
    >>> a
    0.072443
    
    >>> for i in range(100):
    >>>     result += a
    >>> result
    7.2443

    그런데, 이번에는 정확한 결과값이 나왔다. 

    이 결과를 이해하려면 컴퓨터가 실수를 표현하는 방법인 부동소수점이라는 개념을 이해해야 한다.


    부동소수점

     

    IEEE가 1985년에 제정한 ASNI/IEEE 754-1985라는 표준에 따라 실수를 표현하는데, 이 표준에 따른 표현법을 부동소수점(floating-point)라고 한다. '부'는 둥둥 떠다니는 부표를 뜻한다. 왜 이런 이름이 붙은 걸까?

    그림은 실수 123.456을 여러가지 방식으로 표현한 것이다.

    그림의 소수점 위치를 보면 앞에 있기도 하고 뒤에 있기도 하다. 마치 소수점이 떠다니는 것처럼 보인다고 해서 이러한 실수 표현 방식을 부동소수점이라고 부른다.

     


    단정도 부동소수점 / 배정도 부동소수점

    부동소수점에는 단정도 부동소수점과 배정도 부동소수점이 있다.

    -단정도(single-precision)는 실수를 32bit(4byte)로 표현하며 부호 1비트, 지수부 8비트, 가수부 23비트로 구성된다.

    -배정도(double-precision)는 실수를 64bit(8byte)로 표현하며 부호 1비트, 지수부 11비트, 가수부 52비트로 구성된다.

    실수를 표현하는 데 사용하는 비트 수가 단정도보다 두 배 많은 만큼 정밀도가 높다.

    #sys라는 모듈을 불러낸 다음 float_info를 입력하면 최댓값, 최솟값, 정밀도 등을 확인할 수 있다.
    
    >>> import sys
    >>> sys.float_info
    sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308,
    min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15,
    mant_dig=53, epsilon=2.2204466049250313e-16, radix=2, rounds=1)
    >>> sys.float_info.max
    1.7976931348623157e+308
    
    # e는 지수를 뜻하는 exponent의 줄임말이다. 
    # 1.7976931348623157 x 10^308
    >>> sys.float_info.min
    2.2250738585072014e-308
    
    #2.2250738585072014 x 10^308

    (요약) 부동소수점을 사용하면 표현 범위가 엄청나게 넓어진다.

     


    1바이트 실수 자료형 설계 

     ± 1.man x 2^exp-bias

    1.man은 가수(mantissa/fraction), 2는 밑수, exp-bias는 지수(exponent)를 의미한다.

     

    이 식을 이용해 7.75라는 10진수를 1바이트 부동소수점으로 표현해보자.

    1) 10진수를 2진수 실수로 바꾸기

    이 식은 2진수에 대한 식이므로 7.75를 2진수로 바꿔야 한다.

    7.75 = 4 + 2 +1 0.5 + 0.25 = 2^2 + 2^1 + 2^-1 + 2^-2 = 111.11

     

    2) 정규화

    (정규화(normalization)란 소수점 왼쪽에 위치한 가수 부분을 밑수보다 작은 자연수가 되도록 만드는 것이다.)

    2진수의 밑수는 2이므로 2보다 작은 자연수는 1밖에 없다. 따라서 소수점 왼쪽의 가수 부분은 항상 1이 된다.

    111.11을 정규화하면 1.1111 x 2^2

     

    3) 메모리 구조

    정규화된 부동소수점 1.1111 x 2^2를 앞의 수식과 비교해보면 man은 1111이고 exp-bias는 2다. 이제 1바이트 메모리 구조를 정하고 man과 exp값만 저장하면 설계가 끝난다. 이때 지수부와 가수부에 할당하는 비트 수에 따라 표현 범위정밀도가 결정된다.

    1바이트 부동소수점의 구성

    실수 역시 정수와 마찬가지로 첫 번째 비트는 부호를 나타낸다.  가운데 4비트는 지수부로 exp값, 마지막 3비트는 man값을 저장한다. bias는 지수의 부호를 결정하는데 쓴다. 부동소수점의 지수부에는 부호 비트가 없으며 0~15의 양수만 나타낼 수 있다.

    (음수를 사용하려면 bias를 7로 두고 exp-bias값을 실제 지수로 사용한다. bias는 2^n-1 - 1식에 지수부의 비트 수인 4를 대입하면 구할 수 있다.)

     

    정규화된 1.1111x2^2의 실제 지수는 2, bias는 7이기 때문에 exp는 9가 된다.  필요한 값을 모두 구했다.

    • 부호 → 0
    • 지수부 → 1001
    • 가수부 → 1111+

    (가수부는 3비트만 할당되는데 우리가 가진 값은 1111이다. 이런 경우에는 뒷자리 1을 생략한다. 즉, 가수부는 111이 된다.)

     

    0 1001 111 = 0100 1111 = 0x4f

    실수 7.75는 1바이트 부동소수점으로 나타내면 0x4f다.

     

     

    변환 가정에서 가수부를 담을 공간이 부족해 가수부에 들어갈 데이터인 1111에서 맨 뒤에 있는 1을 누락했기 때문에 0x4f는 7.75라는 실수를 완벽하게 표현하지 못한다.

     1.111 x 2^2 = 1 x 2^2 + 1 x 2^2 + 1 x 2^0 + 1 x 2^-1 = 7.5

    0.25만큼 차이가 나므로 정밀도가 그만큼 떨어진다.


    정밀도에 대한 고찰

    실수 자료형에서 엡실론(epsilon)이란 1.0과 그다음으로 표현 가능한 수(representable float) 사이의 차이를 말한다. 

    >>> import sys
    >>> sys.float_info.epsilon
    2.220446049250313e-16

    이전에 파이썬이 사용하는 배정도(double)의 가수부가 52 비트라고 했다. 1.0을 배정도에 맞춰 표현하면 다음과 같다.

    1.0000......0000(0:52개) x 2^0

    배정도에서 1.0 다음으로 표현할 수 있는 수는 다음과 같다.

    1.0000......0000(0:51개, 1:마지막 비트) x 2^0

    따라서 두 수의 차이는 다음과 같다.

    0.0000......0001(0:51개, 1:마지막 비트) x 2^0

    이 수를 10진수로 바꾸면 엡실론 값이 나온다.

    2.220446049250313e-16

     

    엡실론과 정밀도)

    실수가 있을 때 엡실론을 이용하면 그 실수 다음에 표현할 수 있는 수를 알아낼 수 있다. 예를 들어 배정도 실수 9.25를 부동소수점 방식으로 표현하면 1.00101x2^3이다. 이 식에서 지수 부분만 떼어 내 엡실론을 곱하면 이 실수와 다음 표현 가능한 수 사이의 차이를 구할 수 있다.

    >>> import sys
    >>> ep = sys.float_info.epsilon
    >>> a = 9.25
    >>> diff = (2**3)*ep     #1
    >>> diff
    1.7763568394002505e-15
    >>> b = a + diff     #2
    >>> b
    9.250000000000002
    

    #1에서 diff는 지수 부분인 2^3에 엡실론을 곱한 값으로 9.25와 그다음 표현 가능한 수 사이의 사이다. #2에서 b는 a에 diff를 더했으므로 9.25 다음에 표현 가능한 수를 나타낸다.

    9.25에 diff보다 작은 값을 더하면 어떤 일이 일어날까?

    >>> a
    9.25
    >>> half_diff = diff/2             #1
    >>> half_diff
    8.881784197001252e-16     #2
    >>> c = a + half_diff              #3
    >>> a == c                             #4
    True

     half_diff는 diff 값의 반절이다.  #3에서 실수와 실수를 더했으므로 c는 반드시 a보다 커야 한다. 그런데 a와 c는 같다고 나온다.

     

    a 값과 c 값이 같은 이유는 c에 더한 half_diff의 값이 diff보다 작기 때문이다. diff는 9.25와 그다음 표현 가능한 수 사이의 차이이므로 9.25에 diff보다 작은 값을 더한 수를 부동수소점 방식에서는 표현할 수 없다. 달리 표현하면 정밀도가 떨어진다는 뜻이다.

     


    마무리

    부동소수점은 지수부에 따라 아주 작은 수와 아주 큰 수를 표현할 수 있지만, 어떤 상황에서는 16과 17같이 큰 단위의 정수조차 제대로 표현할 수 없을 때도 있다. '표현 범위는 넓지만 정밀도는 낮다'는 의미다.

    'Computer Science' 카테고리의 다른 글

    컴퓨터의 동작 원리  (0) 2021.10.28
    컴파일러 언어vs 인터프리터 언어  (0) 2021.05.09
    Home server  (0) 2021.04.10
    문자와 문자열  (0) 2021.03.03
    양의 정수와 음의 정수  (0) 2021.03.03
Designed by Tistory.