윤년 판별하기
문제
윤년은 지구가 태양을 한 바퀴 도는 데 걸리는 시간과 달력의 차이를 보정하기 위해 하루를 추가하는 제도이다.
지구가 태양을 한 바퀴 도는 데는 약 365.2422일이 걸리지만, 일반적인 달력의 1년은 365일이다.
따라서 매년 약 0.2422일, 즉 약 6시간의 차이가 발생한다.
이 차이를 보정하기 위해 일정한 규칙에 따라 2월이 29일까지 있는 해를 윤년으로 정한다.
윤년 판별 규칙
- 4로 나누어떨어지는 해는 윤년이다. (기본 규칙)
- 다만 100으로 나누어떨어지는 해는 평년이다. (예외)
- 하지만 400으로 나누어떨어지는 해는 다시 윤년이다. (예외의 예외)
앞의 규칙으로 내린 판단은 뒤의 규칙에 따라 바뀔 수 있다.
예를 들어 2100년은 4로 나누어떨어져 처음에는 윤년처럼 보이지만, 100으로도 나누어떨어지므로 최종적으로 평년이다.
반면 2000년은 4와 100으로 나누어떨어져 잠시 평년으로 보이지만, 400으로도 나누어떨어지므로 최종적으로 윤년이다.
| 연도 | 윤년 판별 결과 |
|---|---|
| 2024 | 윤년 |
| 2023 | 평년 |
| 1996 | 윤년 |
| 1900 | 평년 |
| 2000 | 윤년 |
| 2100 | 평년 |
풀이
방법 1: 판별 규칙을 순서대로 검사하기
bool is_leap_year(int year)
{
if (year % 400 == 0)
return true;
if (year % 100 == 0)
return false;
if (year % 4 == 0)
return true;
return false;
}
문제에서는 윤년의 규칙을 4의 배수이면 윤년이라는 기본 규칙부터 설명한 뒤, 100의 배수는 평년, 400의 배수는 다시 윤년이라는 예외를 차례로 덧붙인다.
하지만 이 조건들은 서로 독립적이지 않다.
400의 배수는 항상 100의 배수이고, 100의 배수는 항상 4의 배수이므로 다음과 같은 포함 관계를 가진다.
따라서 조건문에서는 가장 범위가 좁고 특수한 경우부터 검사해야 한다.
예를 들어 1900년은 4의 배수이지만 100의 배수이므로 평년이다.
따라서 4의 배수를 먼저 검사해 윤년으로 반환하면 안 된다.
추가적으로 else if를 사용해도 동작은 같지만, 각 조건에서 바로 값을 반환하므로 독립된 if문을 사용하는 편이 흐름이 더 단순하고 간결하다고 생각한다.
방법 2: 윤년이 되는 조건만 논리식으로 표현하기
bool is_leap_year(int year)
{
return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
}
방법 1에서 마지막 4의 배수 검사까지 도달했다면 앞선 100의 배수 조건을 통과한 상태다.
즉, 이 시점에는 이미 100의 배수가 아니라는 사실이 보장된다.
방법 1의 마지막 조건은 실제로 4의 배수이면서 100의 배수가 아닌 경우를 뜻한다.
따라서 윤년이 되는 조건을 정리하면 다음과 같다.
- 400의 배수인 경우
- 4의 배수이면서 100의 배수가 아닌 경우
방법 2는 이 조건을 하나의 논리식으로 옮긴 것이다.
방법1을 방법2로 재구성을 하면 무엇이 좋을까?
if 키워드가 사라졌다고 실행 속도가 빨라지는 것은 아니다.
여전히 세 개의 조건식을 검사하므로 성능 차이가 있다고 말하기 어렵다.
또한 코드가 한 줄이 되었다고 해서 반드시 더 읽기 쉬운 것도 아니다.
이 재구성은 앞선 분기를 통과했다는 사실 때문에 이후에는 어떤 조건이 암묵적으로 성립하고 있는지 파악하고,
여러 분기에 흩어진 조건을 하나의 논리적인 관점에서 다시 바라보는 데 있다.
테스트 코드
#include <assert.h>
#include <stdbool.h>
bool is_leap_year(int year);
int main(void)
{
int years[] =
{
2024, 2023, 2000, 1900, 2400,
2100, 1600, 1700, 1800, 1996,
1999, 2004, 2200, 2300, 2500,
2600, 2700, 2800, 2020, 2021
};
bool expected[] =
{
true, false, true, false, true,
false, true, false, false, true,
false, true, false, false, false,
false, false, true, true, false
};
for (int i = 0; i < sizeof(years) / sizeof(years[0]); i++)
{
assert(is_leap_year(years[i]) == expected[i]);
}
return 0;
}