CS50 Probelm set 1 - 신용카드 번호 유효성 검사

dondonee·2023년 1월 21일
0

CS50

목록 보기
4/12
post-thumbnail

신용카드 번호 유효성 검사

신용카드 번호

신용카드를 쓰는 사람이 많기 때문에 신용카드 번호는 꽤 길다. American Express의 경우 15자리, MasterCard는 16자리, 그리고 VISA의 경우는 13자리 또는 16자리의 번호를 사용한다. 십진수를 사용하기 때문에 10^15 = 1,000,000,000,000,000개의 고유한 번호를 발급할 수 있다.

하지만 사실 신용카드 번호는 나름의 구조가 있기 때문에 그만큼 발급할 수 있는 것은 아니다. 신용카드의 앞 자릿수로 발행사를 알 수 있는데, American Express는 34 또는 37로 시작하고, MasterCard는 51, 52, 53, 54 또는 55로 시작하며, VISA는 4로 시작한다. 또한 많은 발행사들은 카드 번호의 유효성을 검사하기 위해 Luhn’s algorithm을 사용하고 있다.

Luhn’s Algorithm(룬 알고리즘)

IBM의 Hans Peter Luhn가 발명했으며, 신용카드 번호 등 식별용 번호가 유효한지 확인하기 위한 알고리즘이다. 암호화 해시 함수처럼 악의적인 공격을 막기 위한 것이 아니라, 번호 오기입 등 우연한 실수를 방지하기 위해 고안되었다. 현재는 퍼블릭 도메인이 되어 널리 쓰이고있다.

이 알고리즘을 따르고 있는 번호는 다음과 같은 방법을 통해 유효한 것인지 알 수 있다:

  1. 뒤에서 2번째 자리 숫자부터 시작해, 하나씩 건너뛰면서 2를 곱해준 뒤, 모든 digit을 합산한다.
    • 각 digit의 합산이므로 2를 곱한 결과가 12인 경우 12가 아닌 1과 2를 각각 더한다.
  2. 2로 곱하지 않은 모든 digit을 합산한다.
  3. 총 합계의 마지막 자리가 0이라면(즉 합계 모듈로 10의 결과가 0이라면) 유효한 숫자이다.


Credit 과제

지시 사항

  • 신용카드 번호를 사용자에게 입력받는다.
  • 신용카드 번호의 유효 여부를 검사하고 발행사를 식별하여 결과를 출력한다.
    • 나올 수 있는 결과: INVALID, AMEX, MASTERCARD, VISA

가이드

  • 사용자의 입력을 받기 위해 numeric 타입을 권장한다. 하이픈 등의 문자 입력을 자동 방지해주기 때문이다.
  • get_long을 이용하자. 신용카드는 최대 16자리이기 때문에 int 타입의 크기를 초과한다.

풀이

/*
    Pseudocode:
    1.사용자에게 카드번호 입력을 받아 변수에 저장한다.
    2.배열의 길이를 계산한다.
    3.길이에 맞는 배열을 생성한다.
    4.한 자리씩 각 요소에 저장한다.
    5.checksum 연산
        - 카드번호의 첫번째 숫자부터 검사한다.
        - 배열의 짝수번째 요소이면 2를 곱해준다.
        - 십의자리(한자리 숫자면 0), 일의자리를 각각 sum에 가산한다.
    6.카드의 종류를 판단한다.
        - sum % 10 != 0인 경우 INVALID
        - sum % 10 == 0인 경우 카드사의 종류를 판단한다.
    7.결과값 출력
*/
int main(void)
{
    // 카드번호 프롬프트
    long n = get_long("Number: ");

    // 카드번호의 자리수 구하기
    int count = 0;
    long nTemp = n;
    while (nTemp != 0)
    {
        nTemp = nTemp / 10;
        count++;
    }

    // 카드번호 배열에 한자리씩 저장하기
    int numbers[count];
    int exponent = count - 1;
    nTemp = n;
    for (int i = 0; i < count; i++)
    {
        long digit = (long)pow(10.0, (double)exponent - i);
        numbers[i] = (int)(nTemp / digit);
        nTemp %= digit;
    }

    // 카드번호 자릿수의 홀짝 판단. 총 자릿수가 짝수 -> 배열[짝수]일때 곱하기 2
    int parity = count % 2;

    // checksum 연산
    int sum = 0;

    for (int i = 0; i < count; i++)
    {
        int digit = numbers[i];

        if (i % 2 == parity)
            digit *= 2;

        sum += digit / 10;
        sum += digit % 10;
    }

    // 카드번호가 유효한지 식별
    string result;
    if (sum % 10 != 0)
    {
        result = "INVALID";
    }
    else
    {
        // 유효한 경우 카드사 식별
        int creditor = (numbers[0] * 10) + numbers[1];
        if ((creditor == 34 || creditor == 37) && count == 15)
        {
            result = "AMEX";
        }
        else if (creditor >= 51 && creditor <= 55 && count == 16)
        {
            result = "MASTERCARD";
        }
        else if (creditor >= 40 && creditor <= 49 && (count == 13 || count == 16))
        {
            result = "VISA";
        }
        else
        {
            result = "INVALID";
        }
    }

    // 결과 출력
    printf("%s\n", result);
}

메모

배운 것

  • C언어에서 ^는 제곱 연산자가 아니다(!)
    • C에서 ^는 XOR 연산자이다. 제곱을 구하려면 pow(밑, 지수) 함수를 사용. 단 인수와 출력값 모두 double 타입이므로 int 타입을 원한다면 형변환 해주어야 한다.
  • 변수를 재사용한다고 무조건 좋은 것은 아니다.
    • temp라는 임시 변수를 만들어서 반복문 등 지역적으로만 필요한 때마다 재사용할까 하다가… 이래도 되는건가? 싶어서 검색해보니 <유지보수하기 어렵게 코딩하는 방법>에서 유지보수를 어렵게 하려면 그렇게 해라! 라고 하는걸 발견했다ㅋㅋ 맥락이 다른 곳에서 한 변수를 재사용 하는 것은 지양하자.
    • 그런데 사용자의 인풋을 n이라는 변수에 담았을 때, 원본을 반복문에서 수정하면 안될 것 같아서 nTemp 라는 임시 변수를 만들어서 n을 수정하는 반복문마다 재사용하는 방법을 택했는데... 이런 것은 괜찮은 걸까?
  • printf는 이제 그만… lldb 디버거를 배웠다.
    • Cash 과제까지만 해도 printf로 디버깅을 할 만 했는데, 이번 과제에서는 도저히 번거로워서 못하겠다 싶었다. CS50에서 교수님이 잠깐 소개한 gdb 디버거를 사용하려고 했는데, 맥에서는 lldb라는 내장 디버거가 gdb와 비슷하면서 좋다고 해서 써보았는데 정말 편하다.

궁금한 것

  • 커밋 메세지는 얼마나 쪼개야 하는 걸까?
    • 커밋메세지는 최대한 종류마다 구분해서 하는게 좋다고 하는데.. 변수명 하나 바꾸고 커밋, 주석 조금 바꾸고 커밋, 이렇게 자주자주 해도 되는걸까?

References

과제

0개의 댓글