A Little Of What I Do For A Living
[Major Geek Alert…]
Some of you may have read the news items about Microsoft’s Zune player freezing up on its users last December 31st. The problem it turned out, was in a bit of software that calculates how many days since January 1st the current date is. I’ve no idea why the Zune’s software needed to do that, but it isn’t important to what I’m about to show you. I have fun doing the work I do, in a techno geeky kinda way, and I want to share a bit of that fun with you.
The code that caused that particular bug was leaked out into the wild. Here’s the relevant fragment:
while (days > 365)
{
if (IsLeapYear(year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}
Don’t panic…it’s just code. Code is to a computer program what a chart is to music. It’s not so much the program, as instructions for how to create the program. It’s more human readable then the machine language code microprocessors digest, although that might seem a tad hard to believe if you’re seeing code here for the first time. It’s a kind of highly structured syntax that is precise enough to describe, step-by-step, a series of actions the computer needs to perform. That series of actions is called an ‘algorithm’.
An algorithm is a series of steps needed to perform a specific task in a specific time. So for example, consider the steps necessary to bake a single cake. Those steps constitute an algorithm because they perform a specific task in a specific time. The task is baking a cake. When the cake is baked you are done. Note that a specific time isn’t necessarily a specific amount of time. The important thing is there is an end to it somewhere. The steps needed for a cake factory to make ‘cakes’ is not an algorithm because there is no defined end to the task of baking cakes. It could be one cake or many. But you can repeat the algorithm for baking a single cake as many times as you like, once you have it defined.
Writing computer programs is essentially the art of creating well defined, simple, straightforward algorithms. If you’ve got a head for that, the rest is a matter of mastering a particular programming language or more. The code fragment above is in a language called C++. Never mind why it’s called that and not something more warm and friendly like Fred or Ethel. Computer geeks are weird like that.
This code fragment is from a larger bit of code that tries to determine the number of years the current year is from the year 1980, and the number of days since January 1st. Never mind for now Why. Just focus on the task: to get the number of years since 1980 and the number of days since January 1st.
The function this code fragment lives in receives the current date in the form of the number of days since January 1, 1980. This seems odd, but it is how computers tell time. At the most basic level, they are merely counting fractions of seconds from a given starting point. Consider that a mechanical wrist watch (like the one I wear) tells the time only by counting ticks. If you know how many ticks there are in a second, then you can compute the second, the minute, and the hour by counting the number of ticks and that is just what a mechanical wrist watch does…mechanically. Computers do pretty much the same thing electronically, but their ticks are much smaller, and far more precise.
We know there are 365 days in a normal year. So if we get a number that’s, let’s say 10220, we might just divide that by 365 to get the number of years that have passed. But the added factor of having leap years makes it less simple then that.
Now let me try to make some sense of that C++ code for you. As I said, it’s a highly structured syntax that precisely describes the steps a computer program must perform. Just ignore the brackets…they’re just there to mark off specific sections of the code. Don’t worry about them.
At the beginning of the code you see the word "while". This is a Keyword in the C and C++ languages and it denotes the start of a program Loop. A loop is a series of steps that are repeated. They are very useful for repeating a series of steps over and over as just a few lines of code instead of one or more lines repeated exactly for every time the steps need to be executed. If, say, you had to do something a hundred times you would write the code to loop through the same steps a hundred times, rather then writing the same steps a hundred times in the code. If the steps change, then that’s a hundred changes you need to make. If you’ve written it as a loop you only need to change it once.
Loops are also helpful if you don’t know ahead of time how many times you might have to repeat a particular set of steps. In the cake baking example for instance, you might have to stir some ingredients until they are mixed properly. If you were coding that, you’d write it as a loop where you stir the mix, and then test to see if it’s mixed well enough to stop. If not, stir once more. Test…stir…test…stir… And so on until the the test says you can stop stirring now.
That test is important. It tells you when you can stop stirring. For now just hold this thought: it is important to have a way out of a loop.
The keyword "while" has a test enclosed in the parenthesis next to it: (days > 365). This test compares the variable "days" against a literal value of 366. Think of a variable as a post office box with a name on it, and something inside. In this case, the variable is named "days" and it holds a number that represents a given number of days. This variable is set elsewhere in the code and we don’t need to know why or how at the moment. We’re just looking at what this one bit of code does.
The ">" symbol is an Operator in the C and C++ languages, which means "greater then" If "days" is "greater then" 365 then the next lines of code are executed. This test is at the beginning of the loop, which means the condition is tested first before any of the code in the loop is executed. If the test is true, the loop is entered. If not, the loop is never entered. So the loop takes a value for a number of days at its very beginning. If that value is greater then 365, the rest of the loop executes. If it isn’t, the loop is skipped over. Think of it as saying "while the value stored in "days" is greater then 365, do the following…"
So we enter the body of the loop. The next line is "if (IsLeapYear(year))" Let me unpack that. The word "if" is another keyword, and it denotes a logical test. You are testing if something is, or is not true. The part in the parenthesis is the thing you are testing. IsLeapYear(year) is a function call with its own set of parenthesis. Functions are bits of code that return a value. This particular function returns a value of either true or false. We don’t need to see how this particular works for this example…just that it will return either "true" or "false" back to our "if" test. The word "year" in its parenthesis is another variable and it holds a number that represents the number of years since 1980.
So we are passing in to the function IsLeapYear a number, and it returns either true or false depending on whether or not the number we give it, translates into a leap year. Remember, we’re counting the number of years since 1980. Lets say we make "year" equal to 3. We could as easily write the call as "IsLeapYear(3)", and it would return false, since 1980 plus three years is 1983 which was not a leap year.
Okay…still with me?
An "if" test tests a condition, and the lines of code following the test are either executed or not depending on whether or not the test passed or failed. If IsLeapYear(year) returns true, then the next line is executed.
The next line is another "if" test. if (days > 366). This test compares the variable "days" against a literal value of 366. It is like the test at the beginning of the loop. If "days" is "greater then" 366 then the next lines of code are executed.
These next lines actually do something. the line "days -= 366" means "take the value that’s stored in the variable named "days", subtract 366 from it and store the result back in that variable. The line "year += 1" means "add one to the value stored in the variable named year and put the result back in that variable".
A couple brackets on down (I told you to just ignore them) there is the word "else" It is another keyword that works with the keyword "if" to denote lines of code to be executed if the if test above fails. So in other words, if IsLeapYear(year) returns false, then the steps following the word "else" are performed. Think of the whole thing as "if it’s a leap year do this…if it isn’t (else) do that…" In this case, that is "subtract 365 from the value of the variable named days", and "add one to the value of the variable named year".
So…still with me? This is what the code is doing. The algorithm it embodies is this:
1) Repeat the following for as long as the value of "days" is greater then 365:
2) Check to see if "year" is a leap year.
3) If it is a leap year, check to see if the value of "days" is greater then 366.
4) If it is, then subtract 366 from "days" and add 1 to the value of "year"
5) If "year" isn’t a leap year, then subtract 365 from "days" and add 1 to the value of "year"
There is our loop. Basically, it is taking a number that is the number of days since 1980, and subtracting 365 days for every normal year, 366 for every leap year, and when it finishes you should have a count of the number of years since 1980, and what’s left over is the number of days since January 1st. Simple…no?
The bug in it is subtle. Let’s run through it for December 31, 2007. Lets say we have run this loop for a while and now we have a value of 26 in "year". The value of "days" is 730.
1) The value of "days" is greater then 365…so we do the loop again.
2) Check to see if "year" is a leap year. 26 years since 1980 is 2006. 2006 isn’t a leap year. So the code following the "else" keyword is executed.
3) We subtract 365 from "days" and add 1 to the value of "year"
4) We’re at the beginning of our loop again. The value of "days" is 365. 365 is not greater then 365. So the condition for continuing the loop is now false. So now we can exit the loop.
5) The end values are, year = 27, days = 365.
Okay. That works. But now let’s try it for December 31, 2008. Lets say we have run this loop for a while and now we have a value of 27 in "year". The value of "days" is 731.
1) The value of "days" is greater then 365…so we do the loop again.
2) Check to see if "year" is a leap year. 27 years since 1980 is 2007. 2007 isn’t a leap year. So the code following the "else" keyword is executed.
3) We subtract 365 from "days" and add 1 to the value of "year"
4) We’re at the beginning of our loop again. The value of "days" is 366. 366 is greater then 365. So the condition for continuing the loop is still true.
5) Check to see if "year" is a leap year. 2008 is a leap year. So the code following the "if" keyword is executed.
6) Check to see if the value of "days" is greater then 366. 366 isn’t greater then 366, it’s equal to 366…so the code following the "if" keyword is not executed.
7) We’re at the beginning of our loop again. The value of "days" is still 366 and the value of year is still 28. 366 is greater then 365. So the condition for continuing the loop is still true.
10) Check to see if "year" is a leap year. The value of year wasn’t changed by the last run through the loop. It is still 2008 and 2008 isn’t a leap year. So the code following the "if" keyword is executed.
(can you see this thing starting to run away now…?)
11) Check to see if the value of "days" is greater then 366. 366 isn’t greater then 366, it’s equal to 366…so the code following the "if" keyword is not executed.
12) We’re at the beginning of the loop again…
And that’s where we will keep on ending up until the heat death of the universe, or the Zune’s battery dies, whichever comes first. This is why the Zunes all locked up on December 31st, 2008. The code works fine during a normal year, and on every day but the last day of a leap year. But on the last day of a leap year that loop will run indefinitely, because there is no way out of it on that one day.
Since this code was leaked out into the wild, everybody who does this for a living has an opinion on how to write that algorithm better. There is a kind of fine art and a pure pleasure to some of us in crafting tight, simple, elegant algorithms and some folks have their own deeply held religious beliefs on how to do it best. I haven’t had time to really wrap my head around what this algorithm is doing, but for kicks and grins I might try to write a better version of it myself later. It’s kinda fun to take something like this and try to craft something simple and clean and so logically pure it’s beautiful just to look at. But I’m in a testing and deployment phase of the project I’m one at work now though, and what went through my head when I saw this was they obviously didn’t test how it behaved during a leap year.
This is the world I live and work in. This is what programming is and what programmer’s do. We build these tight little algorithms and embody them in computer code that hopefully allows you to get things done. Except when they don’t.
[Edited a tad to explain the test at the start of the loop, and make some of the rest of it clearer…]