ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Imprecision, overflow | CS50 Week 1
    Computer Science/CS 50 Harvard 2021. 11. 7. 01:55

    floats를 나누고 그 값을 프린트하는 프로그램을 작성해보자.

    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        float x = get_float("x: ");
    
        // Prompt user for y
        float y = get_float("y: ");
    
        // Divide x by y
        float z = x / y;
    
        printf("%f\n", z);
    }

    2개의 floats를 받아서 나눈 후 printf에서 %f를 이용해 프린트하는 로직이다. 컴파일 후 실행시켜보자.

    > make calculator_div
    clang     calculator_div.c  -lcs50 -o calculator_div
    > ./calculator_div
    x: 2
    y: 3
    0.666667
    > ./calculator_div
    x: 1
    y: 10
    0.100000

    소수점 자리가 6자리까지밖에 보이지 않는 것을 발견했다. printf에서% f의 소수점 자리를 명시해주면, 더 많은 소수점 자리를 프린트할 수 있다. 예를 들어 %.2f 의 경우엔 2자리를 %.50f의 경우엔 50자리까지 프린트할 수 있다. 50자리를 프린트하게 수정해보자.

    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt user for x
        float x = get_float("x: ");
    
        // Prompt user for y
        float y = get_float("y: ");
    
        // Divide x by y
        float z = x / y;
    
        printf("%.50f\n", z);
    }

    printf안의 argument를 변경해주고 아래와 같이 컴파일 후 돌리면, 50자리의 소수점이 프린트된다.

    > make calculator_div
    clang     calculator_div.c  -lcs50 -o calculator_div
    > ./calculator_div
    x: 2
    y: 3
    0.66666668653488159179687500000000000000000000000000
    > ./calculator_div
    x: 1
    y: 10
    0.10000000149011611938476562500000000000000000000000

    이것을 floating-point imprecision, 부동소수점이라고 하는데, real number를 컴퓨터가 이해할 수 있는 근삿값(approximated)으로 표현하는 방법이다. 컴퓨터는 무한한 숫자를 저장할 수 없다. integer 또는 floats인 경우에는 32개의 bits에 부호, 정수 그리고 소수점까지 다양한 값을 저장해야 한다. 그렇기 때문에 컴퓨터는 그 값에 가장 가까운 자리를 최대한으로 찾아서 저장한다. C언어가 아닌 다른 언어들에서 같은 방법으로 계산을 하기도 하고, 소수점을 더 많은 bits를 사용해서 저장하기도 한다고 한다.

     

    숫자 7을 저장하기 위해서는 3개의 bits를 사용해야한다.

    111

    8로 만들기 위해서는 또 다른 bit을 더해야만 한다.

    1000

    하지만, 우리가 만약 3개의 bits밖에 없다면? 

    000

    1은 보이지 않고 0이라는 숫자가 보인다. 1이 있어야 할 자리에 사용할 수 있는 자리가 없기 때문이다. 이러한 문제를 integer overflow라고 한다. integer가 너무 커서, 한정적인 bits로 표현하지 못하는 현상이다.

     

    Y2K problem

    초기에 컴퓨터가 발명되었을 때는, 멀리 내다보지 않았었다. 그래서 년도를 저장할 때, 뒤에 두 자리만 저장했었다. 예를 들어 1989년도는 89, 1991년도는 91과 같이. 하지만, 2000년도가 왔을 때, 문제가 발생했다. 00이라고 저장을 했을 때, 1900인지 2000인지 알 방법이 없었다. 이것도 2자리의 digits만 사용할 수 있게 설계해놓은 문제로 발생한 overflow 인 셈이다. 이 사건을 Y2K problem이라고 부른다. 정말 많은 전산 시스템에 오류가 생겨서 수많은 곳에서 1000년 또는 1900년 등 오류들이 찍히는 사건이 있었다. 이 사건에 대해 National Geographic에서 제작한 재밌는 다큐멘터리가 있다. 

    The Y2K Scare | National Geographic

     

    2038

    2038년, 시간을 표현할 때, 우리는 또다시 bits가 더 필요하다. 초창기 사람들은 1970년 1월 1일을 시작으로 시간을 카운트하는데 32 bits를 쓰기로 했다. 아마 그때 당시는 충분하다고 생각했던 것이다. 32-bit integer로는 우리는 약 20억(2,147,483,647)의 숫자를 셀 수 있다. 32비트로 셀 수 있는 가장 큰 숫자는 다음과 같다. $2^31 - 1$이라고도 표현한다.

    01111111111111111111111111111111

    만약 이 숫자에 1을 더하면? 다음과 같다.

    10000000000000000000000000000000

    잘 더해진 것처럼 보이지만, integer에서 첫 번째 bit는 이 숫자가 음수(negative)인지 양수(positive)인지를 나타내는 시그널이다. 즉, 위의 숫자는 $-{2^{31}}$, -2,147,483,648이다. 1이 늘어나기는커녕, 약 마이너스 21억이 되어버렸다. 이는 integer가 나타낼 수 있는 가장 작은 숫자이다. 그렇기 때문에, 컴퓨터는 2038년 어느 날, 우리가 모든 bits를 쓰게 되었을 때, 컴퓨터는 갑자기 1901년이라고 생각하게 될 것이다. 다행히도, 점점 더 좋은 하드웨어가 오늘날 생겨났고, 점점더 큰 값들을 저장할 수 있게 되었다.

     

     

    Pennies

    달러를 pennies로 변환하는 프로그램을 만들어보자.

    #include <cs50.h>
    #include <stdio.h>
      
    int main(void)
    {
        float amount = get_float("Dollar Amount: ");
        int pennies = amount * 100;
        printf("Pennies: %i\n", pennies);
    }

    달러로 얼마만큼 이 있는지 받아서 몇 개의 pennies로 환전할 수 있는지 보여주는 간단한 코드이다. 컴파일 후 프로그램을 돌려보자.

    > make pennies
    clang     pennies.c  -lcs50 -o pennies
    > ./pennies
    Dollar Amount: .99
    Pennies: 99
    > ./pennies
    Dollar Amount: 1.23
    Pennies: 123
    > ./pennies
    Dollar Amount: 4.20
    Pennies: 419

    잘 동작하는 것 같았지만, 마지막 예시에서 420개가 아닌 419의 결과가 나왔다. 아마 컴퓨터에서 4.20이 아닌 4.1999999로 저장한 것이라고 예상해볼 수 있다. 이 문제를 어떻게 해결해볼 수 있을까?

    #include <cs50.h>
    #include <math.h>
    #include <stdio.h>
      
    int main(void)
    {
        float amount = get_float("Dollar Amount: ");
        int pennies = round(amount * 100);
        printf("Pennies: %i\n", pennies);
    }

    math 라이브러리에는 round라는 함수가 있는데, 이것을 이용해서 소수점을 올림 해주면 된다.

    > make pennies
    clang     pennies.c  -lcs50 -o pennies
    > ./pennies
    Dollar Amount: 4.20
    Pennies: 420

    의도한 바대로 잘 돌아가는 것을 볼 수 있다.

     

    https://slideplayer.com/slide/13118197/

    이런 버그나 실수는 항상 일어난다. 2004년 크리스마스에 일어난 Comair Integer Overflow라는 사건이 overflow로 일어난 대표적인 큰 사건인데, flight crew를 스케쥴하는 소프트웨어가 멈춰서 그날 1100개의 비행기가 멈추는 일이 있었다. 이것 또한 overflow 때문에 일어난 일이다. 초기 비행 시스템도 200여일 주기로 멈추는 로직이었다고 한다. 만약 그것을 모르고 비행을 했다면? 정말 끔찍한 일이다. 

     

     

     

    Reference

    댓글

Designed by Tistory.