【看到哪里写到哪里】算闰年的(year 3) == 0
【??BUG??】在MYSQL源码里面有一段,算每年的天数。其中用到了两个很有意思的
1)(year & 3) == 0
2)(year % 400 == 0 && year),为什么要 &&year呢?
>>>>>mysql-server/mysys/my_time.cc 的128~137行/**Calc days in one year.@note Works with both two and four digit years.@return number of days in that year
*/
uint calc_days_in_year(uint year) {return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)) ? 366: 365);
}
一般的算闰年的写法很简单
一、闰年的基本定义与规则
闰年是为了弥补因公历纪年中 “平年”(365 天)与地球实际公转周期(约 365.2422 天)的时间差而设立的。具体判断规则如下:
1. 普通闰年判断规则
满足以下条件之一的年份为闰年:
- 能被 4 整除且不能被 100 整除。
例:2004 年(2004÷4=501,2004÷100=20.04)是闰年;
1900 年(1900÷4=475,但 1900÷100=19)不是闰年。
2. 世纪闰年判断规则
- 能被 400 整除的整百年份为闰年。
例:2000 年(2000÷400=5)是闰年;
1800 年(1800÷400=4.5)不是闰年。
二、规则拆解与逻辑说明
可用逻辑表达式表示为:
(年份 % 4 == 0 且 年份 % 100 != 0) 或 年份 % 400 == 0
-
为什么除以 4?
地球公转约 365.2422 天,每 4 年积累约 0.9688 天,接近 1 天,故每 4 年设 1 个闰年(加 1 天)。 -
为什么除以 100 的例外?
每 4 年加 1 天会导致误差:400 年累计多算约 3 天。因此规定 “能被 100 整除但不能被 400 整除的年份” 不算闰年(如 1900 年、2100 年),以修正误差。 -
为什么除以 400 的例外?
进一步修正误差:每 400 年中,原本应排除 3 个整百年(如 1700、1800、1900),但保留能被 400 整除的年份(如 2000 年),使 400 年内闰年数量为 97 个,更接近实际公转周期。
C语言写个简单的函数,首先要判断不能<=0,再做判断。
int isLeapYear(int year) {if (year <= 0) return 0;return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
=====================================================================
但是前面那段mysql的程序是什么意思呢?
在闰年判断函数中,(year & 3) == 0
是一种高效的位运算技巧,用于替代 year % 4 == 0
判断一个年份是否能被 4 整除。
一、位运算原理:为什么 (year & 3) == 0
等价于 year % 4 == 0
?
1. 4 的倍数的二进制特征
一个整数能被 4 整除,当且仅当其二进制表示的最后两位是 0。
例如:
- 4 的二进制:
100
(最后两位是 00) - 8 的二进制:
1000
(最后两位是 00) - 12 的二进制:
1100
(最后两位是 00) - 15 的二进制:
1111
(最后两位是 11,不能被 4 整除)
2. 位与运算(&)的作用
3
的二进制是 0011
(假设是 32 位整数,则为 000...0011
)。当一个数 year
与 3
进行位与运算时:
year & 3 等价于:只保留 year 的二进制表示的最后两位,其余位清零
因此:
- 若
year
能被 4 整除(最后两位是 00),则year & 3
的结果为00
(十进制 0)。 - 若
year
不能被 4 整除(最后两位不为 00),则year & 3
的结果为01
、10
或11
(十进制 1、2 或 3)。
二、为什么用位运算替代取模(%)?
1. 性能优势
- 取模运算(%):在 CPU 中通常需要除法指令,涉及多次减法或移位操作,计算开销较大。
- 位与运算(&):直接对二进制位进行操作,仅需一次逻辑运算,速度远快于取模。
2. 编译器优化的局限性
虽然现代编译器可能会将 year % 4
优化为 year & 3
(当除数是 2 的幂时),但:
- 不同编译器的优化能力不同,手动使用位运算能确保在所有环境下都高效。
- 代码可读性不受影响,因为
(year & 3) == 0
的含义很直观(“判断是否为 4 的倍数”)。
三、原代码的闰年判断逻辑拆解
原代码为:
return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)) ? 366 : 365);
1. 逻辑表达式的结构
整体逻辑等价于:
if ((year & 3) == 0 && (year % 100 != 0 || year % 400 == 0)) {return 366; // 闰年
} else {return 365; // 平年
}
2. 核心判断步骤
- 第一步:判断是否为 4 的倍数:
(year & 3) == 0
替代year % 4 == 0
,高效检查年份是否能被 4 整除。 - 第二步:判断是否为 100 的倍数:
year % 100 != 0
(原代码中year % 100
为真等价于year % 100 != 0
)。 - 第三步:判断是否为 400 的倍数:
year % 400 == 0
。
3. 特殊情况处理:&& year
的作用
原代码中 (year % 400 == 0 && year)
中的 && year
是冗余的,因为:
- 年份
year
是uint
(无符号整数),其值 ≥ 0。 - 当
year = 0
时,year % 400 == 0
为真,但&& year
为假,结果为平年(符合实际,因为公元 0 年不存在)。 - 当
year > 0
时,&& year
恒为真,不影响判断。