본문 바로가기

프로그래밍/CS

실수의 표현 - 부동소수점

실수(Real Number) = 실제 자연상에 존재하는 수량을 표시하는 수

정수(Integer) = 가지런한 수. 1보다 큰 수

소수(Fraction) = 작은 수. 1보다 작은 수.

소수점 (decimal point) = 정수부와 소수부를 구분하는 점

# 실수 = 정수.소수

(1) 고정소수점 - 정수.소수 (고정)

(2) 부동소수점

! 이것만 알고 가렴 !

  1. 4바이트 유효자릿수 7
  2. 8바이트 유효자릿수 15
  3. IEEE-754 규격의 결함으로, 유효자릿수 내에서도 실수가 완벽하게 표현되지 않을 수 있음(CPU, OS, 컴파일러, JVM의 문제가 아님)

부동소수

: 소수점이 좌우로 이동하도록 만든 2진수 표현법. 한정된 메모리 공간에 큰 수와 작은 수를 모두 표현하려면 소수점이 자유롭게 이동해야 한다.

 

        1.000       *       10³        = 1000

( 4자리 수 가수부 )( 3 : 지수부 )

가수부 : 수의 정밀도 결정

지수부 : 수의 크기 결정


부동소수점을 2진수로 변환하는 규칙(IEEE-754)

  • 부동소수점을 메모리에 저장하려면 2진수로 표현해야 함 -> 정규화(nomalized) 필요 : IEEE-754
  • 부동소수점을 2진수로 바꿀 때, 가수부와 지수부로 분리하여 변환
    • 가수부(fraction/mantissa) : sign-magnitude 방식
    • 지수부(exponent) :  Excess-K 방식
  • 32비트 float 타입 표현 : 단정도(single-precision)
    • [부호비트(1)][지수부(8)][가수부(23)]

  • 64비트 double 타입 표현 : 배정도(double-precision)
    • [부호비트(1)][지수부(11)][가수부(52)]
  • 부호비트(sign bit) : 음수(1) 양수(0)
  • 127 bias를 사용한다. 즉 2의 지수 값에 127을 더한 결과 값을 사용한다.
  • 1.xxxx 값에서 소수점 왼쪽에 있는 1을 제외한 나머지 수를 사용한다.
  • 가수부에 남는 비트가 있다면 0으로 채운다.

 

<3.14159>

System.out.println(0.0314159e2);
System.out.println(31.4159e-1);
System.out.println(314.159e-2);
System.out.println(3141.59e-3);

System.out.println(3.1415926535f); //3.1415927
System.out.println(3.141592653f); //3.1415927
System.out.println(3.14159265f); //3.1415927
System.out.println(3.1415926f); //3.1415925
System.out.println(3.141592f); //3.141592(OK)

System.out.println(314.1592f); //314.1592
System.out.println(3141.592f); //3141.592
System.out.println(31415.92f); //31415.92
System.out.println(314159.2f); //314159.2
System.out.println(3141592.6f); //3141592.5 (Error)

위 예제를 통해 일정 규칙을 찾을 수 있음 

 => 4바이트 메모리에 저장할 수 있는 부동소수점은 소수점의 위치와 상관 없이 숫자의 개수가 7개이면 거의 정상적으   로 저장하고 꺼낼 수 있다.

 이렇게 정상적으로 넣고 꺼낼 수 있는 부동소수점의 숫자 개수를 "유효자릿수"라고 부른다.

 

주의!

부동소수점은 메모리에서 꺼낸 값을 내부의 규칙에 따라 보정하여 리턴하지만, 7자리라 하더라도 값이 구겨질 수 있음(즉, 정상적으로 저장되지 않을 수 있음.)

=> 부동소수점을 2진수로 바꿀 때 IEEE754 규칙에 따라 변경하는데, 이 규칙에서 일부 부동소수점은 2진수로 정확히 변경되지 못하는 문제가 있기 때문.

 

 

실수 값을 정규화하는 방법

= 실수 값을 32비트 2진수로 만드는 방법

예: 12.375(10진수)

 

1) 소수점 앞의 정수 값을 2진수로 변환한다.

12(10진수)

= 1100(2진수)  

 

2) 소수점 뒤의 값을 2진수로 변환한다.

- 변환 규칙

- 소수점을 2로 곱하여 나온 결과에서 정수 부분만을 차례대로 표기한다.

- 소수 부분이 0이거나 반복되면 계산을 멈춘다.

- 예: 0.375(10진수)

  0.375 * 2 = 0.75  --> 0

  0.75 * 2  = 1.5   --> 1

  0.5 * 2   = 1.0   --> 1

  => 0.011(2진수)

 

3) 2진수 바꾼 최종 결과

  12.375(10진수)

  = 12(10진수) + 0.375(10진수)

  = 1100(2진수) + 0.011(2진수)

  = 1100.011(2진수)

  = 1*2^3 + 1*2^2 + 0*2^1 + 0*2^0 + 0*2^-1 + 1*2^-2 + 1*2^-3

  = 1*8 + 1*4 + 0*2 + 0*1 + 0*0.5 + 1*0.25 + 1*0.125

 

 4) 정규화 

 - 소수점의 위치를 조정하여 가수부와 지수부를 분리한다.

 - IEEE 754 명세는 다음과 같은 형식으로 가수부와 지수부를 나눈다.

   1.x1x2x3x4...x23(2진수) * 2^e

  => 소수점 왼쪽에 1만 남도록 소수점을 이동한다.

  => 소수점 왼쪽은 무조건 1이기 때문에 저장하지 않고 버린다.

  => 따라서 소수점 오른쪽 수만 가수부에 놓는다.

     즉 x1, x2 등은 가수부 1번 비트부터 23번 비트까지를 의미한다.

  => 23번 비트까지 채우지 못하면 나머지 비트는 0으로 채운다.

 - 예)

   1100.011(2진수)

  = 1.100011(2진수) * 2^3

  가수부 => 100011(2진수)

  지수부 => 3 + 127(bias) = 130(10진수) = 10000010(2진수)

 

 5) 32비트로 표현하기

   [0][10000010][10001100000000000000000]

   => 0100_0001_0100_0110_0000_0000_0000_0000

   => 0x41460000

 

주의!

- 유효 자릿수의 부동소수점이라도 정규화할 때 2진수로 딱 떨어지지 않은 경우가 있다.

- 예) 2.127

2 => 0010

0.127 => 

0.127 * 2 = 0.254 --> 0

0.254 * 2 = 0.508 --> 0

0.508 * 2 = 1.016 --> 1

0.016 * 2 = 0.032 --> 0

0.032 * 2 = 0.064 --> 0

0.064 * 2 = 0.128 --> 0

0.128 * 2 = 0.256 --> 0

0.256 * 2 = 0.512 --> 0

0.512 * 2 = 1.024 --> 1

0.024 * 2 = 0.048 --> 0

....

이처럼 2진수로 완벽히 표현할 수 없는 수가 있다.

0.00000....1 의 오차가 있다.

그래서 부동소수점은 정수와 다르게 정확하게 메모리에 저장되지 않는다.