闰年判断必须严格遵循格里高利历规则:(year % 4 == 0) && (year % 100 != 0 || year % 400 == 0),否则1900年误判为闰年、2000年误判为平年;需注意整型溢出、负数年份归一化及std::chrono验证的局限性。

闰年判断的数学定义必须严格按格里高利历规则
直接套用“能被4整除就是闰年”会出错——这是儒略历规则,现代C++程序默认按格里高利历(公历)计算,必须同时满足:能被4整除 且(不能被100整除 或 能被400整除)。漏掉括号逻辑或混淆“且/或”优先级,is_leap_year函数就会在1900、2100这类世纪年份上返回错误结果。
常见错误现象:is_leap_year(1900) 返回 true(实际应为 false);is_leap_year(2000) 返回 false(实际应为 true)。
- 务必写成:
(year % 4 == 0) && (year % 100 != 0 || year % 400 == 0) - 不要简化为:
year % 4 == 0 && year % 100 || year % 400 == 0(运算符优先级导致逻辑错乱) - 注意
year类型:若用short或int16_t,2100年可能溢出,建议统一用int或int32_t
用 std::chrono 验证闰年需绕过日期构造陷阱
std::chrono::year_month_day 本身不提供闰年判断接口,但可以反向验证:尝试构造当年2月29日的日期,看是否合法。不过直接传入非法日期(如 2023y/2m/29d)会导致 std::chrono::year_month_day 构造失败,且 C++20 标准未强制要求抛异常——不同编译器行为不一致(GCC 可能截断,MSVC 可能静默修正),不能依赖。
安全做法是先用数学逻辑判断,再用 std::chrono 辅助验证:
立即学习“C++免费学习笔记(深入)”;
bool is_leap_by_chrono(int year) {
std::chrono::year y{year};
auto feb29 = std::chrono::year_month_day{y / std::chrono::month{2} / std::chrono::day{29}};
return feb29.ok(); // 仅当 year 是闰年时返回 true
}
-
feb29.ok()是唯一可靠检查方式,feb29.day()可能返回day{1}(被自动归一化),不能用来判断 - 该方法性能远低于纯数学判断(涉及内部日历转换),仅适合单元测试交叉验证
- C++20 起支持,C++17 及更早版本无
std::chrono::year_month_day
处理负数年份(公元前)时需修正模运算
格里高利历回溯到公元前(即年份 ≤ 0)时,“年份0”不存在,公元1年之前是公元前1年,对应数值为 year = 0。标准模运算 % 在负数上产生负余数(如 -1 % 4 == -1),直接套用原公式会错判。
正确做法是先将年份映射到正数域再计算:
int normalized_year = (year <= 0) ? year + 1 : year; bool is_leap_bc = (normalized_year % 4 == 0) && (normalized_year % 100 != 0 || normalized_year % 400 == 0);
- 公元前1年 =
year = 0,公元前4年 =year = -3,依此类推 - 关键点:
year 时,用 <code>year + 1对齐编号(因无“公元0年”) - 若业务不涉及公元前,可忽略此分支,但库函数若接受
int参数,必须考虑边界
性能敏感场景下避免分支预测失败
高频调用(如天文计算、金融日历批量生成)中,if 分支可能引发 CPU 分支预测失败。可用位运算消除条件跳转,但需确保可读性不严重受损:
constexpr bool is_leap_branchless(int year) {
int a = year % 4;
int b = year % 100;
int c = year % 400;
return (a == 0) & ((b != 0) | (c == 0));
}
- 用
&和|替代&&和||,强制无短路求值,生成更紧凑的汇编 - 仅对
constexpr场景有效;运行时变量仍建议用原版布尔表达式,现代编译器优化足够好 - 别为微小性能牺牲可维护性——除非
perf确认该函数是热点


















