Как одной математической формулой по номеру месяца посчитать количество дней в нем?

Рассказывает Куртис МакЭнроэ 


Недавно после длительного времени, проведенного без сна, я задумался над тем, как можно узнать количество дней в месяце, зная его номер. Существуют детские стишки на эту тему, есть способ подсчета по костяшкам, но эти методы не устраивают меня — я погрузился в поиск математической формулы, которую и вывел спустя некоторое время.

Итак, задача

Формально Другими словами, нам нужно получить функцию f(x), которая бы давала следующий список значений (который я, кстати говоря, нашел где-то в интернете, а не вывел с помощью мнемонических правил):
table1
Стоит учесть, что в качестве аргумента мы получаем только номер месяца, т.е. мы не учитываем високосные года, и f(2) = 28.

Если вы хотите узнать полученный мною результат, пролистните до конца этой страницы. То, что будет описано далее — это вывод искомой формулы.

Чем мы будем пользоваться

Кроме сложения, вычитания и умножения, я воспользуюсь двумя операциями: целочисленным делением и операцией взятия остатка. Напомню, что это такое:

  • Целочисленное деление, или «деление с округлением вниз». У меня оно будет представлено, как обычное деление: a / b — имея в виду ⌊a / b⌋. Например, 5 / 3 = 1.
  • Взятие остатка по модулю. Обозначу традиционно деление с остатком: a % b = a — (a / b) * b. Например, 5 % 3 = 2.

Они имеют одинаковый приоритет и являются левоассоциативными.

Основы, или Правило со множеством исключений

Давайте попробуем найти такую закономерность, которая удовлетворила бы как можно большему количеству значений аргумента. Обычно количество дней в месяце колеблется между 30 и 31. При этом, можно заметить зависимость этого числа от четности месяца — значит, воспользуемся операцией взятия остатка по модулю 2. Кажется, это должно быть нечто, вроде:

f₁(x) = 30 + x%2

table2
Неплохой старт! Не обращая внимания на февраль, для которого явно придется пойти на какие-то уловки, порадуемся тому, что мы смогли подогнать функцию под первую половину года. А далее, начиная с августа, четность надо сменить на противоположную. Сделать это можно, заменив x%2 в первом варианте формулы на (x+1)%2:

f₂(x) = 30 + (x + 1) % 2

table3
Как и ожидалось, теперь первая половина года уже вне области правильных значений, зато месяцы с августа по декабрь дали то, что надо. Найдем способ объединить две эти части.

Маска

Нам нужно, чтобы +1 в делимом «активировалось» только при достижении аргументом значений, больших 8, т.е. нам необходимо применить некоторую маску. При этом значения аргумента не могут превосходить 12. Значит, нам идеально подойдет целочисленное деление аргумента на 8:

table4
Ровно как нам и нужно. Воспользуемся этим выводом:

f₃(x) = 30 + (x + x / 8) % 2

table5
Уху! Все правильно, кроме февраля. Как неожиданно.

Февраль

Во всех месяцах 30 или 31 день, в феврале же — 28 (напомню, мы не рассматриваем високосные года).

Историческая справка: в романском календаре февраль был последним месяцем года — согласитесь, добавление одного дня в високосные годы в конец календаря интуитивно понятнее. Однако мы пользуемся григорианским календарем, в котором самый короткий месяц перенесен ближе к началу по воле одного из мудрых правителей.

В самой последней версии нашей формулы февралю достались целых 30 дней. А потому нам нужно отсечь у него пару дней. Естественно, от этого пострадают и еще какие-то месяцы: или слева от февраля, или справа от него в нашем списке — однако, справа месяцев гораздо меньше, поэтому нам придется пожертвовать именно январем, затем подправив формулу и для него. Отсечь дни для первого и второго месяцев можно с помощью выражения 2%x:

table6

Тогда наша формула принимает уже следующий вид:

f₄(x) = 28 + (x + x / 8) % 2 + 2 % x

table7

Остался последний шаг — подлатать январь. Это сделать не так сложно: просто добавим 2 дня только к нему, т.е. к такому месяцу, чей номер меньше либо равен единице. Как вам идея использовать для этой цели 1/x? Проверяем:

f₅(x) = 28 + (x + x / 8) % 2 + 2 % x + 1 / x * 2

table8
Бинго! 12 из 12!

Заключение

Итак, мы вывели искомую формулу, вот она, записанная на языке JavaScript:

function f(x) { return 28 + (x + Math.floor(x/8)) % 2 + 2 % x + 2 * Math.floor(1/x); }

Спросите меня, сколько дней в сентябре? Я скажу вам: f(9).

Перевод статьи «A Formula for the Number of Days in Each Month»