ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 실수 표현 - 고정소수점, 부동소수점
    ☕️ java 2023. 3. 7. 13:40

    📌 실수의 메모리 표현


    컴퓨터 메모리는 2진수 체계를 기반으로 데이터를 저장한다.

    • 실수 역시 2진수 메모리 비트로 표현해야 한다 → 정수에 비해 복잡

    컴퓨터에서 실수를 표현하는 방식으로는 대표적으로 고정 소수점 방식, 부동 소수점 방식으로 나눌 수 있다.

     

    🔍 고정 소수점 방식


    💡 고정 소수점 방식 (Fixed-Point Number Representation)
    • 메모리를 정수부와 소수부로 고정으로 나누고 지정하여 처리하는 방식 

     

    • 부호 비트 - 양수 / 음수를 표현하기 위한 비트
      • 0 : 양수
      • 1 : 음수

     

     

    예시

    5.625

    = 4 + 1 + 0.5 + 0.125

    = 2^2 + 2^0 + 2^{-1} + 2^{-3}

    = 101.101(2)

     

    • 이진수 실수 계산 결괏값을 각각 정수부, 소수부 메모리 비트에 넣는다.

     

    장 / 단점

    장점

    • 메모리에 실수 표현을 직관적으로 할 수 있다.

     

    단점

    • 표현 가능한 범위가 매우 적다. (치명적인 단점)
    • 낭비되는 공간이 많이 생긴다.

    📖 표현 가능한 범위가 적다.

    Java의 float 타입을 기준으로 실수 메모리는 총 32 bit를 할당받는다.

    • 고정 소수점 방식으로 메모리를 반으로 나누어 설계하면
    • 정수부 비트에서 최대로 표현할 수 있는 숫자는 2^{15} - 1 인 32767이 된다.
    • 40000.01 이라는 실수가 있다면 표현 범위를 넘어서 메모리에 적재할 수 가 없다.

     

    📖 낭비되는 공간이 많이 생긴다.

    • 2000.12 라는 실수가 있을 경우 0.12 라는 작은 숫자를 표현하기 위해 16비트의 소수부를 모두 사용하게 된다.

     

    이런 공간 낭비를 줄이고 효율적으로 실수 메모리를 표현하기 위해서 컴퓨터는 부동 소수점 방식을 사용한다.

     

    ⭐ 부동 소수점 방식


    💡 부동 소수점(floating point) 방식
    • 소수점이 둥둥 떠다닌다 (floating) 라는 의미,
    • 고정 소수점 방식과 달리 메모리를 가수부(23bit), 지수부(8bit)로 나눈다. 

     

    • 가수부 - 실제 실수 데이터 비트들이 들어간다.
    • 지수부 - 소수점의 위치를 가리키는 제곱승이 들어간다.

     

    🆚 고정 소수점 방식

    고정 소수점

    • 12.345 ( 정수 . 소수 )

     

    부동 소수점

    • 1.xxx X $2^n$ ( 가수 x 지수 )

    부동 소수점은 직관적이지 않다.

    하지만 이런 식으로 실수를 표현하는 이유는 큰 범위의 값을 표현하기 위해서이다.

     

    고정 소수점 방식은 물리적으로 정수부와 소수부로 나누어 각각 15, 16bit만 사용할 수 있었다.

    하지만, 부동 소수점 방식은 실수의 값 자체를 가수부(23bit)에 넣어 표현한다

    ⇒ 보다 큰 비트의 범위를 가지게 된다.

     

    또한, 정수부가 크든 소수부가 크든 가수부 내에서 전체 실수를 표현한다.

    ⇒ 공간 낭비 문제를 해결할 수 있다.

     

    📖 지수(e) 표기법

    • 지수 표기법이란 아주 큰 숫자나 아주 작은 숫자를 간단하게 표기할 때 사용되는 표기법이다.
    • 긴 실수를 나타내는데 필요한 자릿수를 줄여 표현해 준다는 장점이 있다.

    double var1 = 1.234e2; // 1.234 * 10^2 = 123.4 
    
    double var2 = 1.7e+3; // 1700.0 
    double var3 = 1.7e-3; // 0.0017

     

    📌 프로그래밍에서 소수 계산 오차 발생


    컴퓨터에서 실수를 저장하는 방식에는 고정 소수점 방식부동 소수점 방식이 있다.

    • 부동 소수점 방식이 좀 더 큰 수를 표현할 수 있다.

     

    무한 소수가 있다면 이는 부동 소수점 방식이라 해도 정확하게 저장할 수 없다.

    • 메모리 한계까지 소수점을 넣고 반올림을 해준다.

     

    ⚠️ 컴퓨터의 메모리는 한정적이라 실수의 소수점을 표현할 수 있는 제한이 존재한다.

    • 부정확한 실수의 계산값을 초래한다.

     

    예시

    @Test
    void test() {
      double value1 = 12.23;
      double value2 = 34.59;
    
      assertThat(value1 + value2).isEqualTo(46.82);
    }

     

    결과

    expected: 46.82
    but was: 46.82000000000001
    
    • 기대결과인 46.82 가 나오지 않고 실제로는 46.82000000000001 가 출력된다.

     

    Why

    • 12.23 과 34.59 를 2진수로 변환하는 과정에서 소수점이 안 떨어지는 무한 소수 현상 발생!
    • 따라서 메모리 할당 크기의 한계 때문에 특정 자릿수까지의 반올림 표현밖에 하지 못한다.
    • 부정확한 값을 이용해 연산을 하게 된다.
    • 따라서 값이 부정확하게 나온다.

     

    이런 컴퓨터의 실수 연산 문제를 해결하기 위해 Java에서는 두 가지 방법을 제공

    1. int, long 정수형 타입으로 치환
    2. BigDecimal 클래스를 이용

     

    📌 소수 정확히 계산하는 방법


    정수 치환하여 계산하기

    ex) 12.23 에 100을 곱해 1223 로 정수로 치환 후 계산 → 다시 100을 나누어 소수 결괏값을 도출

    @Test
    void test() {
      double value1 = 1000.0;
      double value2 = 999.9;
    
      assertThat(value1 - value2 == 0.1).isFalse();
      
      long v1 = (int) (value1 * 10);
      long v2 = (int) (value2 * 10);
      double result = v1 - v2;
    
      assertThat(result / 10.0).isEqualTo(0.1);
    }

     

    BigDecimal 클래스

    소수의 크기가 9자리를 넘지 않으면 int 타입을 사용하고,

    18자리를 넘지 않으면 long 타입을 사용하면 된다.

    하지만, 18자리를 초과하면 BigDecimal 클래스를 사용해야 한다.

     

    📌 double 과 float


    float는 4바이트 실수, double는 8바이트 실수 값을 저장할 수 있다.

     

    컴퓨터에서 부동 소수점으로 실수를 표현하기 때문에 double과 float 간 정확도 차이가 발생한다.

    @Test
    void test() {
      assertThat(1.0 == 1.0f).isTrue();  // 결과 : true
      assertThat(1.1 == 1.1f).isFalse();  // 결과 : false
      assertThat(0.1 == 0.1f).isFalse();  // 결과 : false
      assertThat(0.9 == 0.9f).isFalse();  // 결과 : false
      assertThat(0.01 == 0.01f).isFalse();  // 결과 : false
    }

     

    ❗ 따라서, double과 float 값을 비교할 때에는 모두 float로 형변환을 하거나 정수로 변환하여 비교해야 한다.

    @Test
    void test() {
      assertThat((float) 1.1 == 1.1f).isTrue();  // 결과 : true
      assertThat(0.1f == (double) 0.1f).isTrue();  // 결과 : true
      assertThat(0.1 == (double) 0.1f).isFalse();  // 결과 : false
    }
    • (double) 0.1f ⇒ double 공간에 float의 정밀도를 갖는 값이 저장되는 것.
      • 따라서 double 형의 0.1 과 비교했을 때 같지 않다.

     

    참고자료

    https://inpa.tistory.com/entry/JAVA-☕-실수-표현부동-소수점-원리-한눈에-이해하기

    https://jminc00.tistory.com/m/18

     

    '☕️ java' 카테고리의 다른 글

    🆚 Random 함수 비교  (0) 2023.10.23
    DAO DTO / VO  (0) 2023.03.10
    BigDecimal ❓  (0) 2023.03.07
    [Java] String / StringBuffer / StringBuilde  (0) 2023.03.03
    [Java] Static 과 Final  (0) 2023.03.03

    댓글

Designed by Tistory.