我使用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;
}
15 回答
你确定闰年的逻辑是错误的 . 这应该让你开始(来自维基百科):
x modulo y
表示x
的余数除以y
. 例如,12模5是2 .最有效的闰年测试:
此代码在C,C,C#,Java和许多其他类C语言中有效 . 该代码使用单个TRUE / FALSE表达式,该表达式由三个单独的测试组成:
第四年测试:
year & 3
第100年测试:
year % 25
第400年测试:
year & 15
有关此代码如何工作的完整讨论如下所示,但首先需要讨论维基百科的算法:
维基百科算法不充分/不可靠
维基百科发布了一种伪代码算法(参见:Wikipedia: Leap year - Algorithm),该算法经常受到编辑,舆论和故意破坏 .
DO NOT IMPLEMENT WIKIPEDIA ALGORITHM!
最长期(且效率低下)的维基百科算法之一如下:
上述算法是低效的,因为它总是执行400年和100年的测试,即使是很快就会失败的“第4年测试”(模4测试) - 这是75%的时间!通过重新排序算法来执行第4年测试,我们首先加快了速度 .
"MOST-EFFICIENT" PSEUDO-CODE ALGORITHM
我向维基百科提供了以下算法(不止一次):
这种“最有效”的伪代码只是改变了测试的顺序,因此首先进行4除法,然后进行频率较低的测试 . 因为“年份”不会分成四个75%的时间,所以算法在四个案例中的三个案例中仅进行一次测试后结束 .
讨论“最有效”的年度试验
Bitwise-AND in place of modulo:
我已经使用按位AND运算替换了维基百科算法中的两个模运算 . 为什么以及如何?
执行模数计算需要除法 . 在编程PC时,人们通常不会三思而后行,但是当编程嵌入在小型设备中的8位微控制器时,您可能会发现CPU无法原生执行除法功能 . 在这样的CPU上,除法是一个艰难的过程,涉及重复循环,位移和加/减操作,这些操作非常慢 . 非常希望避免 .
事实证明,使用按位AND运算可以交替地实现两个幂的模数(参见:Wikipedia: Modulo operation - Performance Issues):
x%2 ^ n == x&(2 ^ n - 1)
许多优化编译器会将这些模运算转换为按位AND,但对于较小和较不流行的CPU,不太先进的编译器可能不会 . Bitwise-AND是每个CPU上的单个指令 .
通过用
& 3
和& 15
替换modulo 4
和modulo 400
测试(见下文:'Factoring to reduce math'),我们可以确保在不使用更慢的除法运算的情况下获得最快的代码 .没有两个等于100的幂 . 因此,我们被迫继续使用模运算进行100年测试,但是100被替换为25(见下文) .
Factoring to simplify the math:
除了使用按位AND替换模运算之外,您还可以注意到维基百科算法和优化表达式之间的另外两个争议:
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年不需要的积极因素 .对于8位CPU来说,第400年的优化非常重要,首先是因为它避免了划分;但更重要的是,因为值400是9位数,在8位CPU中处理起来要困难得多 .
Short-circuit Logical AND/OR operators:
使用的最后也是最重要的优化是短路逻辑AND('&&')和OR('||')运算符(参见:Wikipedia: Short-circuit evaluation),它们以大多数类C语言实现 . 短路操作符之所以如此命名,是因为如果左侧的表达式本身决定了操作的结果,那么它们就不会费心去评估右侧的表达式 .
例如:如果年份是2003年,则
year & 3 == 0
为假 . 逻辑AND右侧的测试无法使结果成立,因此无法评估任何其他内容 .通过首先执行第四年测试,仅四分之三(75%)的时间评估第四年测试(简单的按位-AND) . 这大大加快了程序的执行速度,特别是因为它避免了第100年测试所需的划分(模25操作) .
NOTE ON PARENTHESES PLACEMENT
一位评论者认为括号在我的代码中放错位置,并建议子表达式在逻辑AND运算符(而不是逻辑OR)周围重新分组,如下所示:
以上是不正确的 . 逻辑AND运算符的优先级高于逻辑OR,并且首先使用或不使用新括号进行求值 . 逻辑AND参数周围的括号无效 . 这可能导致人们完全消除子分组:
但是,在上述情况下,逻辑OR(第400年测试)的右侧几乎每次都被评估(即,不能被4和100整除的年份) . 因此,错误地消除了有用的优化 .
我原始代码中的括号实现了最优化的解决方案:
这里,逻辑OR仅被评估为可被4整除的年份(因为短路AND) . 逻辑OR的右侧仅评估可被4和100整除的年份(因为短路OR) .
NOTE FOR C/C++ PROGRAMMERS
C / C程序员可能会觉得这个表达式更加优化:
这不是更优化!当显式
== 0
和!= 0
测试被删除时,它们将变为隐式并仍然执行 . 更糟糕的是,代码在强类型语言(如C#,其中year & 3
的计算结果为int
)中不再有效,但逻辑AND(&&
),OR(||
)和NOT(!
)运算符需要bool
参数 .这可能是正确的解决方案 . 维基百科上给出的算法是不对的 .
虽然首先除以400的逻辑是无可挑剔的,但它的计算效率不如先除4 . 你可以用逻辑做到这一点:
对于每个值,这除以4,但对于3/4,测试在那里终止 . 对于通过第一次测试的1/4,它除以100,消除24/25值;对于100中的剩余1个,它也除以400,得出最终答案 . 当然,这不是一个巨大的节省 .
来自Wikipedia article on Leap year:
代码的问题在于,如果您认为年份是闰年,则从
yearr
返回非零值 . 因此,在if语句中不需要!
.http://www.wwu.edu/depts/skywise/leapyear.html
像上面一样改变它 . 另请阅读this .
正如其他人也提到的闰年条件不正确 . 这应该:
在这里阅读how to check leap year in C .
Kevin的答案提供了一个最佳的8运算测试(使用常量的XOR)但如果你正在寻找更具可读性的东西,试试这个9运算测试 .
(year % 100 == 0) ^ (year % 400 == 0)
的真值表现在
!(year % 100 == 0) ^ (year % 400 == 0)
给出你想要的东西 .计算月份的最大/最后一天:1..12,年份:1..3999
#define is_leap(A) !((A) & 3)
只要确保你没有进入负面年份:)