前言

实验课上,老师布置了这样一个任务

万年历 :由用户输入一个日期(包含y年m月d日信息),请编写一个万年历程序能计算出这一天是星期几?

计算天数由三部分构成:

(1)y-1年包含的天数。 days1=(y-1)* 365+闰年的数量;因为闰年有366天。

(2)m-1月包含的天数。 days2=1月天数+2月天数+…+m-1月天数。

(3)days3=d天。

因此, 总的天数=days1+day2+days3,然后将求得的总天数进行模7运算,即得以7为周期的余数。得到的值与星期几之间有如下对应关系: 0-星期日;1-星期一;2-星期二;3-星期三;4-星期四;5-星期五;6-星期六。

(2)和(3)很好解决,只需要按照给定的公式写一个函数再调用就好了

可以我在计算(1)的时候遇到了一些问题

理论上,我们只需要这样写

    // (1) 计算days1
    long long days1 = (y - 1) * 365 
                    + (y - 1) / 4 
                    - (y - 1) / 100 
                    + (y - 1) / 400;

之后把days全部加起来再使用%运算符就可以算出7的余数,也就对应实际的星期了

    // (4) 总天数
    long long days = days1 + days2 + days3;
    // (5) 求余数
    int weekday = days % 7;

但是之后老师又提出了一个问题

如果days值很大,以至于系统的long也无法处理,该怎么办?

虽然C语言中的long long最大值为9223372036854775807,也就意味着除非我们输入年份大于25269512429739111才会出现这种情况,但是问题都提出来了你不能不解决对吧

当然,我们要解决这个问题也不难,我们不用真的求出 days,只要对每个年份求模即可,所以我将其封装出一个函数

long long calculateDays1(int y) {
    long long days1 = ((y - 1) * 365LL % 7
                       + ((y - 1) / 4) % 7
                       - ((y - 1) / 100) % 7
                       + ((y - 1) / 400) % 7
                       ) % 7;
    return days1;
}

真的是非常简单的解决了这个问题呢,直到我在调试的时候不经意输入了2017这个年份,这个时候,days1变成了days1 = -1

这是什么鬼?

取模和取余的区别

搜索资料得知,C语言中的%运算事实上是取余运算而不是取模运算,而我之前一直以为这两种算法没有任何区别

那么什么是取模运算呢?

我们先从小学除法的定义出发:

两个整数相除:
被除数 = 除数 × 商 + 余数

举个例子:

被除数 = 除数 × 商 + 余数

17 = 5 × 3 + 2

这里的 2 就是 余数

那么负数呢?

假设我们计算-17 ÷ 5

这时候问题就来了:
商可以取不同的整数,余数也会变。

在数学上有两种常见的处理方式

名称

商的取法

余数符号

举例 -17 ÷ 5

取余(remainder)

商向 0 取整

余数与被除数同号

商 = -3,余 = -2(因为 -17 = 5×(-3) + (-2))

取模(modulus)

商向下取整(floor)

余数恒为非负

商 = -4,余 = 3(因为 -17 = 5×(-4) + 3)

取模操作也是整数除法后得到的余数,但取模的结果总是非负的。在数学中,取模通常表示为 a mod b,其中 a 是被除数,b 是除数。取模的结果与除数的符号相同

这就意味着

  • 取模的结果总是非负的。

  • 在处理负数时,取模的结果可能是除数的绝对值减去取余的结果。

在我写的这个程序中,因为星期是循环的:

  • 0 → 星期日

  • 1 → 星期一

  • 6 → 星期六

所以我们希望“结果”始终落在 0 ~ 6 范围内,
这就要求必须是数学意义上的模(modulus)

那怎么办?

很简单,只需要在得出的结果加上一个周期(对于这里是一周的天数7),这样无论 x 是正数还是负数,结果都保证落在 0~6

对应到原来的程序中就是

    long long days1 = ((y - 1) * 365LL % 7 
                     + ((y - 1) / 4) % 7   //每 4 年的闰年多一天
                     - ((y - 1) / 100) % 7 //每 100 年去掉一个闰年的天数
                     + ((y - 1) / 400) % 7 //每 400 年加回闰年
                     + 7) % 7;             //强制先加一个正的 7,保证结果非负,因为 C++ 的%运算是取余而不是取模

又是一个引入新功能造出bug的实例

其他的编程语言

% 是取余运算(结果符号与被除数相同)的语言:

  • C / C++

  • Java

  • JavaScript

  • Ruby

  • Go

  • Swift

  • Perl

  • PHP

  • Rust

……

% 是取模运算(结果符号与除数相同)的语言:

  • Python