如何在 C 中以编程方式查找闰年
我用C写了一个程序来判断输入的年份是否是闰年。但不幸的是它运作不佳。它说一年是闰年,上一年不是闰年。
#include <stdio.h>
#include <conio.h>
int yearr(int year);
void main(void) {
int year;
printf("Enter a year:");
scanf("%d", &year);
if (!yearr(year)) {
printf("It is a leap year.");
} else {
printf("It is not a leap year");
}
getch();
}
int yearr(int year) {
if ((year % 4 == 0) && (year / 4 != 0))
return 1;
else
return 0;
}
阅读评论后,我将代码编辑为:
#include <stdio.h>
#include <conio.h>
int yearr(int year);
void main(void) {
int year;
printf("Enter a year:");
scanf("%d", &year);
if (!yearr(year)) {
printf("It is a leap year.");
} else {
printf("It is not a leap year");
}
getch();
}
int yearr(int year) {
if (year % 4 == 0) {
if (year % 400 == 0)
return 1;
if (year % 100 == 0)
return 0;
} else
return 0;
}
I wrote a program in C to find whether the entered year is a leap year or not. But unfortunately its not working well. It says a year is leap and the preceding year is not leap.
#include <stdio.h>
#include <conio.h>
int yearr(int year);
void main(void) {
int year;
printf("Enter a year:");
scanf("%d", &year);
if (!yearr(year)) {
printf("It is a leap year.");
} else {
printf("It is not a leap year");
}
getch();
}
int yearr(int year) {
if ((year % 4 == 0) && (year / 4 != 0))
return 1;
else
return 0;
}
After reading the comments I edited my code as:
#include <stdio.h>
#include <conio.h>
int yearr(int year);
void main(void) {
int year;
printf("Enter a year:");
scanf("%d", &year);
if (!yearr(year)) {
printf("It is a leap year.");
} else {
printf("It is not a leap year");
}
getch();
}
int yearr(int year) {
if (year % 4 == 0) {
if (year % 400 == 0)
return 1;
if (year % 100 == 0)
return 0;
} else
return 0;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
最有效的闰年测试:
此代码在 C、C++、C#、Java 和许多其他类似 C 的语言中有效。该代码使用单个 TRUE/FALSE 表达式,该表达式由三个单独的测试组成:
year & 3
年 % 25
年 & 15
下面是对该代码如何工作的完整讨论,但首先需要讨论维基百科的算法:
维基百科算法无效/不可靠
维基百科已经发布了一个伪代码算法(请参阅:维基百科:闰年 - 算法)一直受到不断的编辑、评论和破坏。
不要实现维基百科算法!
历史最悠久(且效率低下)的维基百科算法如下所示:
上述算法效率低下,因为它总是执行第 400 年和第 100 年的测试,甚至多年这很快就会无法通过“第四年测试”(模 4 测试)——75% 的概率都是如此!通过重新排序算法以首先执行第四年的测试,我们显着加快了速度。
“最高效”伪代码算法
我向维基百科提供了以下算法(不止一次):
这个“最高效”伪代码只是改变了测试的顺序,因此除以 4 需要首先进行测试,然后是不经常发生的测试。由于“年”在 75% 的情况下不会被四整除,因此在四分之三的情况下,算法仅在一次测试后结束。
“最有效”闰年测试的讨论
用位与代替模:
我已经用按位与运算替换了维基百科算法中的两个模运算。为什么以及如何?
执行模计算需要除法。在对 PC 进行编程时,人们通常不会三思而后行,但在对小型设备中嵌入的 8 位微控制器进行编程时,您可能会发现 CPU 本身无法执行除法功能。在此类 CPU 上,除法是一个艰巨的过程,涉及重复循环、移位和加/减操作,速度非常慢。这是非常需要避免的。
事实证明,可以使用按位与运算交替实现 2 的幂模(参见:维基百科:模运算 - 性能问题):
x % 2^n == x & (2^n - 1)
许多优化编译器会为您将此类模运算转换为按位与,但针对较小和不太流行的 CPU 的不太先进的编译器可能不会。按位与 (Bitwise-AND) 是每个 CPU 上的一条指令。
通过用
& 替换
和modulo 4
和modulo 400
测试3& 15
(见下文:“因式分解以减少数学计算”)我们可以确保最快的代码结果,而无需使用慢得多的除法运算。不存在等于 100 的 2 的幂。因此,我们被迫在第 100 年测试中继续使用模运算,但 100 被 25 取代(见下文)。
因式分解以简化数学:
除了使用按位与来代替模运算之外,您可能会注意到维基百科算法和优化表达式之间的另外两个争议:
modulo 100
是替换为modulo 25
modulo 400
替换为& 15
第 100 年测试使用
模 25
而不是模 100
。我们可以这样做,因为 100 因式分解为 2 x 2 x 5 x 5。因为第四年的测试已经检查了 4 的因数,我们可以从 100 中消除该因数,留下 25。这种优化对于几乎每个 CPU 实现来说可能都是微不足道的(因为 100 和 25 都适合 8 位)。第 400 年测试使用
& 15
相当于模 16
。同样,我们可以这样做,因为 400 因式分解为 2 x 2 x 2 x 2 x 5 x 5。我们可以消除通过第 100 年测试检验的 25 因子,留下 16。我们不能进一步减少 16,因为 8 是因子为 200,因此去除更多因子将在第 200 年产生不需要的正值。400 年优化对于 8 位 CPU 来说非常重要,首先是因为它避免了除法;但更重要的是,因为值 400 是一个 9 位数字,在 8 位 CPU 中处理起来要困难得多。
短路逻辑 AND/OR 运算符:
最后也是最重要的优化是短路逻辑 AND ('&&') 和 OR ('||') 运算符(请参阅:维基百科:短路评估),在大多数类似 C 的语言中实现。短路运算符之所以如此命名,是因为如果左侧的表达式本身决定了运算的结果,那么它们就不会计算右侧的表达式。
例如:如果年份是 2003 年,则
year & 3 == 0
是错误的。逻辑 AND 右侧的测试不可能使结果为真,因此不会评估其他任何内容。通过首先执行第 4 年测试,只有第 4 年测试(简单的按位与)在四分之三 (75%) 的时间内进行评估。这极大地加快了程序执行速度,特别是因为它避免了 100 年测试所需的除法(模 25 运算)。
关于括号放置的注释
一位评论者认为我的代码中括号放错了位置,并建议围绕逻辑 AND 运算符(而不是围绕逻辑 OR)重新组合子表达式,如下所示:
以上是不正确的。逻辑 AND 运算符的优先级高于逻辑 OR,无论是否有新括号,都将首先进行计算。逻辑 AND 参数周围的括号无效。这可能会导致人们完全消除子分组:
但是,在上述两种情况下,逻辑 OR(第 400 年检验)的右侧几乎每次都会进行评估时间(即不能被 4 和 100 整除的年份)。因此,一个有用的优化被错误地消除了。
我原始代码中的括号实现了最优化的解决方案:
这里,逻辑 OR 只计算可被 4 整除的年份(由于短路 AND)。逻辑 OR 的右侧仅计算可被 4 和 100 整除的年份(由于短路 OR)。
C/C++ 程序员注意
C/C++ 程序员可能会觉得这个表达式更加优化:
这并没有更加优化!虽然显式的
== 0
和!= 0
测试被删除,但它们变成隐式的并且仍然执行。更糟糕的是,该代码在 C# 等强类型语言中不再有效,其中year & 3
计算结果为int
,但逻辑 AND (&&
)、OR (||
) 和 NOT (!
) 运算符需要bool
参数。Most efficient leap year test:
This code is valid in C, C++, C#, Java, and many other C-like languages. The code utilizes a single TRUE/FALSE expression that consists of three separate tests:
year & 3
year % 25
year & 15
A complete discussion of how this code works appears below, but first a discussion of Wikipedia's algorithm is called for:
Wikipedia algorithm is INEFFICIENT/UNRELIABLE
Wikipedia has published a pseudo-code algorithm (See: Wikipedia: Leap year - Algorithm) that has been subjected to constant editing, opinion, and vandalism.
DO NOT IMPLEMENT WIKIPEDIA ALGORITHM!
One of the longest-standing (and inefficient) Wikipedia algorithms appeared as follows:
The above algorithm is inefficient because it always performs the tests for the 400th year and 100th year even for years that would quickly fail the "4th year test" (the modulo 4 test)—which is 75% of the time! By re-ordering the algorithm to perform the 4th year test first we speed things up significantly.
"MOST-EFFICIENT" PSEUDO-CODE ALGORITHM
I provided the following algorithm to Wikipedia (more than once):
This "most-efficient" pseudo-code simply changes the order of tests so the division by 4 takes place first, followed by the less-frequently occurring tests. Because "year" does not divide by four 75-percent of the time, the algorithm ends after only one test in three out of four cases.
DISCUSSION OF "MOST-EFFICIENT" LEAP YEAR TEST
Bitwise-AND in place of modulo:
I have replaced two of the modulo operations in the Wikipedia algorithm with bitwise-AND operations. Why and how?
Performing a modulo calculation requires division. One doesn't often think twice about this when programming a PC, but when programming 8-bit microcontrollers embedded in small devices you may find that a divide function cannot be natively performed by the CPU. On such CPUs, division is an arduous process involving repetitive looping, bit shifting, and add/subtract operations that is very slow. It is very desirable to avoid.
It turns out that the modulo of powers of two can be alternately achieved using a bitwise-AND operation (see: Wikipedia: Modulo operation - Performance Issues):
x % 2^n == x & (2^n - 1)
Many optimizing compilers will convert such modulo operations to bitwise-AND for you, but less advanced compilers for smaller and less popular CPUs may not. Bitwise-AND is a single instruction on every CPU.
By replacing the
modulo 4
andmodulo 400
tests with& 3
and& 15
(see below: 'Factoring to reduce math') we can ensure that the fastest code results without using a much slower divide operation.There exists no power of two that equals 100. Thus, we are forced to continue to use the modulo operation for the 100th year test, however 100 is replaced by 25 (see below).
Factoring to simplify the math:
In addition to using bitwise-AND to replace modulo operations, you may note two additional disputes between the Wikipedia algorithm and the optimized expression:
modulo 100
is replaced bymodulo 25
modulo 400
is replaced by& 15
The 100th year test utilizes
modulo 25
instead ofmodulo 100
. We can do this because 100 factors out to 2 x 2 x 5 x 5. Because the 4th year test already checks for factors of 4 we can eliminate that factor from 100, leaving 25. This optimization is probably insignificant to nearly every CPU implementation (as both 100 and 25 fit in 8-bits).The 400th year test utilizes
& 15
which is equivalent tomodulo 16
. Again, we can do this because 400 factors out to 2 x 2 x 2 x 2 x 5 x 5. We can eliminate the factor of 25 which is tested by the 100th year test, leaving 16. We cannot further reduce 16 because 8 is a factor of 200, so removing any more factors would produce a unwanted positive for a 200th year.The 400th year optimization is greatly important to 8-bit CPUs, first, because it avoids division; but, more important, because the value 400 is a 9-bit number which is much more difficult to deal with in an 8-bit CPU.
Short-circuit Logical AND/OR operators:
The final, and most important, optimization used are the short-circuit logical AND ('&&') and OR ('||') operators (see: Wikipedia: Short-circuit evaluation), which are implemented in most C-like languages. Short-circuit operators are so named because they do not bother to evaluate the expression on the right side if the expression on the left side, by itself, dictates the outcome of the operation.
For example: If the year is 2003, then
year & 3 == 0
is false. There is no way that the tests on the right side of the logical AND can make the outcome true, so nothing else gets evaluated.By performing the 4th year test first, only the 4th year test (a simple bitwise-AND) is evaluated three-quarters (75 percent) of the time. This speeds up program execution greatly, especially since it avoids the division necessary for the 100th year test (the modulo 25 operation).
NOTE ON PARENTHESES PLACEMENT
One commenter felt parentheses were misplaced in my code and suggested the sub-expressions be regrouped around the logical AND operator (instead of around the logical OR), as follows:
The above is incorrect. The logical AND operator has higher precedence than logical OR and will be evaluated first with or without the new parentheses. Parentheses around the logical AND arguments has no effect. This might lead one to eliminate the sub-groupings entirely:
But, in both cases above, the right side of the logical OR (the 400th year test) is evaluated almost every time (i.e., years not divisible by 4 and 100). Thus, a useful optimization has been mistakenly eliminated.
The parentheses in my original code implement the most optimized solution:
Here, the logical OR is only evaluated for years divisible by 4 (because of the short-circuit AND). The right side of the logical OR is only evaluated for years divisible by 4 and 100 (because of the short-circuit OR).
NOTE FOR C/C++ PROGRAMMERS
C/C++ programmers might feel this expression is more optimized:
This is not more optimized! While the explicit
== 0
and!= 0
tests are removed, they become implicit and are still performed. Worse, the code is no longer valid in strongly-typed languages like C# whereyear & 3
evaluates to anint
, but the logical AND (&&
), OR (||
) and NOT (!
) operators requirebool
arguments.您确定闰年的逻辑是错误的。这应该可以帮助您开始(来自维基百科):
x modulo y
表示x
除以y
的余数。例如,12 模 5 等于 2。Your logic to determine a leap year is wrong. This should get you started (from Wikipedia):
x modulo y
means the remainder ofx
divided byy
. For example, 12 modulo 5 is 2.很多答案都谈到性能。没有显示任何测量值。
gcc 文档中关于
__builtin_expect
的精彩引用说:大多数实现使用
&&
和||
的短路作为优化工具,并继续为“最佳”的整除性检查规定“正确”的顺序表现。值得一提的是,短路不一定是优化功能。同意,某些检查可能会给出明确的答案(例如年份不是 4 的倍数)并使后续测试变得无用。此时立即返回似乎是合理的,而不是继续进行不必要的计算。另一方面,早期返回会引入分支,这可能会降低性能。 (请参阅这篇传奇的帖子。)分支错误预测和不必要的计算之间的权衡很难猜测。事实上,它取决于硬件、输入数据、编译器发出的确切汇编指令(可能从一个版本更改为另一个版本)等。
续集将显示在 quick-bench.com。在所有情况下,我们都会测量检查 std::array中存储的每个值是否为闰年所需的时间。这些值是伪随机的,均匀分布在区间 [-400, 399] 内。更准确地说,它们是由以下代码生成的:
即使可能,我也不用
&
替换%
(例如year & 3 == 0< /code> 而不是
year % 4 == 0
)。我相信编译器(GCC-9.2 at-O3
)会为我做到这一点。 (确实如此。)4-100-400 次测试
闰年检查通常以三个整除性测试的形式编写:
year % 4 == 0
、year % 100 != 0
和年份 % 400 == 0
。以下是涵盖这些检查可能出现的所有可能顺序的实现列表。每个实现都以相应的标签为前缀。 (有些命令允许两种不同的实现,在这种情况下,第二个获得后缀b
。)它们是:结果如下所示。 (查看他们现场。)
与许多人的建议相反,检查首先被
4
整除似乎并不是最好的选择。相反,至少在这些测量中,前三个柱属于最差的五个柱。最好的是4-25-16 次测试
另一个提供的提示(我必须承认我认为这是一个很好的提示)是将
year % 100 != 0
替换为year % 25 != 0< /代码>。这不会影响正确性,因为我们还会检查
year % 4 == 0
。 (如果数字是4
的倍数,则被100
整除相当于被25
整除。)同样,year % 400 =由于存在
可以替换为25
的整除性检查,因此 = 0year % 16 == 0
。与上一节一样,我们有 8 个使用 4-25-16 整除性检查的实现:
结果(实时此处):
同样,首先检查
4
的整除性看起来不是一个好主意。在这一轮中,快速是4-100-400 次测试(无分支)
如前所述,分支可能会降低性能。特别是,短路可能会适得其反。在这种情况下,一个经典技巧是将逻辑运算符
&&
和||
替换为按位对应的&
和<代码>|。实现变为:结果(实时此处):
一个值得注意的特征是,性能变化并不像分支情况那样明显,因此很难宣布获胜者。我们选择这个:
4-25-16 测试(无分支)
为了完成练习,我们考虑使用 4-25-16 整除性测试的无分支情况:
结果(实时 此处):
再次,很难定义最好的,我们选择这个:
联赛
冠军 现在是时候挑选前面每个部分的最佳部分并进行比较:
结果(实时此处):
该图表表明短路确实是一种优化,但被
4
整除应该是最后检查的而不是第一个检查的。为了获得更好的性能,应该首先检查是否能被100
整除。这实在是太令人惊讶了!毕竟,后一个测试永远不足以确定年份是否为闰年,并且始终需要后续测试(通过400
或通过4
)。同样令人惊讶的是,对于分支版本,使用更简单的除数
25
和16
并不比使用更直观的100
和400< /代码>。我可以提供我的“理论”,它也部分解释了为什么首先测试
100
比测试4
更好。正如许多人指出的那样,4
的整除性测试将执行分为 (25%, 75%) 部分,而100
的测试将其分为 (1%, 99%) 。在后一个检查之后,执行必须继续进行另一个测试并不重要,因为至少分支预测器更有可能正确猜测要走哪条路。同样,通过25
检查整除性会将执行分为 (4%, 96%),这对于分支预测器来说比 (1%, 99%) 更具挑战性。看起来最好是最小化分布的熵,以帮助分支预测器,而不是最大化早期返回的概率。对于无分支版本,简化的除数确实提供了更好的性能。在这种情况下,分支预测器不起作用,因此越简单越好。一般来说,编译器可以用较小的数字执行更好的优化。
是这个吗?
我们是否碰壁并发现这
是闰年性能最好的检查?绝对不是。例如,我们没有混合分支运算符
&&
或||
,而没有分支运算符&
和|< /代码>。也许...让我们看看上面的内容并将其与其他两个实现进行比较。第一个是
(注意分支
||
和非分支&
运算符的混合。)第二个是一个晦涩的“hack”:后者有效吗?是的,确实如此。我建议不要给出数学证明,而是将上面发出的代码与这个更具可读性的来源的代码进行比较:
在 Compiler 中资源管理器。它们几乎相同,唯一的区别是一个使用
add
指令,而另一个使用lea
指令。这应该让您相信黑客代码确实有效(只要其他代码有效)。基准测试结果(实时此处):
等等,我听到你说,为什么不呢使用上图中更易读的代码?好吧,我已经尝试过并学到了另一个教训。当将此代码插入基准循环时,编译器将源代码作为一个整体进行查看,并决定发出与单独查看源代码时不同的代码。表现更差了。去算算吧!
现在呢?是这个吗?
我不知道!有很多事情我们可以探索。例如,最后一节展示了使用
if
语句而不是短路的另一个版本。这可能是获得更好性能的一种方法。我们还可以尝试三元运算符?
。请注意,所有测量和结论均基于 GCC-9.2。使用另一个编译器和/或版本,事情可能会改变。例如,GCC 从 9.1 版本开始引入了一种新的改进的整除性检查算法。因此,旧版本具有不同的性能,并且不必要的计算和分支错误预测之间的权衡可能已经改变。
我们绝对可以得出的结论是:
Many answers talk about performance. None shows any measurement.
A nice quote from gcc's documentation on
__builtin_expect
says this:Most implementations use short-circuiting of
&&
and||
as an optimization tool and go on to prescribe the "right" order for the divisibility checks for "best" performance. It is worth mentioning that short-circuiting is not necessarily an optimization feature.Agreed, some checks might give a definitive answer (e.g. year is not multiple of 4) and make useless the subsequent tests. It seems all reasonable to immediately return at this point rather than keeping going with needless calculations. On the other hand, early returns introduce branches and this might decrease performance. (See this legendary post.) The trade-off between a branch misprediction and an unnecessary calculation is very hard to guess. Indeed, it depends on the hardware, on input data, on the exactly assembly instructions emitted by the compiler (which might change from one version to another), etc.
The sequel shall show measurements obtained in quick-bench.com. In all cases, we measure the time taken to check whether each value stored in an
std::array<int, 65536>
is a leap year or not. The values are pseudo-random, uniformly distributed in the interval [-400, 399]. More precisely, they are generated by the following code:Even when possible, I do not replace
%
with&
(e.g.year & 3 == 0
instead ofyear % 4 == 0
). I trust the compiler (GCC-9.2 at-O3
) will do that for me. (It does.)4-100-400 tests
Checks for leap years are, usually, written in terms of three divisibility tests:
year % 4 == 0
,year % 100 != 0
andyear % 400 == 0
. The following is a list of implementations covering all possible orders in which these checks can appear. Each implementation is prefixed with a corresponding label. (Some orders allow two different implementations, in which case, the 2nd one gets a suffixb
.) They are:The results are shown below. (See them live.)
Contrarily to what many have advised, checking divisibility by
4
first does not seem to be the best thing to do. At the contrary, at least in these measurements, the three first bars are among the worst five. The best is4-25-16 tests
Another provided tip (which I must confess I thought was a good one) is replacing
year % 100 != 0
withyear % 25 != 0
. This doesn't affect correctness since we also checkyear % 4 == 0
. (If a number is multiple of4
then divisibility by100
is equivalent to divisibility by25
.) Similarly,year % 400 == 0
can be replaced withyear % 16 == 0
due to the presence of divisibility check by25
.As in the last section, we have 8 implementations using 4-25-16 divisibility checks:
Results (live here):
Again, checking divisibility by
4
first does not look a good idea. In this round the fast is4-100-400 tests (no branching)
As previously mentioned branching might degrade performance. In particular, short-circuiting might be counterproductive. When this is the case, a classical trick is replacing logical operators
&&
and||
with their bit-wise counterparts&
and|
. The implementations become:Results (live here):
A noticeable feature is that performance variation is not as pronounced as in the branching case and it makes difficult to declare a winner. We pick this one:
4-25-16 tests (no branching)
To complete the exercise, we consider the no-branching case with 4-25-16 divisibility tests:
Results (live here):
Once again, it's difficult to define the best and we select this one:
Champions League
It's now time to pick the best of each previous section and compare them:
Results (live here):
This chart suggests that short-circuiting is, indeed, an optimization but divisibility by
4
should be the last to be checked rather then the first. For better performance one should check divisibility by100
first. This is rather surprising! After all, the latter test is never enough to decide whether the year is leap or not and a subsequent test (either by400
or by4
) is always required.Also surprising is that for the branching versions using simpler divisors
25
and16
is not better than using the more intuitive100
and400
. I can offer my "theory" which also partially explains why testing for100
first is better than testing for4
. As many pointed out, the divisibility test by4
splits execution into (25%, 75%) parts whereas the test for100
splits it into (1%, 99%). It doesn't matter that after the latter check, the execution must carry on to another test because, at least, the branch predictor is more likely to guess correctly which way to go. Similarly, checking divisibility by25
splits execution into (4%, 96%) which is more challenging for the branch predictor than (1%, 99%). It looks like that it is better to minimize the entropy of the distribution, helping the branch predictor, rather than maximizing the probability of early return.For the no branching versions, simplified divisors do offer better performance. In this case the branch predictor plays no role and, therefore, the simpler the better. Generally, the compiler can perform better optimizations with smaller numbers.
Is this it?
Did we hit the wall and found out that
is the most performant check for leap years? Definitely not. We haven't for instance, mixed branching operators
&&
or||
with no branching ones&
and|
. Perhaps... Let's see and compare the above with two other implementations. The first is(Notice the mix of branching
||
and non-branching&
operators.) The second is an obscure "hack":Does the latter work? Yes it does. Instead of giving a mathematical proof I suggest comparing the code emitted for the above with the one for this more readable source:
in Compiler Explorer. They are almost identical, the only difference being that one uses an
add
instruction when the other uses alea
. This should convince you that the hack code does work (as long as the other does).Benchmark results (live here):
Hold on, I hear you say, why not using the more readable code in the picture above? Well, I've tried and learned another lesson. When this code is inserted into the benchmark loop, the compiler looked at the source as a whole and decided to emit different code than when it sees the source in isolation. The performance was worse. Go figure!
And now? Is this it?
I don't know! There are many things that we could explore. The last section, for instance, showed yet another version using an
if
statement rather that short-circuiting. That could be a way to get even better performance. We could also try the ternary operator?
.Be aware that all measurements and conclusions were based on GCC-9.2. With another compiler and/or version things might change. For instance, GCC from version 9.1 introduced a new improved algorithm for divisibility checks. Hence, older versions have different performances and the trade-off between an unnecessary calculation and a branch misprediction have likely changed.
The points that we can definitely conclude are:
这可能是正确的解决方案。维基百科上给出的算法不正确。
This could be the right solution. Algorithm given on Wikipedia is not right.
虽然先除以 400 的逻辑无可挑剔,但它的计算效率不如先除以 4。您可以使用以下逻辑来做到这一点:
对于每个值都除以 4,但对于其中的 3/4,测试将在此终止。对于通过第一次测试的 1/4,然后除以 100,消除 24/25 个值;对于 100 中剩下的 1,它也除以 400,得出最终答案。当然,这并不是一个巨大的节省。
Although the logic that divides by 400 first is impeccable, it is not as computationally efficient as dividing by 4 first. You can do that with the logic:
This divides by 4 for every value, but for 3/4 of them, the testing terminates there. For the 1/4 that pass the first test, it then divides by 100, eliminating 24/25 values; for the remaining 1 out of a 100, it divides by 400 too, coming up with a final answer. Granted, this is not a huge saving.
来自关于闰年的维基百科文章:
From Wikipedia article on Leap year:
您的代码的问题在于,如果您认为年份是闰年,那么您将从
yearr
返回非零值。因此,您的 if 语句中不需要!
。The problem with your code is that you are returning a non-zero value from
yearr
if you think that the year is a leap year. So you don't need the!
in your if statement.http://www.wwu.edu/depts/skywise/leapyear.html
http://www.wwu.edu/depts/skywise/leapyear.html
像上面那样改变它。另请阅读此内容。
Change it like above. Also read this.
这里还有另外 2 个解决方案,它们似乎在 quick-bench.com 基准测试中击败了之前的解决方案。
这个有一个测试,但使用 clang 编译为无分支代码:
这个使用单个模运算并且没有测试,并且仅编译为 2 个乘法:
clang 64 位程序集:
这是基准测试结果 >:
Here are 2 more solutions that seem to beat previous ones on the quick-bench.com benchmark.
This one has a test but that compiles to branchless code with clang:
This one uses a single modulo operation and no tests, and compiles to just 2 multiplications:
clang 64-bit Assembly:
Here is the are the benchmark results:
正如其他人也提到的那样,闰年的条件不正确。它应该:
在这里阅读如何检查闰年在 C 中。
As other have also mentioned condition for leap year is not correct. It should:
Read it here how to check leap year in C.
Kevin 的答案提供了最佳的 8 操作测试(使用常量进行异或),但如果您正在寻找更具可读性的东西,请尝试这个 9 操作测试。
(year % 100 == 0) ^ (year % 400 == 0)
的真值表!(year % 100 == 0) ^ (year % 400 == 0)< /code> 给出你想要的。
Kevin's answer provides an optimal 8 operation test (with XOR using constants) but if you are looking for something a bit more readable, try this 9 operation test.
Truth table for
(year % 100 == 0) ^ (year % 400 == 0)
Now
!(year % 100 == 0) ^ (year % 400 == 0)
gives what you want.计算月份的最大/最后一天:1..12,年份:1..3999
Calculate max/last day for month: 1..12, year: 1..3999