前言
前几日做到一个机试题,给出一个日期,让你输出那天是星期几,这种题无疑两种思路:一是从今天(前提是知道今天日期及周几)开始推算,计算今天与目标日期差的天数再取模运算,考虑到还要考虑闰年什么的,立即推->用下一种方法;直接利用一个什么公式来计算(问题是这样算起来容易,但公式不好记啊啊啊)。
蔡勒公式
w = ( C / 4 − 2 C + Y + Y / 4 + 13 ( M + 1 ) / 5 + D − 1 ) % 7 w =(C/4-2C+Y+Y/4+13(M+1)/5+D-1)\%7 w=(C/4−2C+Y+Y/4+13(M+1)/5+D−1)%7
其中 / / / 为除法运算,结果取整(即商), % \% % 为取模运算; C C C 表示已过世纪数,即本世纪减一,; Y Y Y 表示 世纪内的年份,即一般是年份的后两位数字; M M M 代表月份,如果 M < 2 M<2 M<2,则加12,并且年份减1(即将某年的1月、2月,看作上一年的13月、14月来带入计算,并且这一步要先于计算 C C C); D D D 表示日。
w w w 为 0 时表示星期日,不为0时, w w w是几便是星期几。
话不多说,上代码(C++),代码输入输出格式是按照机试格式要求来的
input:
2
2020 4 8
2000 2 5
output:
3
6
#include <iostream>
#include <cmath>
using namespace std;
int cen(int y);
int main(void)
{
int n;
int y, m, d;
while (cin >> n)
{
for (int i = 0; i < n; i++)
{
cin >> y >> m >> d;
if (m < 3)
{
y--;
m += 12;
}
int y1 = y - cen(y)*100;
int w = (y1 + y1/4 + cen(y)/4 - 2*cen(y) + 13*(m+1)/5 + d - 1) % 7;
if (0 == w)
w = 7;
cout << w << endl;
}
}
return 0;
}
int cen(int y) // 已过世纪数
{
return y/100;
}
这样好像测试的两个例子都正确,但是真的有这么简单?╮(╯_╰)╭
输入如下测例试试
2006 4 4
2100 12 2
结果
-5
-3
所以问题出在哪呢,enmmm,结果是通过蔡勒公式直接计算出来的,结果为负就说明蔡勒公式的被除数部分 ( C / 4 − 2 C + Y + Y / 4 + 13 ( M + 1 ) / 5 + D − 1 ) (C/4-2C+Y+Y/4+13(M+1)/5+D-1) (C/4−2C+Y+Y/4+13(M+1)/5+D−1) 可能为负,然后就涉及到负数取模的问题,,关于负数取模,百度一下就会发现不同的语言实现方法并不相同!
- C/C++/Java 采用了 truncate 除法。
-7 % 3 = -7 - (-7) / 3 * 3= -7 - (-2) * 3 = -7 + 6 = -1 - python 则采用floor 除法
-7 % 3 = -7 - (-7) / 3 * 3= -7 - (-3) * 3 = -7 + 9 = 2
可以看出两种取模方法根本在于除法是采用向上取整还是向下取整。很明显蔡勒公式中的负数取模方式采用的是向下取整!
这与 C++ 的负数取模方式不同,所以对于负数取模,我们需要进一步处理;
代码更改如下:
#include <iostream>
#include <cmath>
using namespace std;
int cen(int y); // 已过世纪数
int main(void)
{
int n;
int y, m, d;
while (cin >> n)
{
for (int i = 0; i < n; i++)
{
cin >> y >> m >> d;
if (m < 3)
{
y--;
m += 12;
}
int y1 = y - cen(y)*100;// or y1 = y % 100
int w = (y1 + y1/4 + cen(y)/4 - 2*cen(y) + 13*(m+1)/5 + d - 1);
if (w < 0) // 负数取模
w = 7 - (-w) % 7;
else
w = w % 7;
if (0 == w)
w = 7;
cout << w << endl;
}
}
return 0;
}
int cen(int y) // 已过世纪数
{
return y/100;
}
拓展
万年历(公历)
/* 输入日期(年月日),打印本月日历 **/
#include <iostream>
#include <iomanip>
using namespace std;
int week(const int y, const int m, const int d); // 计算一号是周几
void printMon(const int y, const int m, const int d); // 打印月历
bool isLeap(const int y); // 判断闰年
int main(void)
{
int y, m, d;
cin >> y >> m >> d;
printMon(y, m, d);
return 0;
}
int week(const int y, const int m, const int d) // 利用蔡勒公式求星期几
{
int y1 = y;
int m1 = m;
int d1 = d;
if (m1 < 3)
{
m1 += 12;
y1--;
}
int cen = y1 / 100;
int y2 = y1 % 100;
int w = (y2 + y2/4 + cen/4 - 2*cen + 13*(m1+1)/5 + d1 - 1);
if (w < 0) // 负数取模
w = 7 - (-w) % 7;
else
w = w % 7;
if (0 == w)
w = 7;
return w;
}
void printMon(const int y, const int m, const int d) // 打印月历
{
int mon[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day = mon[m-1];
int w = week(y, m, d);
if (isLeap(y) && 2==m)
day++;
cout << "Sun " << "Mon " << "Tues " << "Wed " << "Thur " << "Fri " << "Sat\n";
if (7 == week(y, m, 1))
{
for (int i = 1; i <= day; i++)
{
cout << setiosflags(ios::right) << setw(3) << i << " ";
if (i % 7 == 0) // 周六后换行
cout << endl;
}
}
else
{
for (int i = 0; i < week(y, m, 1); i++) // 控制一号前的距离
cout << " ";
for (int i = 1; i <= day; i++)
{
cout << setiosflags(ios::right) << setw(3) << i << " ";
if ((i+week(y, m, 1)) % 7 == 0) // 周六后换行
cout << endl;
}
}
cout << endl;
}
bool isLeap(const int y)
{
if (y%400==0 || (y%100!=0 && y%4==0))
return true;
else
return false;
}
效果: