C语言 如何在C中以编程方式查找闰年

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3220163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 05:51:28  来源:igfitidea点击:

How to find leap year programmatically in C

cleap-year

提问by fuddin

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.

我用 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;
}

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;
}

回答by Kevin P. Rice

Most efficient leap year test:

最有效的闰年测试:

if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0))
{
    /* leap year */
}

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:

此代码在 C、C++、C#、Java 和许多其他类似 C 的语言中有效。该代码使用由三个独立测试组成的单个 TRUE/FALSE 表达式:

  • 4th year test: year & 3
  • 100th year test: year % 25
  • 400th year test: year & 15
  • 第四年测试: year & 3
  • 100年测试: year % 25
  • 第 400 年测试: 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:

历史最悠久(且效率低下)的维基百科算法之一如下所示:

if year modulo 400 is 0 then
   is_leap_year
else if year modulo 100 is 0 then
   not_leap_year
else if year modulo 4 is 0 then
   is_leap_year
else
   not_leap_year

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.

上述算法效率低下,因为它总是执行第 400 年和第 100 年的测试,即使是那些会很快通过“第 4 年测试”(模 4 测试)的年份——这是 75% 的时间!通过重新排序算法以首先执行第 4 年测试,我们显着加快了速度。

"MOST-EFFICIENT" PSEUDO-CODE ALGORITHM

“最高效”伪代码算法

I provided the following algorithm to Wikipedia (more than once):

我向维基百科提供了以下算法(不止一次):

if year is not divisible by 4 then not leap year
else if year is not divisible by 100 then leap year
else if year is divisible by 400 then leap year
else not leap year

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.

这个“最有效”的伪代码只是简单地改变了测试的顺序,所以先除以 4,然后是发生频率较低的测试。由于“年份”在 75% 的时间内不会被 4 整除,因此算法在四分之三的情况下仅在一次测试后结束。

NOTE:I have fought various Wikipedia editors to improve the algorithm published there, arguing that many novice—and professional—programmers quickly arrive at the Wikipedia page (due to top search engine listings) and implement the Wikipedia pseudo-code without any further research. Wikipedia editors repudiated and deleted every attempt I made to improve, annotate or even merely footnote the published algorithm. Apparently, they feel finding efficiencies is the programmer's problem. That may be true, but many programmers are too hurried to perform solid research!

注意:我曾与各种 Wikipedia 编辑器抗争以改进在那里发布的算法,认为许多新手和专业程序员很快就到达了 Wikipedia 页面(由于顶级搜索引擎列表)并在没有任何进一步研究的情况下实现了 Wikipedia 伪代码。维基百科编辑否认并删除了我为改进、注释甚至只是为已发布的算法添加脚注所做的每一次尝试。显然,他们觉得寻找效率是程序员的问题。这可能是真的,但是许多程序员太匆忙而无法进行扎实的研究!

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.

执行模计算需要除法。在对 PC 进行编程时,人们通常不会三思而后行,但是在对嵌入在小型设备中的 8 位微控制器进行编程时,您可能会发现除法功能无法由 CPU 本地执行。在此类 CPU 上,除法是一个艰巨的过程,涉及非常缓慢的重复循环、位移和加/减操作。非常希望避免。

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):

事实证明,可以使用按位与运算交替实现 2 的幂的模(参见:维基百科:模运算 - 性能问题):

x % 2^n == x & (2^n - 1)

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.

许多优化编译器会为您将此类模运算转换为按位与,但用于较小和不太流行的 CPU 的不太先进的编译器可能不会。按位与是每个 CPU 上的一条指令。

By replacing the modulo 4and modulo 400tests with & 3and & 15(see below: 'Factoring to reduce math') we can ensure that the fastest code results without using a much slower divide operation.

通过更换modulo 4modulo 400使用试验& 3& 15(见下文:“保理,以减少数学”),我们可以保证最快的代码结果,而不使用更慢的除法运算。

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).

不存在等于 100 的 2 次幂。因此,我们被迫在第 100 年测试中继续使用模运算,但是 100 被 25 取代(见下文)。

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 100is replaced by modulo 25
  • modulo 400is replaced by & 15
  • modulo 100被替换为 modulo 25
  • modulo 400被替换为 & 15

The 100th year test utilizes modulo 25instead of modulo 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).

第 100 年测试使用modulo 25而不是modulo 100. 我们可以这样做,因为 2 x 2 x 5 x 5 的 100 个因数。因为第 4 年的测试已经检查了 4 的因数,我们可以从 100 中消除该因数,留下 25。这种优化可能对几乎每个 CPU 实现都无关紧要(因为 100 和 25 都适合 8 位)。

The 400th year test utilizes & 15which is equivalent to modulo 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.

第 400 年测试使用& 15相当于modulo 16. 同样,我们可以这样做,因为将 400 个因子分解为 2 x 2 x 2 x 2 x 5 x 5。我们可以消除第 100 年测试所测试的因子 25,留下 16。我们不能进一步减少 16,因为 8 是因子为 200,因此删除更多因子将在第 200 年产生不需要的正数。

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.

第 400 年优化对于 8 位 CPU 非常重要,首先,因为它避免了除法;但是,更重要的是,因为值 400 是一个 9 位数字,在 8 位 CPU 中处理起来要困难得多。

Short-circuit Logical AND/OR operators:

短路逻辑 AND/OR 运算符:

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.

使用的最后也是最重要的优化是短路逻辑 AND ('&&') 和 OR ('||') 运算符(请参阅:维基百科:短路评估),它们在大多数类 C 语言中实现. 短路运算符之所以如此命名,是因为如果左侧的表达式本身决定了操作的结果,则它们不会费心计算右侧的表达式。

For example: If the year is 2003, then year & 3 == 0is 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.

例如:如果年份是 2003,year & 3 == 0则为 false。逻辑 AND 右侧的测试无法使结果为真,因此不会评估其他任何内容。

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).

通过首先执行第 4 年测试,只有四分之三 (75%) 的时间评估第 4 年测试(简单的按位与)。这大大加快了程序的执行速度,特别是因为它避免了第 100 年测试(模 25 运算)所需的除法。

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:

一位评论者认为括号在我的代码中放错了位置,并建议将子表达式重新组合在逻辑 AND 运算符周围(而不是逻辑 OR 周围),如下所示:

if (((year & 3) == 0 && (year % 25) != 0) || (year & 15) == 0) { /* LY */ }

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:

以上说法不正确。逻辑 AND 运算符比逻辑 OR 具有更高的优先级,并且将首先使用或不使用新括号进行评估。逻辑 AND 参数周围的括号不起作用。这可能会导致完全消除子分组:

if ((year & 3) == 0 && (year % 25) != 0 || (year & 15) == 0) { /* LY */ }

But, in bothcases 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.

但是,在上述两种情况下,几乎每次都评估逻辑 OR(第 400 年测试)的右侧(即不能被 4 和 100 整除的年份)。因此,错误地消除了有用的优化。

The parentheses in my original code implement the most optimized solution:

我原代码中的括号实现了最优化的解决方案:

if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) { /* LY */ }

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).

在这里,逻辑 OR 仅对可被 4 整除的年份进行评估(由于短路 AND)。逻辑 OR 的右侧仅评估可被 4 和 100 整除的年份(因为短路 OR)。

NOTE FOR C/C++ PROGRAMMERS

C/C++ 程序员注意事项

C/C++ programmers might feel this expression is more optimized:

C/C++ 程序员可能会觉得这个表达式更优化:

if (!(year & 3) && ((year % 25) || !(year & 15))) { /* LY */ }

This is not more optimized! While the explicit == 0and != 0tests are removed, they become implicit and are still performed. Worse, the code is no longer valid in strongly-typed languages like C# where year & 3evaluates to an int, but the logical AND (&&), OR (||) and NOT (!) operators require boolarguments.

这不是更优化!当显式== 0!= 0测试被删除时,它们变成隐式并且仍然被执行。更糟糕的是,该代码在 C# 等强类型语言中不再有效,其中year & 3计算结果为 an int,但逻辑 AND ( &&)、OR ( ||) 和 NOT ( !) 运算符需要bool参数。

回答by Alok Singhal

Your logic to determine a leap year is wrong. This should get you started (from Wikipedia):

您确定闰年的逻辑是错误的。这应该让你开始(来自维基百科):

if year modulo 400 is 0
       then is_leap_year
else if year modulo 100 is 0
       then not_leap_year
else if year modulo 4 is 0
       then is_leap_year
else
       not_leap_year

x modulo ymeans the remainder of xdivided by y. For example, 12 modulo 5 is 2.

x modulo y表示x除以的余数y。例如,12 模 5 是 2。

回答by st0le

int isLeapYear(int year)
{
   return (year % 400 == 0) || ( ( year % 100 != 0) && (year % 4 == 0 ));
}

回答by Jonathan Leffler

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:

先除以400的逻辑虽然无懈可击,但计算效率不如先除以4。你可以用逻辑做到这一点:

#define LEAPYEAR(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))

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.

这对每个值除以 4,但对于其中的 3/4,测试在那里终止。对于通过第一次测试的 1/4,然后除以 100,消除 24/25 个值;对于 100 中剩下的 1,它也除以 400,得出最终答案。当然,这并不是一个巨大的节省。

回答by Solid Line

This could be the right solution. Algorithm given on Wikipedia is not right.

这可能是正确的解决方案。维基百科上给出的算法是不正确的。

-(BOOL)isLeapYear: (int)year{    

    if(year%4==0){
      if(year%100!=0){
        return YES;
     }
     else if(year%400!=0){
        return YES;
     }
     else return NO;
   }

    else return NO;
  }

回答by Cassio Neri

Many answers talk about performance. None shows any measurement. A nice quote from gcc's documentation on __builtin_expectsays this:

许多答案谈论性能。无显示任何测量。来自 gcc 文档的一个很好的引用是这样__builtin_expect说的:

Programmers are notoriously bad at predicting how their programs actually perform.

众所周知,程序员不擅长预测他们的程序实际执行情况。

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 necessarilyan 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 mispredictions 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.

同意,某些检查可能会给出明确的答案(例如,年份不是 4 的倍数)并使后续测试无用。在这一点上立即返回而不是继续进行不必要的计算似乎是合理的。另一方面,早期返回会引入分支,这可能会降低性能。(请参阅这篇传奇文章。)很难猜测分支错误预测和不必要计算之间的权衡。实际上,它取决于硬件、输入数据、编译器发出的确切汇编指令(可能会从一个版本更改为另一个版本)等。

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:

续集应显示在quick-bench.com 中获得的测量结果。在所有情况下,我们都会测量检查存储在std::array<int, 65536>a中的每个值是否为闰年所花费的时间。这些值是伪随机的,均匀分布在区间 [-400, 399] 中。更准确地说,它们是由以下代码生成的:

auto const years = [](){
  std::uniform_int_distribution<int> uniform_dist(-400, 399);
  std::mt19937 rng;
  std::array<int, 65536> years;
  for (auto& year : years)
    year = uniform_dist(rng);
  return years;
}();

Even when possible, I do not replace %with &(e.g. year & 3 == 0instead of year % 4 == 0). I trust the compiler (GCC-9.2 at -O3) will do that for me. (It does.)

即使可能,我也不会替换%&(例如year & 3 == 0代替year % 4 == 0)。我相信编译器(GCC-9.2 at -O3)会为我做到这一点。(确实如此。)

4-100-400 tests

4-100-400 次测试

Checks for leap years are, usually, written in terms of three divisibility tests: year % 4 == 0, year % 100 != 0and year % 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 suffix b.) They are:

闰年的检查通常根据三个可分性测试编写:year % 4 == 0year % 100 != 0year % 400 == 0。以下是涵盖所有可能出现这些检查的顺序的实现列表。每个实现都以相应的标签为前缀。(有些命令允许两种不同的实现,在这种情况下,第二个得到后缀b。)它们是:

b4_100_400  : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
b4_100_400b : (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
b4_400_100  : year % 4 == 0 && (year % 400 == 0 || year % 100 != 0)
b100_4_400  : (year % 100 != 0 && year % 4 == 0) || year % 400 == 0
b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0
b400_4_100  : year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
b400_100_4  : year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
b400_100_4b : (year % 400 == 0 || year % 100 != 0) && year % 4 == 0

The results are shown below. (See them live.)

结果如下所示。(现场观看。)

4-100-400 tests

4-100-400 次测试

Contrarily to what many have advised, checking divisibility by 4first 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 is

与许多人的建议相反,4首先检查可分性似乎不是最好的做法。相反,至少在这些测量中,前三个柱是最差的五个。最好的是

b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0

4-25-16 tests

4-25-16 测试

Another provided tip (which I must confess I thought was a good one) is replacing year % 100 != 0with year % 25 != 0. This doesn't affect correctness since we also check year % 4 == 0. (If a number is multiple of 4then divisibility by 100is equivalent to divisibility by 25.) Similarly, year % 400 == 0can be replaced with year % 16 == 0due to the presence of divisibility check by 25.

另一个提供尖端(我必须承认我认为是一个很好的)被替换year % 100 != 0year % 25 != 0。这不会影响正确性,因为我们还会检查year % 4 == 0. (如果一个数是 的倍数,则被4整除性100等于 被 整除性25。)同样,由于存在可整除性检查,year % 400 == 0可以替换year % 16 == 025

As in the last section, we have 8 implementations using 4-25-16 divisibility checks:

和上一节一样,我们有 8 个使用 4-25-16 可分性检查的实现:

b4_25_16  : year % 4 == 0 && (year % 25 != 0 || year % 16 == 0)
b4_25_16b : (year % 4 == 0 && year % 25 != 0) || year % 16 == 0
b4_16_25  : year % 4 == 0 && (year % 16 == 0 || year % 25 != 0)
b25_4_16  : (year % 25 != 0 && year % 4 == 0) || year % 16 == 0
b25_16_4  : (year % 25 != 0 || year % 16 == 0) && year % 4 == 0
b16_4_25  : year % 16 == 0 || (year % 4 == 0 && year % 25 != 0)
b16_25_4  : year % 16 == 0 || (year % 25 != 0 && year % 4 == 0)
b16_25_4b : (year % 16 == 0 || year % 25 != 0) && year % 4 == 0

Results (live here):

结果(住在这里):

4-25-16 tests

4-25-16 测试

Again, checking divisibility by 4first does not look a good idea. In this round the fast is

同样,4首先检查可分性看起来不是一个好主意。在这一轮中,斋戒是

b25_16_4  : (year % 25 != 0 || year % 16 == 0) && year % 4 == 0

4-100-400 tests (no branching)

4-100-400 次测试(无分支)

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:

如前所述,分支可能会降低性能。特别是,短路可能会适得其反。在这种情况下,一个经典的诀窍是代替逻辑运算符&&,并||与他们的逐位同行&|。实现变为:

nb4_100_400  : (year % 4 == 0) & ((year % 100 != 0) | (year % 400 == 0))
nb4_100_400b : ((year % 4 == 0) & (year % 100 != 0)) | (year % 400 == 0)
nb4_400_100  : (year % 4 == 0) & ((year % 400 == 0) | (year % 100 != 0))
nb100_4_400  : ((year % 100 != 0) & (year % 4 == 0)) | (year % 400 == 0)
nb100_400_4  : ((year % 100 != 0) | (year % 400 == 0)) & (year % 4 == 0)
nb400_4_100  : (year % 400 == 0) | ((year % 4 == 0) & (year % 100 != 0))
nb400_100_4  : (year % 400 == 0) | ((year % 100 != 0) & (year % 4 == 0))
nb400_100_4b : ((year % 400 == 0) | (year % 100 != 0)) & (year % 4 == 0)

Results (live here):

结果(住在这里):

4-100-400 tests (no branching)

4-100-400 次测试(无分支)

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:

一个值得注意的特点是性能变化不像分支情况那样明显,并且很难宣布获胜者。我们选择这个:

nb100_400_4  : ((year % 100 != 0) | (year % 400 == 0)) & (year % 4 == 0)

4-25-16 tests (no branching)

4-25-16 测试(无分支)

To complete the exercise, we consider the no-branching case with 4-25-16 divisibility tests:

为了完成练习,我们考虑具有 4-25-16 可分性测试的无分支情况:

nb4_25_16  : (year % 4 == 0) & ((year % 25 != 0) | (year % 16 == 0))
nb4_25_16b : ((year % 4 == 0) & (year % 25 != 0)) | (year % 16 == 0)
nb4_16_25  : (year % 4 == 0) & ((year % 16 == 0) | (year % 25 != 0))
nb25_4_16  : ((year % 25 != 0) & (year % 4 == 0)) | (year % 16 == 0)
nb25_16_4  : ((year % 25 != 0) | (year % 16 == 0)) & (year % 4 == 0)
nb16_4_25  : (year % 16 == 0) | ((year % 4 == 0) & (year % 25 != 0))
nb16_25_4  : (year % 16 == 0) | ((year % 25 != 0) & (year % 4 == 0))
nb16_25_4b : ((year % 16 == 0) | (year % 25 != 0)) & (year % 4 == 0)

Results (live here):

结果(住在这里):

4-25-16 tests (no branching)

4-25-16 测试(无分支)

Once again, it's difficult to define the best and we select this one:

再一次,很难定义最好的,我们选择了这个:

nb25_16_4  : ((year % 25 != 0) | (year % 16 == 0)) & (year % 4 == 0)

Champions League

冠军联赛

It's now time to pick the best of each previous section and compare them:

现在是时候选择前面每个部分中最好的部分并进行比较:

b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0
b25_16_4    : (year % 25 != 0 || year % 16 == 0) && year % 4 == 0
nb100_400_4 : ((year % 100 != 0) | (year % 400 == 0)) & (year % 4 == 0)
nb25_16_4   : ((year % 25 != 0) | (year % 16 == 0)) & (year % 4 == 0)

Results (live here):

结果(住在这里):

Champions League

冠军联赛

This chart suggests that short-circuiting is, indeed, an optimization but divisibility by 4should be the last to be checked rather then the first. For better performance one should check divisibility by 100first. 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 by 400or by 4) is always required.

该图表表明短路确实是一种优化,但4应最后检查而不是第一个检查可整除性。为了获得更好的性能,100首先应该检查可分性。这相当令人惊讶!毕竟,后一个测试永远不足以确定年份是否为闰年,并且始终需要后续测试(by400或 by 4)。

Also surprising is that for the branching versions using simpler divisors 25and 16is not better than using the more intuitive 100and 400. I can offer my "theory" which also partiallyexplains why testing for 100first is better than testing for 4. As many pointed out, the divisibility test by 4splits execution into (25%, 75%) parts whereas the test for 100splits 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 by 25splits 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.

同样令人惊讶的是,对于使用更简单除数的分支版本2516并不比使用更直观的100400. 我可以提供我的“理论”,这也部分解释了为什么测试100first 比测试4. 正如许多人指出的那样,可分性测试4将执行分成 (25%, 75%) 部分,而测试100将其分成 (1%, 99%)。在后一个检查之后,执行必须继续进行另一个测试并不重要,因为至少,分支预测器更有可能正确猜测该走哪条路。类似地,通过以下方式检查可除性25将执行分成 (4%, 96%),这对分支预测器来说比 (1%, 99%) 更具挑战性。看起来最好是最小化分布的熵,帮助分支预测器,而不是最大化早期返回的概率。

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

我们是不是撞墙了才发现

b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0

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

是最高效的闰年检查吗?当然不。例如,我们没有混合分支运算符&&||没有分支运算符&|。也许......让我们看看上面的内容并将其与其他两个实现进行比较。第一个是

m100_400_4 : (year % 100 != 0 || year % 400 == 0) & (year % 4 == 0)

(Notice the mix of branching ||and non-branching &operators.) The second is an obscure "hack":

(注意分支||和非分支&运算符的混合。)第二个是一个晦涩的“黑客”:

bool b;
auto n = 0xc28f5c29 * year;
auto m = n + 0x051eb850;
m = (m << 30 | m >> 2);
if (m <= 0x28f5c28)
  b = n % 16 == 0;
else
  b = n % 4 == 0;
return b

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:

后者有效吗?是的,它确实。我建议将上面发出的代码与这个更具可读性的源代码进行比较,而不是给出数学证明:

bool b;
if (year % 100 == 0)
  b = year % 16 == 0;
else
  b = year % 4 == 0;
return b;

in Compiler Explorer. They are almost identical, the only difference being that one uses an addinstruction when the other uses a lea. This should convince you that the hack code does work (as long as the other does).

编译器资源管理器中。它们几乎相同,唯一的区别是一个使用add指令而另一个使用lea. 这应该使您相信黑客代码确实有效(只要其他人有效)。

Benchmark results (live here):

基准测试结果(住在这里):

Final

最终的

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 ifstatement rather that short-circuiting. That could be a way to get even better performance. We could also try the ternary operator ?.

我不知道!我们可以探索的东西很多。例如,最后一节展示了另一个使用if语句而不是短路的版本。这可能是获得更好性能的一种方式。我们也可以试试三元运算符?

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.

请注意,所有测量和结论均基于 GCC-9.2。使用另一个编译器和/或版本,事情可能会改变。例如,9.1 版的 GCC 引入了一种新的改进算法,用于可分性检查。因此,旧版本具有不同的性能,并且不必要的计算和分支预测错误之间的权衡可能已经改变。

The points that we can definitely conclude are:

我们可以肯定地得出以下几点:

  1. Do not overthink. Prefer clear code over difficult to understand optimizations. After all, a hand-crafted snippet might not be the most performant option when you upgrade compiler. As ex-nihilosaid in a comment, "There is little point in fussing over a piece of code like this unless it is known to be a performance bottleneck."
  2. Guessing is difficult and it's better to measure.
  3. Measuring is difficult but is better than guessing.
  4. Short-circuiting/branching can be good for performance but it depends on many things including how much the code helps the branch predictor.
  5. Code emitted for a snippet in isolation might be different when it's inlined somewhere.
  1. 别想太多。更喜欢清晰的代码而不是难以理解的优化。毕竟,当您升级编译器时,手工制作的代码段可能不是性能最高的选项。正如ex-nihilo在评论中所说,“除非已知它是性能瓶颈,否则对这样的一段代码大惊小怪是没有意义的。”
  2. 猜测是困难的,最好是衡量。
  3. 测量很难,但总比猜测好。
  4. 短路/分支对性能有好处,但它取决于许多因素,包括代码对分支预测器的帮助程度。
  5. 单独为代码片段发出的代码在某处内联时可能会有所不同。

回答by miku

From Wikipedia article on Leap year:

来自维基百科关于闰年的文章

if (year modulo 4 is 0) and (year modulo 100 is not 0) or (year modulo 400 is 0)
   then is_leap_year
else
   not_leap_year

回答by torak

The problem with your code is that you are returning a non-zero value from yearrif you think that the year is a leap year. So you don't need the !in your if statement.

您的代码的问题在于,yearr如果您认为年份是闰年,您将返回一个非零值。所以你不需要!在你的 if 语句中。

回答by John Boker

http://www.wwu.edu/depts/skywise/leapyear.html

http://www.wwu.edu/depts/skywise/leapyear.html

Leap Year Rules

There is a leap year every year whose number is perfectly divisible by four - except for years which are both divisible by 100 and not divisible by 400. The second part of the rule effects century years. For example; the century years 1600 and 2000 are leap years, but the century years 1700, 1800, and 1900 are not. This means that three times out of every four hundred years there are eight years between leap years.

闰年规则

每年都有一个闰年,其数字完全可以被 4 整除 - 除了可以被 100 整除但不能被 400 整除的年份。规则的第二部分影响世纪年。例如; 世纪 1600 年和 2000 年是闰年,但世纪年 1700、1800 和 1900 年不是。这意味着每四百年中有三次闰年之间有八年。

回答by Praveen S

 if(year%400 ==0 || (year%100 != 0 && year%4 == 0))
    {
        printf("Year %d is a leap year",year);
    }
    else
    {
        printf("Year %d is not a leap year",year);
    }

Change it like above. Also read this.

像上面一样改变它。也读这个