Monday, April 18, 2011

CS: Loops and conditionals, pt. 2

Sorry this post is coming a bit later than I originally intended. Let's briefly review what we talked about last time before diving into the new material.

Loops and conditionals allow us to control which instructions the computer processor is going to execute and when. Loops let us execute some instructions again, even after we already have executed them. Last time we saw the example of a while() loop that executed the code to add 1 to x and store it in x, and this loop executed 10 times.

Today we're talking about using if() statements to decide if we want to skip over a block of code. If the conditional statement inside the if() statement is true, then the block of code that follows the if() statement is executed. If the conditional statement inside the if() statement is false, then the block of code that follows the if() statement is skipped (it is not executed). Look at this code snippet:

x=1
if(x>2)
{
x=0
}

After the execution of this example the value of x will still be 1 (the value that was set at the beginning of the example). Since x>2 is false (because x is 1, and 1 is not greater than 2), we skipped over the assignment statement that would set x to 0 (x=0). Let's look at an example where the conditional statement evaluates to true:

x=4
if(x>2)
{
x=0
}

Here x starts out as 4, but since 4>2 is true, we execute the block following the if() statement. This block tells us to set x to 0. So at the end of this code snippet, x's final value will be 0.

We can see that if() statements allow us to skip blocks of code that we would only want to execute if the conditional statement of the if() statement is true. Let's look at a more complex example that combines a while() loop, an if() statement and a break:

x=0
y=0
while(x+y<10)
{
if(x<5)
{
y=y+2
}
if(y>4)
{
break
}
x=x+1
}

This example has if() statements inside a while loop. Let's walk through what happens here. First of all, both x and y are set to 0, and then we look at the while loop. Since x+y=0 right now, and 0<10, we will execute the while loop at least once, so let's jump into it. The first thing we run into inside the while loop's block is an if() statement, which asks if x is less than 5. This is true right now, so let's execute that if() statement's block, which increases y by 2. At this point, x is still 0 and y is now 2. Now we look at the next if() statement, which asks if y is greater than 4. This is not true, so we'll skip that block for now. Next we add 1 to x and store the result in x. We've now gone through the loop one time, and x is 1 and y is 2.

What do you do when you finish a run through a while() loop? Go back to the top and test to see if you need to do it all over again. We go back to the top and test x+y<10. Right now x+y is 3, which is definitely less than 10, so we should execute the while() loop's block again. Again we run into the if(x<5) statement, and again this is true, so we execute y=y+2, so now y is 4. Next we test to see if(y>4). Since y is exactly equal to 4, it is *not* greater than 4, so this conditional statement is still false, so we skip the contents of that block. Finally we execute x=x+1, and we're done with our second run of that loop. At this point, x=2 and y=4.

We again go back to the top of the loop, x+y is still less than 10, so we execute the loop one more time. if(x<5) is still true, so we execute y=y+2 again, making y equal to 6 now. This time when we execute the if(y>4) statement, y *is* greater than 4, so we execute the break statement. A break statement means "you're done with this loop now, so exit it immediately without doing anything else on your way out." After executing the break we're now outside the loop and we're done with our code snippet. The final score has x=2 and y=6. Notice that if it weren't for the break statement, since x+y<10 is still true we would still be going through our while() loop until such a time as when x+y is greater than 10.

You might need to read through the examples here a few times. It's substantially more complicated than anything I've presented lately, so you should take your time and go slowly. Make sure you get it.

Next time I'm probably going to talk more about consumer advice. Some time in the future I'll talk a little bit about electricity, just because someone was curious, but I remind you that I am not really qualified to explain it. Good luck to me writing that and you reading that.

Tuesday, April 12, 2011

CS: Loops and conditionals, pt. 1

When I set out to write this series of articles, I wanted to help people who use computers, but don't understand what's going on under the hood, understand what's going on under the hood. Before the reset a couple weeks ago I talked about binary math, computer memory, and branching, in that order. This time around I've skipped binary math, I've talked about memory from a high, abstract level, and now I'm going to tackle branching/looping again. Instead of presenting simple programs as short assembly instruction sequences like I did last time, I'm going to take the easy way out and use a format that might resemble a regular programming language. With that out of the way ...

Everything a computer does can be broken up into individual, small and distinct operations called "instructions." Instructions are simple units of work, and a single instruction might say "load the variable at this memory address into the processor," or "add these two variables together and store the result in this other variable." These instructions are stored in memory and the processor tackles these in the order they appear in memory (for example, it executes the instruction at memory address 200 before it executes the instruction at address 201, and both of these are done before the address at 202, &c.).

Performing instructions in order is useful because it lets the programmer have guarantees about what the processor will do when it executes the program (he knows it won't just randomly execute whatever it feels like). The immediate downside is that if the computer keeps executing instructions in a straight line, there's no way to re-execute old instructions again, or to skip instructions maybe you don't want to execute right now. Both of these problems are solved via looping and conditional execution.

First a bit of vocabulary. I'm going to refer to the endless, ordered list of instructions the processor can execute as the "instruction stream." In this context, "looping" means to repeat an earlier part of the instruction stream (perhaps many times) and "conditional execution" means that you *maybe* will execute the next part of the instruction stream, or you might skip it.

Next we need to be familiar with some computer language jargon and notation relating to loops and conditionals. Here are some more vocabulary words to remember:

conditional statement - This is a question that you can ask about the current state of the program and its variables that you can either answer with "true" or "false." For example, you can ask "is variable X is greater than 10?" As a conditional statement that would be in a real program, this would appear as "X>10" (note there is no question mark, and we're using the "greater than" symbol). If X is greater than 10, this will mean "true," and if X is 9 or less, then this will mean "false." It's conditional.

while() - Yes, the parentheses are included in this vocabulary word. Inside the parentheses goes a "conditional statement." If the conditional statement is true, then you perform some loop as long as the conditional remains true. If the conditional was never true (i.e., always false), then you skip over the while-loop altogether.

if() - Yes, again the parentheses are included, and there's another conditional statement in there. This is really similar to a while-loop, except instead of repeating what comes after this over and over as long as the condition is true, you execute it only once if the condition is true, or zero times if it is false.

{} - These braces let you know that all of the instructions between them are grouped together. This is called a "code block" ("block" as in a block of a street, where all of the houses on that block are neighbors). This group of instructions is what gets repeatedly executed in a loop, or what might skipped over entirely if an if-statement evaluates to false.

break - this special command lets you get out of a while loop early. It usually shows up right after an if-statement. We'll see one of these in action in the next article.

Before I end this article I want to show one example of a simple while loop that is executed 10 times (I parenthetically explain each line. Don't get confused by too many parentheses):

x=0 (this means we're making the variable x hold the value 0)
while(x<10) (execute this loop as long as x is less than 10)
{
x=x+1 (this means we're taking whatever x currently holds, adding one, and making x hold that new value. Essentially we're increasing the vale of x by 1)
}

Variable x starts out at 0, and 1 is added to it 10 times. This is a while-loop that loops around 10 times (or 9 if you don't count the first time through as a "loop"). Notice the braces, also. This time there was only one instruction inside the braces, but there could have been more. If there were more, they would each be on their own line, and each would get executed in the order it appears. When we loop, we go back to the very beginning of the loop, to the very first instruction if there is more than one in the code block.

That's it for this time. Next time, I'll show an example using if-statements, and then one big example that incorporates a while-loop, if-statement, a code block that's bigger than 1, and a break command.

Friday, April 8, 2011

CONSUMER ADVICE: How much RAM?

When buying a new computer, the single most important thing for the average consumer to consider is how much RAM their computer will have. RAM is short for Random Access Memory, but that is neither here nor there--most people don't need to know/care about that. What RAM means to the typical consumer is that more of it lets your computer "feel faster." Well, up to a point.

I suppose this requires a little bit more explanation. In computers there are two types of data storage that make the biggest difference to consumers--hard drive space and RAM. The hard drive holds your permanent data that will stick around long after you've turned your computer off (it will still be there when you turn it on the next time, too). The hard drive holds your photos, music, movies and programs, any and all of which you *can* use whenever you want. The more hard drive capacity you have, the more stuff your computer can permanently store. Hard drives have incredible capacity but they are very, very, very slow. They are slow because they have mechanical moving parts that have to physically spin around to access your data. It's a tradeoff.

RAM, on the other hand, contains what your computer *is* working on *right now*. It's like temporary storage for your computer's current needs. Unlike a hard drive, RAM has no moving parts and its speed is only limited by the speed that electricity can flow through wires and that logic chips can act fast enough to control it. In other words, it's way faster than a hard drive. Unfortunately, it also has much less capacity than a hard drive. This low capacity can be a problem.

If you are running a heavy workload on your computer, it's possible that not everything you're working on will fit inside your RAM. If this happens, then some of what you're working on will spill over to your hard drive (which will definitely have enough space). This spilling over effect makes your computer *feel* very, very, very slow because your hard drive *is* very, very, very slow. This is what is going on when most people say their computer "feels slow." It feels slow because it *is* slow.

So what's the solution? The solution is to buy *enough* RAM. "Enough" is the key word here. You need to buy enough RAM to fit all of the programs and movies and photos you want to work on at the same time without spilling over into the hard drive. Buying any more than just "enough" RAM will probably be a waste of money, because that extra RAM will go unused. But how much is "enough?" Well, I can't tell you exactly how much you personally will need, but I can give you some good general guidelines that should suit the Average User.

If you are buying a new computer right now (April 2011) I would recommend to not get less than 3GB (giga bytes) of RAM. Any less than this and you will probably run into that "slow feeling" occasionally during normal use. 4GB of RAM would be even better, but if you are buying a system that you want to last more than 2 years, I would recommend getting a computer with 6GB or 8GB of RAM. The good thing about buying more RAM is that it's a pretty cheap upgrade that can go a long way toward making your computer "feel fast." Buying 12GB or more is probably overkill at this point in time, but if the price is right or you expect you will use it (like if you are a graphics or movie editing professional) then it's probably worth it.

Of course there are many aspects to a computer that consumers need to be aware of when choosing what to buy, and I've only scratched the surface here. I feel like I did start with the most important aspect, though. Buying "enough" RAM (whatever that means) is the best thing you can do for making your computer feel fast, and is honestly the first thing you should look at on a specifications sheet when shopping for a new computer. Don't skimp!

Monday, April 4, 2011

CS THEORY: Variables and memory

The last time I used the tag "CS THEORY" I didn't really talk about CS theory at all, did I? All I talked about is that there are memory cells and they have numbers in them. That's fine, but it didn't quite make it into the realm of CS theory. In order to do that, we need to talk about what a memory can DO, not just what it is.

As far as "what it is" goes, it's just as I've already said. It's a giant list of cells, each of which can each hold a number. That's not very interesting. What's more interesting is how the numbers get there, and what happens to them once they are there. There are two interesting aspects of memory cells that I'm going to talk about today.

First, memory cells are useful because they each have a unique identifier, and that makes it possible to not get mixed up about what numbers you're storing where. In the memory of a computer, each memory cell has a unique number associated with it called an "address." In a typical computer today its memory will have addresses 0 through about 4 billion. This means there are 4 billion different memory cells where you could store a number. Every time you talk about memory cell 2,361 you're going to be talking about the same memory cell. It didn't go anywhere since the last time you used it.

Second, memory cells in a computer are useful because they are reliable. If you put a number into a memory cell, it will stay there forever, or until you put a different number there. You don't need to worry about the contents of the memory cell accidentally disappearing. That doesn't happen. If it did randomly happen that your data could just disappear, then you wouldn't have a very reliable or useful computer. You couldn't trust it to do anything correctly. Computers are built upon the assumption that this doesn't happen, and that you can always get out of a memory what you put in earlier.

These two properties of computer memory allow us to do real work with our computers. In programming languages that are used to program computers, we often use variables to represent memory cells. Instead of talking about "memory cell 2,361" the programmer will just type "X" (or any other name he dreams up) and it means the same thing. I just pulled the number 2,361 out of the air. It doesn't matter what the address is.

In fact, the programmer doesn't even need to ever know what real address variable X refers to. The programmer can just logically reason about made up variables like X and Y and Z, and while ultimately in the computer these variables will stand for memory cell addresses, the programmer doesn't ever need to think about that. The programmer can type "Add X and Y and store the result in Z," and that might get translated by the computer into "add the contents of memory cell 2,361 and memory cell 7,841 and store the result in memory cell 451." This is called abstraction. The programmer thinks about pretend variables and lets the computer worry about how it will physically carry out the math the programmer has asked it to do by manipulating real memory cells that are physically somewhere in the computer.

Today we talked more about computer memory, how every cell in a computer memory has an address, and how what you put into a memory will stay there and won't just disappear on you. These properties make it very convenient to write computer programs, even without having to know how memory cells really work. Programmers can just abstract away all those complicated details and play with raw, abstract variables instead.

I think for my next post I will give some consumer advice. Next time we talk about CS theory I think I will talk about C-like assignment statements, conditional statements and loops.

Thursday, March 31, 2011

CS THEORY: Memorable numbers

See, I told you this would be the title of the next article.

I've decided to start with something that is more fundamental to computer science, and less fundamental to computer architecture. I think talking about the concepts behind computer memory from a theoretical, mathematical kind of perspective will be more valuable to more people when starting out than talking about binary mathematics. So for now, I'm not going to talk about binary numbers (although you're welcome to imagine all the numbers I do talk about as such if you like). Instead, I'm going to talk about regular numbers and memory cells.

I kind of struggled with how I wanted to explain computer memory this time. Last time I talked about a lineup of bathtubs filled with binary digit buckets. That was pretty weird, in retrospect. Let's move away from that and talk instead about regular numbers and boxes that can hold those numbers.

Imagine a spreadsheet. Imagine the giant grid that extends on forever to the right and down, with the alphabet along the top naming each of the columns, and the numbers along the side naming the rows. Now, erase columns B-infinity, so we're left only with column A. This is a pretty good way to visualize computer memory. A single, numbered list of boxes that can each hold a number. It doesn't really make a difference whether you imagine this numbered list of boxes as being vertically or horizontally oriented, but for the sake of space efficiency, I'm going to write it out as a horizontal row of boxes of numbers. Here it is:

[_] [_] [_] [_]

Imagine that as 4 boxes, none of which currently has a number in it. I just got tired of typing those out after 4 boxes, but in real computer memory there are billions. Yeah, billions. That's a lot. One of the things about these memory boxes, or "memory cells" as I'm going to call them, is that they *need* to hold numbers. They can't actually be empty. So let's put some numbers in there:

[83] [101] [116] [104]

Pretty good numbers, right? Let's list some things we could do with those numbers. Well, we could add two of them together, we could multiply two of them together, we could do any kind of math on them that we want. Those are pretty predictable uses of numbers. One thing about computers, though, is that the computer doesn't care what we do with those numbers or what we pretend those numbers mean (unless we ask the computer to do math on those numbers, then the computer will treat them as numbers and do the math correctly).

In fact, those numbers I listed up above are not totally random. There is a way of pretending that numbers are actually letters and symbols called ASCII that says that those numbers I chose up there actually stand for something else. According to ASCII, the 83 stands for 'S', the 101 stands for 'e', the 116 stands for 't' and the 104 stands for 'h'. So [83] [101] [116] [104] can actually stand for "Seth" (that's my name, by the way).

Now to relate this to the computer you're reading this on right now. Your computer has billions of memory cells, each with some kind of number in it, and those numbers can mean things that humans can understand, like letters we can read, or colors we can see, or sounds. Since I typed out this word "Seth" and your computer downloaded this webpage so it could be displayed on your screen, somewhere in your computer's memory is the sequence of numbers: [83] [101] [116] [104].*

From today's article I hope you got an idea that there is a series of memory cells in your computer and each cell holds a number in it. These numbers can be meaningful to humans in ways other than just how we understand numbers as numbers. We used the example of the ASCII encoding for the word "Seth" as a way to understand how a series of numbers can really be understood as a series of letters we can read.

Next time we talk about CS THEORY we'll talk about how memory cells are organized.

*Ok, so this webpage was probably encoded with some other number-to-text system than ASCII, but you get the point.

Wednesday, March 30, 2011

Next time, on ...

Hey, everybody. I haven't made any updates in the last week or so. I recently heard from a couple of smart people who have read through my posts so far that I'm not doing a good enough job of explaining everything plainly. Because of this, I'm going to do a "reboot" of this series of articles on the basics of computer science.

In writing this series I was attempting to do the opposite of what is usually done in computer science education. Usually, you start with the basics of creating a working computer program in a computer language like C or Java, and then eventually reveal the details under the hood that allow that program to be run on the computer.

I was trying to do the opposite of this, and talk about how things like binary math and computer memory work without giving them any context or enough explanation. In my reboot, I'm still going to do this, but I'm going to interleave a few different types of topics as I go. I'm also going to re-write on some of the topics I've already covered, trying to give them a better treatment than I did the first time. I'm not going to erase anything that has come before, because I think I have written some good things there, that maybe you should revisit as a reader after I've given more background.

Like I already mentioned, I'm going to discuss topics under large umbrella topics. At the beginning of each post title I will say in capital letters which of these umbrellas that post falls under. For example, my first post "Of bits" would be named "MATH: Of bits" under this new naming convention.

So that's the plan moving forward. I think the first thing I will talk about with this new plan will be titled "CS THEORY: Memorable numbers." I'm going to forget talking about binary numbers until later, and just focus on the basic concept of what computer memory is and what it does.

I've also received a request to talk about more electricity-oriented topics (digital circuits and whatnot). I am not very well qualified to explain the circuits that make computers happen, but I will take a stab at it after I have read up on the subject a little more. Until next time.

Wednesday, March 23, 2011

Two's complement examples

My friend suggested that I should talk more about one's complement and endianness. I think that endianness is confusing, so I don't want to talk about it, other than to say that it deals with the order of bytes in a multi-byte number in memory (click the link if you want to get confused more). I will mention one's complement, though, because I think it's interesting.

Two's complement is the system where in order to turn a number into its negative you flip all the bits and add one. One's complement is similar, except you don't add one at the end of the number conversion. For example, in one's complement the number -11 would be represented like this:

0b0000 1011 (that's positive 11)
flip all the bits
0b1111 0100 (and that's -11 in one's complement)

This looks a lot easier, right? Well, the downside is that there's an additional step when adding negative numbers with this representation. After you do the normal addition, you have to look at the final carry-out and add that value back to the one's place of your result. That right there isn't that big of a deal, and adders could be built to automatically do that.

The other bad thing about one's complement is that it's possible to have both positive and negative ZERO. Both 0b0000 0000 and 0b1111 1111 mean "zero" but one of them is positive and one is negative (remember to look at the sign bit to determine whether it's positive or negative). That's weird and it means that we can only represent 255 numbers with a single byte instead of 256 that we can represent using two's complement.

I'm not going to do any examples of one's complement, because it isn't used in real life. Instead, I'm going to do a couple examples of two's complement. First, let's transform positive 56 into negative 56, and back again. I didn't mention it last time, but you can tell the value of a negative two's complement number by repeating the same steps that made transformed it from positive to negative in the first place. Like this:

0b0011 1000 (+56)
flip all the bits
0b1100 0111
and add 1
0b1100 1000 (now we have -56)

Now let's turn that negative number back into a positive:

0b1100 1000 (-56)
flip all the bits
0b0011 0111
and add 1
0b0011 1000 (and we're back to +56)

Now let's subtract 15 from 32 (32-15=?).

Here's 32:
0b0010 0000 (+32)

Now let's figure out what -15 is:
0b0000 1111 (+15)
flip all the bits
0b1111 0000
and add 1
0b1111 0001 (-15)

Now we add them together, because 32 + (-15) is the same as 32-15.

+0b0010 0000 (32)
+0b1111 0001 (-15)
=0b0001 0001 (17)

This time we're going to totally ignore the final carry-out. Look at the sign bit, and notice that it's a 0, that means we ended up with a positive number. In fact, that number we ended up with is 17, which is the right answer. 32-15=17.

Just to review, this is how subtraction is accomplished in real computers, and how negative numbers are represented in real computers. We use two's complement because it lets us represent a wide range of positive and negative values (it doesn't have that silly +0/-0 issue one's complement has), and it is terribly, terribly convenient for allowing us to use regular multi-bit adders to perform subtraction.

I don't have a plan already in my head for what to present next, but if you ever have a recommendation, let me know in the comments.