喜欢 发表于 2015-2-28 06:51:37

【半原创】计算某一天是星期几

本帖最后由 喜欢 于 2015-2-27 20:31 编辑

前天(25号)去玩了一个题:用公式计算某一天是星期几,写在日志里。看了我的日志,
有人引介我去看他们讨论过的做这件事的帖子;
有人说费那个劲干嘛?无论是Excel还是其它编程语言,都有现成的功能可以直接调用;
不过更有人帮我解答了我的疑问!——这就是神一般的牛同学—— @holycow !{:soso_e178:}

今天得空,我把这个题重新审视了一遍,把神牛的解释也就是原公式读懂了,并学习了一下前天尚不大会用的Excel的数组功能,改进了我的公式。现在我可以用三个不同的方式实现这个计算功能,算出历史上、现在和将来任何一天是星期几!

问我为什么要算这个?就好比问我为什么要玩、为什么要上网、为什么要出游、为什么要做开心的事一样,我喜欢!{:soso_e113:} 在“软件人家”那个帖子中,大家纷纷表示做过这个题,用各种语言。那就是了。当年我也做过这个题。编程序么,这是一个很不错的题,可以用到各种语句、功能……最后要打印出来“月历”。我还给自己加码,专门用循环语句实现打印。有同学不解,问我为什么要舍简求繁?操练呀,做这题本就为了操练各种语句和功能嘛,简单的途经练不到循环语句呀。目的又不是打印月历,谁还缺挂历不成?哦,忘了说,当时我用的是汇编语言。{:soso_e106:}

扯远了,拉回来。我不管它是儒略历还是格里高利历,反正就是现在我们用的这个公历,按照这个公式往前往后都能算,算出任何一天是星期几。(如果说公元1582年10月4日或10月15日之前的日子照这算法有所不同,那不是这公式的错,那是历史的错!需要历史学家去修正,咱不管那一段儿~{:soso_e112:} )

这公式是网上找来的,如下:

公式:
W=+r(y/7)-2r(c/4)+m'+d
此处得到的W要对7取余,0表示星期天;
其中y为年份的后两位;c为年份的前两位;d为日期;[] 为取整的意思;r为取余的意思(在公式中分别对7、对4取余);
m’为“月份的修正数”,即对应每个月都有一个修正数,从1月到12月依次为:
6,2,2,5,0,3,5,1,4,6,2,4

我前天只用Excel实现了这个算法,但对公式还不是很理解。于是神牛同学帮我解析了这个公式:

我想出来了。

每年正常365天,除7余1,所以造成的星期位移是1;闰年是2.

每400年的位移是400+97闰 = 497 mod 7 = 0,正好一循环。而每一百年的位移是100 + 24 = 124 mod 7 = 5. 所以r(c/4)那项之前的因子应该是5,mod 7运算+5 = -2, 故取 -2r(c/4)

r(y/7) + 是最近100年的位移。

这个位移实际已经算到ccyy+1年,所以一月一号要倒回来365天,应该-1,结果他取修正+6. 其余各个月的修正迎刃而解。

我读懂了神牛同学的分析,并用自己的语言复述它如下,请神牛同学认可:

● 首先,公元1年1月1日是星期一,这个是基础;
● 其次,所有7的倍数天数就不必算了,因为星期一到星期天是个循环,周而复始;
● 我们只要算自“公元1年1月1日”以来所有7的倍数之外的那些天加起来,对7取余,就知道是星期几;
● 每年正常365天,除7余1,所以要算的就是每年1天;闰年再加1天;
● 每400年的位移就是400天+97(闰)天 = 497天;497 mod 7 = 0,正好一循环。所以不用算这部分;
● 不足400年的百年部分c要算:每一百年的位移是100天+24(闰)天=124天,124 mod 7 = 5。有一个百年就是一个5,r(c/4)就是1、2或3个百年(逢4为零,不必算)。那项之前的因子应该是5,mod 7运算+5 = -2, 故取 -2r(c/4);
● 然后算不足百年的年数y,只取mod7即可:r(y/7);
● 再算此间闰年有过多少个:;
● 再加上此前经过的各个整月累计余下了多少个少于7的日子,即m’;
● 再加上本月至此的天数d;
● 以上的总和对7取余,得几就是星期几,0为星期天。

● 最后还有一点要单说,就是那m’是如何确定的。我要跟神牛同学握握手——因为你也没放过这个细节(当然不能放过^^)。
我算出来的m’分明是:
0,3,3,6,1,4,6,2,5,0,3,5
但公式中给出的却是:
6,2,2,5,0,3,5,1,4,6,2,4
整差1
这是因为公式中使用了y——今年的年份,本来应该用(y-1)才对。于是公式就在m’处做了修正。

● 最后一点:如果将来“逢百闰逢4百不闰”又有误差了,比如逢4千又闰了,那是将来的事,此处不能预估之。

好了。现在我已算出一些日子的结果,列出如下(为对齐起见,适当加了前置的0):

01        6        0       C        Y       M       D       Day    Day    Day
02        2        3        20        15        02        25        3        3        3
03        2        3        20        15        02        26        4        4        4
04        5        6        20        15        02        27        5        5        5
05        0        1        20        15        02        28        6        6        6
06        3        4        20        15        03        01        0        0        0
07        5        6        20        15        03        02        1        1        1
08        1        2        20        15        03        03        2        2        2
09        4        5        19        49        10        01        6        6        6
10        6        0        19        11        10        10        2        2        2
11        2        3        19        45        08        15        3        3        3
12        4        5        20        11        07        01        5        5        5

后面三列我用了不同的公式,达到了相同的结果:

1,前天对数组算法不熟悉,索性试试嵌套的IF语句我能否写得明白。经过一番周折,写明白了:
=MOD(INT(E15/4)+MOD(E15,7)-2*MOD(D15,4)+G15+IF(F15<>5,IF(F15<>8,IF(AND(AND(F15<>2,F15<>3),F15<>11),IF(F15<>6,IF(AND(F15<>9,F15<>12),IF(AND(F15<>4,F15<>7),IF(AND(F15<>1,F15<>10),"wrong",6),5),4),3),2),1),0),7)
很不简单哪!那么多括号嵌来嵌去的,不晕才怪!{:soso_e125:}

2,今天学习了一下,用数组功能实现原公式,很简单明了(即,用到上图表中的前面两列数据):
=MOD(INT(E15/4)+MOD(E15,7)-2*MOD(D15,4)+G15+VLOOKUP(F15,$A$14: $B$25,2,FALSE),7)

3,既然原公式并不是很直观,那我索性就把它改一下,用更直观的方式(用到上图表中的第一和第三列数据):
=MOD(INT((E15-1)/4)+MOD((E15-1),7)+5*MOD(D15,4)+G15+VLOOKUP(F15,$A$14: $C$25,3,FALSE),7)

至此,圆满完成了一次……玩乐!{:soso_e128:}

神牛同学请跟帖,俺要给你加分!谢谢你~~{:soso_e163:} {:soso_e179:} {:soso_e182:} {:soso_e166:} {:soso_e178:}

holycow 发表于 2015-2-28 07:11:58

我后来又想到一个问题:

维特啊塞根德,为嘛修正值可以不随平闰年的变化而变化?

{:199:}

雨楼 发表于 2015-2-28 08:59:59

等夏天咱去草地里数蚂蚁吧?比这个好玩多了。要不弹玻璃球?:loveliness:

雨楼 发表于 2015-2-28 09:01:22

holycow 发表于 2015-2-27 18:11
我后来又想到一个问题:

学霸摔完肥鸡又要优化算法鸟。

喜欢 发表于 2015-2-28 09:19:48

本帖最后由 喜欢 于 2015-2-27 20:25 编辑

holycow 发表于 2015-2-27 18:11
我后来又想到一个问题:

哎,你提到的这个问题,它确实是一个问题!
当初我做那个m’的时候还想过,想着弄好一般的年份再考虑闰年,然后,就,忘,了!{:soso_e117:}

刚才我去验算,艾玛,用它的公式,凡是闰年的前两个月都算不对呀!!{:soso_e122:}
用我的公式(以y-1代替y的算法)呢?咳咳,闰年的后十个月都算不对!{:soso_e127:}
架不住我可以改哈,他的公式谁给他改呢?{:soso_e112:}

把我的公式从这样:
=MOD(INT((E15-1)/4)+MOD((E15-1),7)+5*MOD(D15,4)+G15+VLOOKUP(F15,$A$14 : $C$25,3,FALSE),7)
改成:
=MOD(INT((E15-1)/4)+MOD((E15-1),7)+5*MOD(D15,4)+G15+VLOOKUP(F15,$A$14 : $C$25,3,FALSE)+IF(F15>=3,IF(MOD(E15,4)<>0,0,1)),7)
就能算对了!

修正内容:
若y是闰年,则从3月起的后十个月,都要再+1


运算结果:

                               C       Y       M       D        His   His   Mine    Real
01        6        0        20        15        02        27        5        5        5        5
02        2        3        19        49        10        01        6        6        6        6
03        2        3        19        11        10        10        2        2        2        2
04        5        6        19        45        08        15        3        3        3        3
05        0        1        19        72        01        01        0        0        6        6
06        3        4        19        72        02        28        2        2        1        1
07        5        6        19        72        02        29        3        3        2        2
08        1        2        19        72        03        01        3        3        3        3
09        4        5        19        72        05        01        1        1        1        1
10        6        0        20        12        02        28        3        3        2        2
11        2        3        20        12        02        29        4        4        3        3
12        4        5        20        12        03        01        4        4        4        4

其中:
前3列是数据
中间4列是年月日
接下来两列是不同方式实现的网上那个公式,我叫它做His(他的)——在闰年的前两个月,都会算错!
倒数第2列是我(修正过)的公式算出来的——正确!
最后一列是查万年历查出来的实际“星期几”,作为正确值,做标尺用。

耶,这次真的圆满了!{:soso_e121:}

神牛你再来,我还用分数谢谢你~{:soso_e163:} {:soso_e179:} {:soso_e182:}

holycow 发表于 2015-2-28 09:21:32

握手,这个也是我最后得出来的公式:handshake
页: [1]
查看完整版本: 【半原创】计算某一天是星期几