12

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

17 回答 17

127

最有效的闰年测试:

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

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

  • 第四年考试:year & 3
  • 100年测试:year % 25
  • 第 400 年测试:year & 15

下面是对这段代码如何工作的完整讨论,但首先需要讨论维基百科的算法:

维基百科算法是低效/不可靠的

维基百科发布了一个伪代码算法(参见:维基百科:闰年 - 算法),该算法一直受到编辑、意见和破坏。

不要实施维基百科算法!

存在时间最长(且效率低下)的 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

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

“最高效”的伪代码算法

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

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

这个“最有效”的伪代码只是改变了测试的顺序,所以首先除以 4,然后是不太频繁发生的测试。因为“年”在 75% 的情况下不会除以四,所以算法在四分之三的情况下仅在一次测试后结束。

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

讨论“最高效”的闰年测试

按位与代替模:

我已将 Wikipedia 算法中的两个模运算替换为按位与运算。为什么以及如何?

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

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

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

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

通过用 and 替换andmodulo 4测试modulo 400(见下文:'因式减少数学'),我们可以确保最快的代码结果,而无需使用更慢的除法运算。& 3& 15

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

简化数学的因式分解:

除了使用按位与来替换模运算之外,您可能会注意到 Wikipedia 算法和优化表达式之间的两个额外争议:

  • modulo 100被替换为modulo 25
  • modulo 400被替换为& 15

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

第 400 年测试使用& 15相当于modulo 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 ('||') 运算符(参见:Wikipedia: Short-circuit evaluation),它们在大多数类 C 语言中实现. 短路运算符之所以如此命名,是因为如果左侧的表达式本身决定了运算的结果,它们不会费心计算右侧的表达式。

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

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

关于括号放置的说明

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

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

以上是不正确的。逻辑 AND 运算符比逻辑 OR 具有更高的优先级,并且将首先在有或没有新括号的情况下进行评估。逻辑 AND 参数周围的括号无效。这可能会导致完全消除子分组:

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

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

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

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

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

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

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

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

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

于 2012-07-21T21:15:23.690 回答
17

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

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 y表示x除以的余数y。例如,12 模 5 为 2。

于 2010-07-10T17:33:15.760 回答
9

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

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

大多数实现使用短路&&||作为优化工具,并继续为“最佳”性能的可分性检查规定“正确”顺序。值得一提的是,短路不一定是优化功能。

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

续集将显示在quick-bench.com中获得的测量结果。在所有情况下,我们都会测量检查存储在 an 中的每个值是否std::array<int, 65536>是闰年所花费的时间。这些值是伪随机的,均匀分布在区间 [-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;
}();

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

4-100-400 次测试

闰年的检查通常是根据三个可分性测试编写的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

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

4-100-400 次测试

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

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

4-25-16 测试

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

与上一节一样,我们有 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

结果(住在这里):

4-25-16 测试

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

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

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

如前所述,分支可能会降低性能。特别是,短路可能会适得其反。在这种情况下,一个经典的技巧是用它们的位对应物替换逻辑运算符&&和。实现变为:||&|

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)

结果(住在这里):

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

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

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

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

为了完成练习,我们考虑使用 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)

结果(住在这里):

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

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

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

冠军联赛

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

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)

结果(住在这里):

冠军联赛

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

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

对于无分支版本,简化除数确实提供了更好的性能。在这种情况下,分支预测器不起作用,因此越简单越好。通常,编译器可以用较小的数字执行更好的优化。

是这个吗?

我们是不是碰壁了才发现

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

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

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

(注意分支||和非分支&运算符的混合。)第二个是一个不起眼的“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

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

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

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

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

最后

等等,我听到你说,为什么不使用上图中更易读的代码呢?好吧,我已经尝试并学到了另一个教训。当将此代码插入基准循环时,编译器会整体查看源代码,并决定发出与单独查看源代码时不同的代码。表现更差。去搞清楚!

现在?是这个吗?

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

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

我们可以明确得出的结论是:

  1. 别想太多。更喜欢清晰的代码而不是难以理解的优化(除非您是库编写者,为您的用户围绕非常有效的实现提供更高级别的 API)。毕竟,当您升级编译器时,手工制作的片段可能不是性能最高的选项。正如ex-nihilo在评论中所说,“除非已知它是性能瓶颈,否则对这样的一段代码大惊小怪是没有意义的。”
  2. 猜测是困难的,最好是衡量。
  3. 测量是困难的,但总比猜测好。
  4. 短路/分支可能对性能有好处,但这取决于很多事情,包括代码对分支预测器的帮助程度。
  5. 单独为代码段发出的代码在某处内联时可能会有所不同。
于 2020-03-12T02:53:55.107 回答
6
int isLeapYear(int year)
{
   return (year % 400 == 0) || ( ( year % 100 != 0) && (year % 4 == 0 ));
}
于 2010-07-10T17:36:40.283 回答
4

尽管先除以 400 的逻辑无可挑剔,但它的计算效率不如先除以 4。您可以使用以下逻辑来做到这一点:

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

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

于 2011-09-29T05:27:43.563 回答
4

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

-(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;
  }
于 2015-05-21T08:20:59.690 回答
3

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

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
于 2010-07-10T17:32:40.443 回答
2

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

于 2010-07-10T17:35:56.403 回答
1
    #包括
    无效主要(无效)
    {
        整数年;
        printf("输入年份判断是否为闰年\n");
        scanf("%d",&year);
        if(year%400==0) /*为什么是 mod 400 */
            printf("%d 是闰年\n",year);
        else if(year%100==0) /*  为什么是 mod 100   */
            printf("%d 不是闰年\n",year);
        否则如果(年%4==0)
            printf("%d 是闰年\n",year);
        别的
            printf("%d 不是闰年\n",year);

    }

于 2014-11-26T09:51:51.037 回答
1

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

闰年规则

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

于 2010-07-10T17:32:50.973 回答
1
 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);
    }

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

于 2010-07-10T17:33:04.663 回答
1

这里还有 2 个解决方案似乎在quick-bench.com基准测试中击败了之前的解决方案。

这个有一个测试,但是用 clang 编译成无分支代码:

int isleap3(int year) {
    unsigned y = year + 16000;
    return (y % 100) ? !(y % 4) : !(y % 16);
}

这一个使用单个模运算并且没有测试,并且只编译为 2 次乘法:

static unsigned char const leaptest[400] = {
    1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
    0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
    0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
    0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
};
int isleap4(int year) {
    unsigned y = year + 16000;
    return leaptest[y % 400];
}

铿锵声 64 位汇编

isleap3:                                # @isleap3
        add     edi, 16000
        imul    eax, edi, -1030792151
        ror     eax, 2
        cmp     eax, 42949673
        mov     eax, 15
        mov     ecx, 3
        cmovb   ecx, eax
        xor     eax, eax
        test    ecx, edi
        sete    al
        ret
isleap4:                                # @isleap4
        add     edi, 16000
        imul    rax, rdi, 1374389535
        shr     rax, 39
        imul    eax, eax, 400
        sub     edi, eax
        movzx   eax, byte ptr [rdi + leaptest]
        ret
leaptest:
        .asciz  "\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000"

以下是基准测试结果

在此处输入图像描述

于 2020-03-30T16:40:54.837 回答
1
I used this code:

#include <stdio.h>

int main()
{
    int yr;
    printf ("Enter a year \n");
    scanf ("%d", &yr);

    if (yr%400 == 0)
        printf("\n LEAP YEAR.");

    else if (yr%4==0 && yr%100!=0)
        printf("\n LEAP YEAR.");
    else
        printf ("\n NOT LEAP YEAR.");
}
于 2017-07-26T09:02:03.610 回答
0

正如其他人也提到闰年的条件不正确。它应该:

int yearr(int year)  
{  
    if(((year%4 == 0) && (year%100 !=0)) || (year%400==0))  
        return 1;    
    else    
        return 0;    
}  

在此处阅读如何在 C 中检查闰年

于 2015-05-24T14:57:08.043 回答
0

Kevin 的回答提供了一个最佳的 8 操作测试(使用常量进行 XOR),但如果您正在寻找更具可读性的东西,请尝试这个 9 操作测试。

year % 4 == 0 && !((year % 100 == 0) ^ (year % 400 == 0))

真值表(year % 100 == 0) ^ (year % 400 == 0)

                              (year % 100 == 0) ^ (year % 400 == 0)
100 doesnt divide year     .    F
only 100 divides year      .    T
100 and 400 divides year   .    F

现在!(year % 100 == 0) ^ (year % 400 == 0)给你想要的。

于 2016-12-07T05:23:33.743 回答
-1

计算月份的最大/最后一天:1..12,年份:1..3999

maxDays = month == 2 ?
  28 + ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) :
  30 + ((month & 1) ^ (month > 7));
于 2016-09-28T03:24:31.633 回答
-6

#define is_leap(A) !((A) & 3)

只要确保你没有输入负年份:)

于 2016-02-20T09:42:30.377 回答