Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

The function round(n*x)

Status
Not open for further replies.

karluk

MIS
Nov 29, 1999
2,485
US
I'm not sure this merits a separate thread, but I would like to record my thoughts on the subject while they're still fresh in my mind.

Recall from Chris Miller's calendar thread that there are five different ranges of x which correspond to 11 month, 337 day calendars that differ only in the placement of the 30 day months. They are

30 13/22 <= x < 30 11/18 generates our actual calendar from March to January.
30 11/18 <= x < 30 5/8 generates a calendar with 30 days in April, June, September and December.
30 5/8 <= x < 30 9/14 generates a calendar with 30 days in April, July, September and December.
30 9/14 <= x < 30 13/20 generates a calendar with 30 days in April, July, October and December.
30 13/20 <= x < 30 15/22 generates a calendar with 30 days in April, July, October and January.

Furthermore, the transition points between these different calendars is always an exact fraction determined by when a 30 day month starts getting rounded up to 31 days. If the 30 day month is in position n on the calendar, then the fractional part of the transition point has denominator 2*n.
 
In mathematics the Farey sequence of order n is the sequence of completely reduced fractions with denominators less than or equal to n arranged in order of increasing size. The Wikipedia article is
Restricted to the range 13/22 <= x <= 15/22, the Farey sequence of order 22 is

13/22, 3/5, 11/18, 8/13, 13/21, 5/8, 12/19, 7/11, 9/14, 11/17, 13/20, 2/3, 15/22
 
Thanks, karluk!

I didn't know about the Farey sequence, now I even know how it can be related to our calendar.
I realize I knew Farey already, from Farey addition, which I know as Freshman addition.

Chriss
 
Yes, Chriss, I studied Farey sequences in college but have rarely used them since. It was fun to see them make a reappearance in this calendar problem, although I don't think knowing the theory behind Farey sequences is much help in analyzing the different possible patterns.

In any case, we can see by inspection that the terms of the Farey sequence with even denominators form the boundary conditions for generating the five distinct calendars possible with round().

What about the other terms? 3/5, 7/11 and 2/3 also mark transitions - the whole number values where a month goes from rounding up to rounding down. Naturally, because of the way round() works, there are no changes to the calendar at these transitions.
 
Some step functions really do change values on whole number values, and I was naturally curious to see what calendars they might produce. Take int(), the greatest integer function, as an example. Int(n*x) produces 11 month, 337 day calendars for 30 7/11 <= x < 30 8/11

The terms of the Farey sequence of order 11 in the range 30 7/11 <= x < 30 8/11 are

7/11, 2/3, 7/10, 5/7, 8/11

Using int(n*x) to generate calendars, I got the following:

30 7/11 <= x < 30 2/3 generates a calendar with 30 days in March, May, August and November.
30 2/3 <= x < 30 7/10 generates a calendar with 30 days in March, June, September and December.
30 7/10 <= x < 30 5/7 generates a calendar with 30 days in March, June, September and January.
30 5/7 <= x < 30 8/11 generates a calendar with 30 days in March, June, October and January.
 
The remaining option is to use ceiling() to round up to the next integer. Ceiling(n*x) produces 11 month, 337 day calendars for 30 6/11 < x <= 30 7/11

The terms of the Farey sequence of order 11 in the range 30 6/11 < x <= 30 7/11 are

6/11, 5/9, 4/7, 3/5, 5/8, 7/11

Using ceiling(n*x) to generate calendars, I got the following:

30 6/11 < x <= 30 5/9 generates a calendar with 30 days in May, July, September and November.
30 5/9 < x <= 30 4/7 generates a calendar with 30 days in May, July, September and December.
30 4/7 < x <= 30 3/5 generates a calendar with 30 days in May, July, October and December.
30 3/5 < x <= 30 5/8 generates a calendar with 30 days in May, August, October and January.
30 5/8 < x <= 30 7/11 generates a calendar with 30 days in May, August, November and January.
 
I wonder if that shows the calendar origin has distributed month length this way.

From the month names it's clear February actually was the last month of the year once, especially September-December point it out.
The paragraph "When was the first leap year" in mentions the Egytians used twelve 30-day months and added 5 extra days to the end.

The modern leap year definition goes back to 1752 and I know not all countries introduced the Gregorian calendar in the same year.

The 455 day long year of confusion mentioned, was the year before the Julian calendar was introduced on January 1sr 45 BC (of course there never were BC years at their time, 753 BC was the founding of rome. and so the introduction was 1st January 709 AUC. It didn't have the leap year every 400 years, but that was its only difference from ours.

I wonder if the Julian calendar that also was changing the first month of the calendar, maybe the 455 days point to that shift of momths? 455 days would be about 15 months. But when the calender previous to the Julian calendar had Maarch as first month, this would shift the begin of the year from March to June, not to January. I think you have to dig deeper into calendar history to find out.

You rarely will find a PC calendar correctly going from Gregorian to Julian calendar prior to the year of adaption, which differs from country to country, they all extrapolate the calendar back. Using the signed int or signed long int with the middle at midnigth 1st Jan 1970 you get invalid dates for 1752 and before and surely for 45 BC.

Chriss
 
@Chris Miller
I don't know how authoritative the Wikipedia article on the Roman calendar is, but it addresses most of the issues you bring up:
In brief, the calendar that preceded the Julian calendar alternated between years that were much shorter than 365 days and years that were much longer. Wikipedia says that the intent was to create a 24 year cycle of thirteen 355 day years, seven 377 day years and four 378 day years, with an average length of 365.25 days per year over the entire 24 year cycle. The so-called "year of confusion" in 46 B.C. wasn't really confusing at all by the standards of the time - it was just an unusually large adjustment. Switching the beginning of the year to January was an earlier change that had nothing to do with the adoption of the Julian calendar.

The Julian calendar very wisely preserved the average length of the year at 365.25 days by distributing 10 extra days to the short 355 day year and giving February an extra day every four years. So we have the creators of the Julian calendar to thank for our current "30 days hath September..." pattern of 30 and 31 day months. The calculations I made earlier in this thread show that they made very good choices in distributing the 30 day months in a balanced manner - not the only way to balance things, but there's no clearly superior method.

You are right that a lot of commercial software products do a very poor job with date calculations. I personally became aware of a really ugly bug in Oracle when I used it to do some research on the Mayan calendar. See thread1177-1346513
 
Nice, thank you. I read a lot on Wikipedia about it, too, but mainly about the Julian and the Gregorian calendar.

I also think you would need better historical reference about calendar reforms to proof. At least Google doesn't present any random blog or homepage about the topics. The britannica says January replaced Marchas first month, indeed. ends in stating there is evidence it was not offical until 153 BCE. But without referring to the evidence.

Chriss
 
As far as I can see, my earlier calculations are complete. So there are only 14 different 11 month, 337 day calendars that can be generated from round(n*x) and the related step functions int(n*x) and ceiling(n*x). This confirms my original gut feeling that such calendars are unusual. There are

(11*10*9*8)/(1*2*3*4) = 330

ways to distribute four 30 day months in an 11 month calendar and only 14/330 or about 4.2% of them can be generated in a plausible way through multiples of step functions.

I no longer feel, however, that our current calendar being one of the 14 is a "remarkable" coincidence. If anything, I would now call it more likely than not. All of these 14 calendars balance the occurrences of the 30 day months, so that there are never too many 31 day months before a 30 day month follows. This sort of balance is clearly something the inventors of the Julian calendar would have tried to achieve.

If I may make an editorial comment, if I had been Julius Caesar and had to choose among these 14 options, the only choice I would seriously consider over the one that was actually adopted is the one generated by ceiling() which has 30 day months in May, July, September and November. It is the only option which doesn't contain any three month sequence of less than 90 days. This improves on our current calendar in which February, March and April usually have only 89 days.
 
Up until now I have been avoiding 12 month calendars because round(n*x) would need to be modified to produce a 28 day February. However, I was interested in seeing which 30 day month patterns round(n*x) would generate. So I calculated round(n*x) for a 12 month, 367 day calendar. That would generate balanced calendars with five 30 day months, which of course would immediately get out of balance by needing to whack off two days from February to get down to 365 days.

Note: My calculations for 11 month calendars all began with March as the first month. For the 12 month calendars I began with January.

Using round(n*x) on a 12 month 367 day calendar, I got the following.

30 13/24 <= x < 30 11/20 generates a calendar with 30 days in February, April, June, August and October.
30 11/20 <= x < 30 9/16 generates a calendar with 30 days in February, April, June, August and November.
30 9/16 <= x < 30 7/12 generates a calendar with 30 days in February, April, June, September and November.
30 7/12 <= x < 30 13/22 generates a calendar with 30 days in February, April, July, September and November.
30 13/22 <= x < 30 11/18 generates a calendar with 30 days in February, April, July, September and December.
30 11/18 <= x < 30 5/8 generates a calendar with 30 days in February, April, July, October and December.

Our current calendar can be obtained from the 30 9/16 <= x < 30 7/12 entry by subtracting two days (one day in a leap year) from February.
 
So, round(n * 30 9/16) produces a twelve month year that matches a non leap year Gregorian year except that February has 30 days, not 28. There must be any number of ways to bring February down to 28, but the thematic way is to use step functions, in this case int(). int((n+10)/12) changes value in February, so

round (n * 30 9/16) - 2 * int((n+10)/12)

produces a 28 day February while leaving the length of the other months unchanged.
 
Now that we have a simple algebraic expression for a non leap year Gregorian year, it is extremely tempting to try to extend the algebra to be able to express any Gregorian year whatsoever.

My calculations indicate that this can be done. One expression that works is

round ((n * 30 9/16) - 2 * int((n+10)/12) + 1/4 * int(n/12) + int((n+10)/48) - int((n+10)/1200) + int((n+10)/4800))

This expression is unfortunately nowhere near as elegant as round(n*x), but each additional term has, I believe, a clear purpose which makes it easy to understand:

-2 * int((n+10)/12) produces a 28 day February.

1/4 * int(n/12) is a numerical adjustment so that the math produces regular years of exactly 365 days and leap years of exactly 366 days.

int((n+10)/48) produces a leap year every four years.

-int((n+10)/1200) takes away the leap year every hundred years.

int((n+10)/4800) adds back the leap year every 400 years.
 
You actually don't need a solution for n=12, the days in February are given, I'll post in my own thread in spoiler tags, so you see.

On the other side, it's fine to have a simple formula for a full year, it'll be able to check a date for being valid, but the universal thing about converting a date to a number is to be able to calculate differences, you can also check a date if you have the reverse calculation and then get back to the same date. Say someone enters 29th of February 2022, which doesn't exist, you compute a day number and then reverse it to the date 1st March 2022 and see the date is wrong.

Chriss
 
Allow me to clarify the formula in my previous post. What I posted is the method I used in a spreadsheet - add up all of the terms and then apply round() to get to the nearest integer. However, all but two of the terms are already integers and don't need to be inside the round() function. So the following expression should be equivalent, and is perhaps clearer:

round ((n * 30 9/16) + 1/4 * int(n/12))
- 2 * int((n+10)/12) + int((n+10)/48) - int((n+10)/1200) + int((n+10)/4800)
 
I also looked into using step functions to generate balanced month distributions for a Martian calendar. I won't record my results because the issues didn't seem to be particularly complex. There is an average of 668.5907 sols per Martian year. The natural way to handle this is to have a mixture of 668 sol regular years and 669 sol leap years. If the 668 sol regular year is divided into a 24 month calendar, there are twenty 28 sol months and four 27 sol months. It is hard to justify distributing them in any way other than one 27 sol month every six months. Indeed, this is the way the proposed Darian calendar does it.

Wikipedia said:
The year is divided into 24 months. The first 5 months in each quarter have 28 sols, while the final month has 27 sols unless it is the final month of a leap year, when it contains the leap sol as its final sol.


Unsurprisingly, this layout can be generated with step functions, specifically ceiling(n*x) in the range

27 19/23 < x <= 27 5/6
 
I promised I post something in my thread, I still haven't yet.

But I found another reason for the month lengths that also explains the factor 30.6: Starting in March up to September, a 5 months period (like March to July, April to August,..., and September to January), sums to 153 days, which is exactly 5*30.6.

In October that's broken, because 5 months starting with October include February. Or said the other way around: (Almost) every period of 5 months including February doesn't sum to 153. Periods containing a leap year February total to 152,152,[highlight #FCE94F]153[/highlight],152, and 152 (there you have the exception). Periods including a normal February are, of course, one day shorter.

It seems a bit oddly balanced, since it better fits into leap years than into normal years. I also found calculations using 30.6001, which addresses floating point imprecisions. Maybe with single precision floats. I also get the right sequence with a factor 30.59375 using doubles, which is even lower than 30.6 - to state the obvious.

I thought I mention the 153 day fact for you contemplating about a another reason the simple step function succeeds covering the lengths of 11 months. Pardon me if you already observed that and I overlooked it. I think somewhere here or there you mentioned the product is exact. It's now easy to spot, both 153 and 306 are in the number series.

Chriss
 
Interesting observation about all of these consecutive stretches of 153 day, five month periods, Chriss. It's hard to say if it played a significant role in picking the April, June, September and November spacing of the 30 day months. Perhaps someone back in the day noticed it and thought it was way too cool of a pattern to pass up. At the least it goes a long way towards explaining why round(30.6*n) is such a nice fit with the March-January calendar.
 
By the way, Chriss, I think you're right that these five month, 153 day sequences are related to my earlier post about "3/5, 7/11 and 2/3 also mark transitions - the whole number values where a month goes from rounding up to rounding down." Your result is stronger than what I posted, but it looks to me as if one needs this sort of whole number transition to have any chance to have this type of sequence.

So the natural thing to do is look at the other whole number transitions - 7/11 and 2/3. 7/11 doesn't work, but 2/3 does. Round(n*(30 2/3)) generates an 11 month calendar with 30 day months in April, July, October and January. Every three months sequence (except the ones including February) using this calendar contains exactly 92 days. Very interesting, but the creators of the Julian calendar apparently didn't prioritize having the maximum number of 92 day, three month stretches.
 
I always think of accounting or taxes working in quarters. Well, three month stretches, as you say. Maybe 5 month stretches played a role in ancient administration. And not just 5 month stretches that follow each other, but that overlap, a moving 5 month window.

I faintly remember some economists once proposed the fluctuation of month lengths are a problem and a calendar reform could optimize that. The idea of a 13 month calender with 4 week months each, plus some extra days, would still keep the concept of quarters as they would simple be three 4 week months and a week, in short 13 weeks quarters. Quite easy to see because a quarter of each month would be 1 week, so a quarter of a 13 month year with this (almost) constant month length is 13 weeks.

The other idea behind this is that you will hardly be able to break the 7 day week cycle, but straightening the month length could be a real benefit.

My major intention rather is a more complete calendar implementation, including more calendars in use around the world.
Simply the fact new Date(year, month, day) in JS works with a 0 based month index 0-11 instead of the usual 1-12 bugs me. I'm not against 0 based counting, but then why only the month and not the days? And who named the method returning the day datepart as getDate(). I understand that's because getDay() is reserved to return a week day number 0-6 for Sunday to Saturday.

I think it's no wonder many JS libraries about the topic of calendars exist.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top