求解析《Tiny C Projects》中的月相计算函数运行逻辑
这段月相计算代码的原理拆解
这段代码是为嵌入式/低性能场景设计的极简近似朔望月相位计算实现,所有计算逻辑都用了刻意简化的经验近似值,放弃精度换计算效率,所以和专业天文算法对比有1-2天误差是预期结果。
分步逻辑说明
1. 计算当前日期的年累计日数(日积)
if (month == 2) { day += 31; } else if (month > 2) { day += 59 + (month - 3) * 30.6 + 0.5; }
这段逻辑是计算从当年1月1日到当前日期的总天数:
- 1月直接返回当月天数
- 2月累加1月的31天总天数
- 3月及以后先累加1、2月的总天数59(默认按平年2月28天计算,没有做闰年判断是误差来源之一),再用每月平均30.6天的近似值计算后续月份的天数,加0.5是为了做四舍五入取整,避免浮点转整型时截断误差。
2. 计算默冬章位置(黄金数)
int g = year % 19;
这里用了阴历的19年默冬周期规则:19个公历年的长度和235个朔望月的长度几乎相等,所以用年份模19得到该年在默冬周期中的位置,用来估算当年的朔日偏移。
原始版本里(year - 1900) % 19和你简化的year % 19完全等价,因为1900刚好是19的整数倍,减去的数值不影响模19的结果,所以确实是冗余操作。
3. 计算朔日经验偏移量
int e = (11 * g + 29) % 30; if (e == 25 || e == 24) { ++e; }
这段是经验公式:通过默冬章位置估算当年首个朔日的偏移天数,模30是取近似朔望月的整数长度。后面的e值修正属于人为凑出来的误差校准,用来抵消前面一系列近似计算带来的固定偏差。
4. 计算月相索引
int phase = ((((e + day) * 6 + 5) % 177) / 22 & 7);
这段是把累计天数转换成8个标准月相的索引:
- 先把
e+日数乘以6,把单位从「天」转换成「1/6天」,目的是全程用整型计算避免浮点运算 - 模177对应一个近似朔望月的长度:29.5天 = 177 * 1/6天
- 除以22对应每个月相的长度:22 * 1/6天 ≈ 3.67天,刚好对应朔望月8等分的单段长度(29.5/8≈3.69天,近似值)
- 最后
&7是取低3位,确保返回值落在0-7的区间,刚好对应8个月相的描述字符串索引。
误差来源说明
- 日积计算完全没有考虑闰年,每到闰年3月以后就会少算1天
- 所有参数都是近似值:朔望月取29.5天而非精准的29.53059天,月均长度取30.6天都是为了简化计算做的妥协
- 经验修正规则非常简陋,只能抵消部分固定误差,无法适配跨数十年的日期计算
内容的提问来源于stack exchange,提问作者Rowan
相关产品推荐
相关产品推荐

