Velocity Reviews > A better algorithm to calculate a leap year?

# A better algorithm to calculate a leap year?

Eric Sosman
Guest
Posts: n/a

 11-05-2007
http://www.velocityreviews.com/forums/(E-Mail Removed) wrote On 11/05/07 13:30,:
> I'm new here, so excuse me if my style is incorrect. Can anyone come
> up with a better method for this calculation?
>
> Code:
> int is_leap(int year)
> {
> switch (year % 19) {
> case 0: case 3: case 6: case 8:
> case 11: case 14: case 17: return 1;
> default: return 0;
> }
> }

The principal alternative (which I haven't seen
anyone mention yet) would be to use a table:

int is_leap(int year)
{
static const int answer[19] = {
1, 0, 0, 1, 0, 0, 1, 0, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0 };
return answer[ year % 19 ];
}

Whether this is better depends on how you define
"better." The tabular version may be more compact;
the switched version is easier to read.

Both versions can misbehave if `year' is negative:
The remainder `-3 % 19' can be either -3 or 16 under
C89 rules; under C99 rules the result is -3. Negative
years may be nonsensical given the application, but the
principle of coding defensively suggests you should be
wary. Three possibilities occur to me:

1) Make the `year' argument an `unsigned int', so
negative values can never appear.

2) Insert an explicit test for negative `year', and
take some appropriate action (error message?) if
you get one.

3) Replace `year % 19' with `(year % 19 + 19) % 19',
or with `(year %= 19 < 0) ? year + 19 : year'.

(There are other ways, but these seem to cover the major
themes.)

--
(E-Mail Removed)

user923005
Guest
Posts: n/a

 11-05-2007
On Nov 5, 1:15 pm, jxh <(E-Mail Removed)> wrote:
> On Nov 5, 11:10 am, (E-Mail Removed) wrote:
>
> > On Nov 5, 1:30 pm, (E-Mail Removed) wrote:
> > > I'm new here, so excuse me if my style is incorrect. Can
> > > anyone come up with a better method for this calculation?

> > Sorry, should have noted. This is to generate a Jewish calendar
> > (This is the year 5768, and therefore a leap year with an extra
> > month thrown in mid-March to mid-April, which is why Easter is
> > 3 weeks later this year than it was last year). The numbers in
> > the code are correct. I was wondering if there was any better
> > algorithm. Thanks to all who responded, and apologies for the
> > misunderstanding.

>
> int is_leap(int year)
> {
> return (0x00024949U & (1U << year%19)) != 0;
>
> }
>
> Although, I would recommend renaming the function to something that
> won't cause confusion if the application is ever combined with code
> that also deals with Gregorian.
>
> -- James

This page has two versions (including a version called 'rectified'
which aligns with GMT perfectly):
http://individual.utoronto.ca/kalend.../rect.htm#leap

pete
Guest
Posts: n/a

 11-05-2007
Richard Heathfield wrote:
>
> (E-Mail Removed) said:
>
> > I'm new here, so excuse me if my style is incorrect. Can anyone come
> > up with a better method for this calculation?
> >
> > Code:
> > int is_leap(int year)
> > {
> > switch (year % 19) {
> > case 0: case 3: case 6: case 8:
> > case 11: case 14: case 17: return 1;
> > default: return 0;
> > }
> > }
> >
> > This is part of a calendar program.

>
> Here's a better method:
>
> int really_is_leap(int year)
> {
> return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
> }
>
> This one has the merit of actually giving the right results.

The parentheses in the return statement
make no difference in the results.

--
pete

user923005
Guest
Posts: n/a

 11-05-2007
On Nov 5, 1:40 pm, pete <(E-Mail Removed)> wrote:
> Richard Heathfield wrote:
>
> > (E-Mail Removed) said:

>
> > > I'm new here, so excuse me if my style is incorrect. Can anyone come
> > > up with a better method for this calculation?

>
> > > Code:
> > > int is_leap(int year)
> > > {
> > > switch (year % 19) {
> > > case 0: case 3: case 6: case 8:
> > > case 11: case 14: case 17: return 1;
> > > default: return 0;
> > > }
> > > }

>
> > > This is part of a calendar program.

>
> > Here's a better method:

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

>
> > This one has the merit of actually giving the right results.

>
> The parentheses in the return statement
> make no difference in the results.

True, but they are useful as a sort of comment for people who are
unsure about precedence. For sure, the code generated will not be any
worse.

P.S.
There are gobs of Hebrew calendars on Sourceforge such as:
http://sourceforge.net/project/showf...group_id=63109

Christopher Benson-Manica
Guest
Posts: n/a

 11-05-2007
[comp.lang.c] Richard Heathfield <(E-Mail Removed)> wrote:

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

> This one has the merit of actually giving the right results.

How about for year 0?

--
C. Benson Manica | I appreciate all corrections, polite or otherwise.
cbmanica(at)gmail.com |
----------------------| I do not currently read any posts posted through
sdf.lonestar.org | Google groups, due to rampant unchecked spam.

Ben Pfaff
Guest
Posts: n/a

 11-05-2007
user923005 <(E-Mail Removed)> writes:

> On Nov 5, 1:40 pm, pete <(E-Mail Removed)> wrote:
>> Richard Heathfield wrote:
>> > int really_is_leap(int year)
>> > {
>> > return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
>> > }
>> > This one has the merit of actually giving the right results.

>>
>> The parentheses in the return statement
>> make no difference in the results.

>
> True, but they are useful as a sort of comment for people who are
> unsure about precedence.

The parentheses are not redundant--deleting them changes how the
expression parses. The precedence of && and || simply doesn't
matter in this case.
--
"I don't have C&V for that handy, but I've got Dan Pop."
--E. Gibbons

Rob Kendrick
Guest
Posts: n/a

 11-05-2007
On Mon, 05 Nov 2007 21:55:58 +0000, Christopher Benson-Manica wrote:

> [comp.lang.c] Richard Heathfield <(E-Mail Removed)> wrote:
>
>> int really_is_leap(int year)
>> {
>> return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
>> }

>
>> This one has the merit of actually giving the right results.

>
> How about for year 0?

There wasn't a year 0. The year after 1 BC was 1 AD.

B.

pete
Guest
Posts: n/a

 11-05-2007
user923005 wrote:
>
> On Nov 5, 1:40 pm, pete <(E-Mail Removed)> wrote:
> > Richard Heathfield wrote:
> >
> > > (E-Mail Removed) said:

> >
> > > > I'm new here, so excuse me if my style is incorrect.
> > > > Can anyone come
> > > > up with a better method for this calculation?

> >
> > > > Code:
> > > > int is_leap(int year)
> > > > {
> > > > switch (year % 19) {
> > > > case 0: case 3: case 6: case 8:
> > > > case 11: case 14: case 17: return 1;
> > > > default: return 0;
> > > > }
> > > > }

> >
> > > > This is part of a calendar program.

> >
> > > Here's a better method:

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

> >
> > > This one has the merit of actually giving the right results.

> >
> > The parentheses in the return statement
> > make no difference in the results.

>
> True, but they are useful as a sort of comment for people who are
> unsure about precedence. For sure, the code generated will not be any
> worse.

Those parentheses impose new precedence,
rather than emphasize the natural precedence of && and ||.

Part of what I was getting at, was that these expressions

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

have the same value.

--
pete

Keith Thompson
Guest
Posts: n/a

 11-05-2007
(E-Mail Removed) writes:
> I'm new here, so excuse me if my style is incorrect. Can anyone come
> up with a better method for this calculation?
>
> Code:
> int is_leap(int year)
> {
> switch (year % 19) {
> case 0: case 3: case 6: case 8:
> case 11: case 14: case 17: return 1;
> default: return 0;
> }
> }
>
> This is part of a calendar program.

You mentioned later that this is for a Jewish calendar. I'm not
familiar with the rules; does it really have 7 leap years every 19
years?

Assuming the algorithm is correct, the code looks decent. You should
think about the behavior for negative years; it may not be an issue,
but you should think about it.

Another possibility would be a table lookup, something like:

int is_leap(int year)
{
static const int leap_table[19]
= { 1, 0, 0,
1, 0, 0,
1, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0 };
return leap_table[year % 19];
}

Or you could use a bit array.

Either looks about equally good to me. In your code, I'd probably
line up the "case" keywords:

case 0: case 3: case 6: case 8:
case 11: case 14: case 17:
return 1;
default:
return 0;

or perhaps put one "case" on each line.

Given the regularity of the pattern, I suspect there's a fairly simple
integer arithmetic expression that avoids enumerating all 19 cases, or
all 7 cases, but I'm too lazy to figure it out, and for something this
small it wouldn't be a great improvement.

--
Keith Thompson (The_Other_Keith) (E-Mail Removed) <http://www.ghoti.net/~kst>
Looking for software development work in the San Diego area.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

jameskuyper@verizon.net
Guest
Posts: n/a

 11-05-2007
Keith Thompson wrote:
> (E-Mail Removed) writes:
> > I'm new here, so excuse me if my style is incorrect. Can anyone come
> > up with a better method for this calculation?
> >
> > Code:
> > int is_leap(int year)
> > {
> > switch (year % 19) {
> > case 0: case 3: case 6: case 8:
> > case 11: case 14: case 17: return 1;
> > default: return 0;
> > }
> > }
> >
> > This is part of a calendar program.

>
> You mentioned later that this is for a Jewish calendar. I'm not
> familiar with the rules; does it really have 7 leap years every 19
> years?

The months of the Hebrew calendar are lunar months, of either 29 or 30
days. Originally the length was determined by observation of the moon,
but eventually the average length was set to 29 days 12 hours 44
minutes and 3+1/3 seconds, the value that was published by Ptolemy in
the Almagest as the period of the Lunar month. It's about 3/5 second
longer than the current astronomical month. The solar year contains
more than 12 and less than 13 lunar months. The Hebrew calendar deals
with this by inserting an extra intercalary month in the years
identified by the above algorithm. These aren't really leap years as
the term is understood in the Gregorian calendar system. The Hebrew
calendar's year comes out to about 6 minutes and 25+25/57 seconds
longer than the current astronomical solar year.

All of this is from Wikipedia, with all of the usual caveats. However,
it is consistent with what I remember from more traditionally reliable
sources.

> Assuming the algorithm is correct, the code looks decent. You should
> think about the behavior for negative years; it may not be an issue,
> but you should think about it.

The world was supposedly created at or shortly before the start of the
first year of the Jewish calendar; it's possible that the people who
actually use it have little or no use for negative year values.