閏年的最佳效率演算法
• Leap Year, Algorithm
所謂閏年,維基百科做了如是介紹:
閏年是比普通年分多出一段時間的年分,在各種曆法中都有出現,目的是為了彌補人為規定的紀年與地球公轉產生的差異。
目前使用的格里曆閏年規則如下:
- 西元年分除以400可整除,為閏年。
- 西元年分除以4可整除但除以100不可整除,為閏年。
- 西元年分除以4不可整除,為平年。
- 西元年分除以100可整除但除以400不可整除,為平年。
在C,C++,C#,Java,以及許多類C編程語言的入門書籍裏,舉凡講到運算子一節,一般都會提及閏年的演算法;且不出意外、一般皆為:
這演算法簡單明瞭、但在效率上卻顯得差強人意;因而、在stackoverflow上有討論,認為若以效率考量、則最佳演算法為:
那麼、這「最佳效率」演算法如何得來呢?以下便是推演過程。
短路求值
有以下兩點:
- 許多編程語言都有短路求值的策略
- 不能被4整除的數、亦不能被400整除
則演算法可以重寫為:
這樣、舉凡不能被4整除的年份皆為平年(格里曆閏年規則#3);不用再去運算(year % 400) == 0、大大提高了演算法的效率。
因式分解
因有等式100=4×25,則可被100整除等同於可被4和25整除。依據短路求值策略,在進行運算(year % 100) != 0時必有先決條件:運算式(year % 4) == 0為真,即年份可被4整除。
故而、演算法可以重寫為:
同理、因有400=25×16,演算法進一步可以重寫為:
位元與取代模除
執行模除運算需要除法。而除法運算在某些低端CPU上非常耗時。因而、最好避免不必要的模除運算。
特別地、當模數為2的指數冪時、模除運算可用位元與代替,即:x % 2^n == x & (2^n - 1)。
因而、演算法可以重寫為:
至此、推演告一段落;不過、以下兩點更加有趣哦~
15還是12
有以下三點:
- 運算式(year & 3) == 0檢查年份的[0..1]位元是否皆為0
- 運算式(year & 12) == 0檢查年份的[2..3]位元是否皆為0
- 運算式(year & 15) == 0檢查年份的[0..3]位元是否皆為0
因而、#3可由#1與#2協同完成;而依據短路求值策略,在進行運算(year & 15) == 0時必有先決條件:運算式(year & 3) == 0為真。
故15也可以12替代、演算法也可重寫為:
4000年問題
按照現行閏年規則,西元4000年應當為閏年;但到西元8000時,時日又會有所差錯,故有提議西元4000年為平年,並修改規則為:
- 西元年分除以4000可整除,為平年。
- 西元年分除以400可整除但除以4000不可整除,為閏年。
- 西元年分除以4可整除但除以100不可整除,為閏年。
- 西元年分除以4不可整除,為平年。
- 西元年分除以100可整除但除以400不可整除,為平年。
據此、又有人提出了「最終極」「最佳效率」演算法: