Chapter 6
If statements

So far, our programs can do math, but they can’t really “think.” They should be able to look at the variables and somehow decide what to do next. Using one very simple new rule, we can add the ability to make all sort of complex decisions.

I’ll start out by just showing the first half of the rule, then the mechanics of how it works, then the second half. After that, we can program a crazy amount of stuff, just by being clever.

The basic decision statement is an if. Here’s a sample if statement:

public int x; // input  
 
void Start() {  
  print( "A" );  
 
  // the interesting part:  
  if( x < 10 ) {  
    print("x is small");  
    print("It’s less than 10");  
  }  
 
  print("B");  
}

x is the input, using the usual trick – change x in the Inspector, then run. The middle chunk of Start is a single if statement. What it does is probably obvious: it only prints the middle two lines if you entered 9 or less for x.

If you just looked at that and guessed the rules, you’d probably get most of them right. But, just to be complete, here they are:

Just in case, a run through of the example: the program always prints A, then it might print the things in the if, then it always prints B.

If you enter 14 for x, the program only prints A then B. The if is false, so it skips the lines in the middle. If you entered 6, it prints A, the if is true so it prints small and it’s less than 10, then it prints B.

The program will always print one of those two ways: either only A then B; or else all four lines.

6.1 Comparison Operators

Less than, <, is the same as regular math. So is greater than, >. But the rest of the math symbols we use aren’t written on our keyboard, so need rules for how to write them. Here are all of the compare symbols, and some rules/comments:

Computer math compare symbols are:

<  less than  
>  greater than  
<= less than or equal to  
>= greater than or equal to  
== equals  
!= not equals

String compare symbols are the last two: equals and not-equals.

Less-than and greater look like regular math:

if(x<0) { print( "x is negative"); }  
if(x>0) { print( "x is positive"); }

The last four take some explaining. In real math, we write “or equal to” by adding an extra line underneath the symbol. But we can’t type that, so we add an equals after it. <= counts as one symbol – no spaces, must be in that order.

Flipping them to make =< is an error. I remember equals comes last because in school I learned to say it in that order: “less than or equal to”. Ex’s:

if( x <= -1 ) { print( "x is negative"); }  
if( x >= 1 ) { print( "x is positive"); }

The second-to-last == is the compare-equals symbol. Together both =’s count as one symbol. The reason for this is that single = is already an assignment statement. We don’t want = to mean different things in different places, so we invented a special double-equals to mean compare-equals. Ex’s:

if(x==5) { print("you have five"); }  
if(w=="cat") { print( "meiow" ); }

The compare for string == is case-sensitive. If w is "cat" then if(w=="CAT") is false.

Finally, != is “not-equals”. In regular math, we put a slash through an equals, but we can’t type that. We could move it in front, like /=, but we used that up in the special divide-and-assign rule. It turns out, real mathematicians have been using an explanation point to mean not for a long time (they call it a bang,) so we stole it from them.

Like <=, != counts as one symbol, and must be in that order. The way I remember is ! means not, so != means not equal. Exs:

if(x!=0) { print( "not zero" ); }  
if(w!="abc123") { print( "password incorrect" ); }

6.2 Some semi-useful ifs

There are a few ways to think of an if, and a few tricks we can do with them. One thing if’s can do is keep numbers in range:

// x can’t be more than 10:  
if(x>10) { x=10; }

This uses the rule that any normal statements can be inside of an if. Assignment statements are normal statements, so they can be in the body.

We can grow that idea to make sure x is between 1 and 10:

// make sure x is 1-10:  
if(x<1) { x=1; } // fix too small  
if(x>10) { x=10; } // fix too big

In this case, we can think of the if as detecting and fixing bad things. True means the if is unhappy. False means we “passed” and don’t need to do anything.

The same idea can be used fix bad strings:

// fix common spelling errors  
if(w=="teh") { w="the"; }  
if(w=="thier") { w="their"; }  
 
// if they leave the name blank, use jane:  
if(name=="") { name="jane"; }

Notice how this mixes == and =. They can be easy to confuse. Just remember that == means compare and = means change. It works out the == goes inside the parens, since that’s where you do the comparing.

I want to add a note here: if’s follow the same “do it and move on” rules as everything else. They aren’t sticky. For example, this code ends with x as 11, even though it has a “can’t go past 10” if in it:

int x=17;  
if(x>10) { x=10; } // fixed to not be past 10  
x=x+1; // it’s 11. The if was a 1-time thing

We can also use an if to make tweaks. The way it works is the same as fixing bad values, but it feels like a different thing. In this example, "Beth" and "Dick" aren’t bad, but we prefer the long versions:

// undo common nicknames:  
if(name=="Beth") { name="Elisabeth"; }  
if(name=="Dick") { name="Richard"; }

if’s can be used to make table lookups. For example numbers to number words. I’m using the shortcut where we can leave out the body curly-braces if we only have one line:

// get words for numbers:  
string numWord = "unknown num";  
if(num==1) numWord="one";  
if(num==2) numWord="two";  
if(num==3) numWord="three";  
if(num==4) numWord="four";  
if(num>=5) numWord="many"; // <- everything else

We can use this trick for any made-up tables, like hurricane names:

// get hurricane names:  
if(num==1) hName="Ana";  
if(num==2) hName="Bill";  
if(num==3) hName="Claudette";  
 ...

Or, this is a completely made-up table of monsters and weapons they use, which looks up on strings:

public string monster; // enter this  
 
  string weapon = "";  
  if(monster=="pirate") weapon="rusty cutlass";  
  if(monster=="ninja") weapon="poisoned shuriken";  
  if(monster=="alien") weapon="zap ray";

Then there are some common miscellaneous uses of an if:

If we have a word, like duck, and a count, we can add an “s” for plurals:

if( howMany != 1 ) animal=animal+"s"; // ex: duck becomes ducks  
print(howMany+" "+animal); // 1 duck, or 4 ducks

The last print is a little interesting. In something like "The "+w we sneak the space into the end of The. In howMany+animal there’s nowhere to sneak in that space, but we can still add it using +" "+.

We can “take off the minus sign” from a number by flipping negatives to positive:

if(x<0) x=x*-1; // make negative #’s positive

This turns -6 into 6, but leaves +4 alone. -x is a shortcut for -1*x, so x=-x; also would have worked.

We sometimes use ifs as a guard. In our minds, this adds 1 to x, but not if it would bring us past 10:

// add 1 to the score, but not if it would go past 10:  
if(x<10) { x=x+1; }

We can also add first, then fix it later:

x=x+1;  
if(x>10) x=10;

In real life, you wouldn’t want to add 11 gallons to a 10-gallon bucket, then take out a gallon. But in computers it’s often easier to go past, then fix it.

For fun, we can use both ways to add 3 to the score, but not past 10. Here’s the method where we check before adding:

// add 3, not past 10:  
if(x<10) x+=3;

There’s a problem. This will add 3 to 8, giving 11; or 3 to 9, giving 12. We could fix that with if(x<8), but now it won’t add anything to 8 or 9. Gah! We can do it correctly by using several ifs:

// add 3, not past 10, 2nd try:  
// handle overshoot cases. They go straight to 10:  
if(x==8) x=10;  
if(x==9) x=10;  
// now handle non-overshoot:  
if(x<=7) x+=3;

That’s a bit long. The “fix afterwards” method works with no changes:

x+=3;  
if(x>10) x=10; // 7,8,9 all become 10

Moving on, we can use a pair of ifs to find the largest of two variables:

// find the largest:  
if(n1>=n2) largest=n1;  
if(n2>=n1) largest=n2;

The two if’s work together – one of them has to be true.

An interesting thing is that this gives the largest value, but doesn’t tell us which variable it came from. Usually it won’t matter – just knowing the number is good enough. Like if we order pizza and meatballs. Maybe the pizza takes 10 minutes and the meatballs take 15 – they tell us it will be a 15 minute wait, and that’s all we want to know most of the time.

Another interesting thing is if the numbers are equal, both if’s are true, and that’s fine.

6.3 Compare precedence

At some point, you might put some math inside of an if, and it might start to look funny. For examples:

  if( x>y+1 ) ...  
  if( x + y<10 ) ...

The rule is: math goes first, compares go last. The official rule is that compares count as math, but all have lower precedence than plus, minus, multiply and divide.

This is the rule you want. It means you almost never have to put extra grouping parens inside of an if. But, same as before, you can if you want:

  if( x > (y+1) ) ...  
  if( (x+y) < 10 ) ...

As with print, the outer parens are required, and the inner ones are just math parens.

As usual, you can still use parens to change the order of + and *. This adds 1 to a first:

  if( (a+1)*b < 10 ) ...

This is really just the rule that things work the same, no matter where they are in the program. Any place you have math, you can use all the regular math rules.

6.4 else

A regular if either runs the lines, or does nothing. A little different version can choose between two things. It adds the word else, and is usually called an if-else statement. An example:

if(x<10) {  
  print( "less than 10" );  
}  
else {  
  print( "10 or more" );  
}

It’s just a regular if, with else { doThisWhenFalse } jammed at the end. Notice how there are still no special if semicolons. All six lines count as one giant if-else statement.

Also notice there isn’t a test on the else. It’s just else, then a curly brace. We usually think of it as two bodies with a test to pick one.

Sometimes we call them branches. Here’s a picture of how we sometimes think about it flowing through an if-else, picking a branch:

            (x<10)  
           Y      N  
          /        \  
{ true body }    { false body }  
         \          /  
       back to the program

You can also think of an if-else as a shortcut for two “opposite” ifs in a row. Here are two ways to write small or big. The if-else version is shorter, and it’s easier to see what it does:

// 2 opposite ifs version:  
if(n<100) { w="small"; }  
if(n>=100) { w="big"; }  
 
// else version:  
if(n<100) { w="small"; }  
else { w="big"; }

Here’s “find the largest” both ways. I think the if-else version makes it more obvious we’re picking between n1 and n2:

// 2 opposite ifs largest:  
if(n1>n2) largest=n1;  
if(n2>=n1) largest=n2;  
 
// if-else:  
if(n1>n2) largest=n1;  
else largest=n2;

Choosing between two equally good options is one way to use an if-else. Another way is just to tack-on a little extra. Two old examples with an extra else added:

if(x<10) x=x+1;  
else { print("sorry - can’t add past 10"); }  
 
if(password != "abc123") print("bad password");  
else print("password accepted");

More else notes:

6.5 And, Or

The test is allowed to use combiners and and or. This if is true for yellow dogs:

if(animal=="dog" && color=="yellow")  
  print("You should name your yellow dog Sparky.");

I’m just going to write all the rules, then examples for them later:

Suppose we want to know if animal is a cat or a dog. We have to write a legal check for a cat, then a legal check for a dog, and combine them:

if(animal=="cat" || animal=="dog")  print("common housepet");

You can think of this as the “each has to be legal” rule, or as the ”and/or always goes last” rule. Suppose we wrote if(animal=="cat" || "dog"). The computer sees 2 things joined by an or, and the second one is if("dog"), which makes no sense (it gives a compile error.)

Suppose we want to check for discount tickets (very old or very young.) We have to write each part out, like this:

if(age<=12 || age>=65)  print("discount tickets");

If we tried the shortcut if(age<=12 || >=65) the computer would see if(>=65) for the second part (obviously an error.)

If we need x and y to both be 0 or more, we also have to write it the long way:

if(x>=0 && y>=0) print("both 0 or more");

The shortcut if(x && y>=0) would see if(x) for the first part (also a compile error.)

A good thing about each part being separate is you can mix and match any kind of variables and tests. For example, a string and int check:

// young cats are kittens:  
if(animal=="cat" && age<=3)  print("kitten");

The way the computer sees it, animal=="cat" is true or false. Then age<=3 is true or false. By the time the && happens, everything is down to only true’s and false’s.

With string comparing, it’s fine to mix == for one and != for the other:

// non-brown cows are fancy:  
if(animal=="cow" && color!="brown") print("fancy cow");

The multiple compare rule says we can check for baby fancy cows by adding one more:

if(animal=="cow" && color!="brown" && age<=3) print("young fancy cow");

This is a little bit like 3+7+4. There’s no special 3-way rule. Technically you’re adding a pair at a time: (3+7)+4. Also technically, the computer and’s the animal and color first. But it works out the same either way - they all have to be true.

It can be helpful to look at some broken code. These are legal, but pointless. Just say them out loud and it’s obvious way:

if(age>=18 && age>=21)  print("same as if(age>=21)");  
if(age>=18 || age>=21)  print("same as if(age>=18)");

For the second one, age of 21 or more seems like it might be “double-true,” but there’s no such thing. Everything inside of an if always comes out to be just regular true or false, no matter how complicated it is.

We can make some other wrong example ifs that are always false. A number can’t be less than 5 and more than 10 at the same time, and a word can’t be "cow" and "horse" at the same time:

if(num<5 && num>10)  print("always false");  
if(animal=="cow" && animal=="horse") print("always false");

Here are some useless always true ifs using an or. They’re always either true or “double true”:

if(num>7 || num<15)  print("always true");  
if(animal!="cow" || animal!="horse") print("always true");

In the first, all small numbers are less than 15, all larger numbers are greater than 7, and 8-14 are “double true” (which is the same as normal true). In the second, cow is true since it’s not horse, horse is true since it’s not cow, and everything else is true since it’s not cow or horse.

This next example shows how precedence normally does the right thing (the math is made-up. Just think about the grouping):

if( x + 3 < y || 3 < x || y == 9 ) ...  
// how the computer sees it:  
if( ((x+3) < y) || (3<x) || (y==9) ) ...

It looks a little like the computer might try y || 3, or maybe x || y, but it knows and’s and or’s go last.

6.5.1 Useful and/or’s

Now onto tricks and examples people really use.

We check “if it’s one of these” by using ==’s chained together by or’s:

if(n==2 || n==4 || n==6 || n==8)  
  print("single digit even");  
 
if(w=="bill" || w=="willy" || w=="billy")  
  print("william");

It might seem like there would be a shortcut where we didn’t have to write n== or w== over and over. But there isn’t.

Range-checking is common. In math, we’d write 1 to 10 as: 1 n 10. That’s really a shorthand for two compares. In a program, we have to write them out:

if(n>=1 && n<=10) print("n between 1 and 10");

A way to think of this is it can’t be too small (n>=1) and can’t be too big (n<=10). Both parts have to be true, so it’s an and. It we used an or by mistake, the whole thing would always be true (everything is either bigger than 1 or less than 10. Some numbers are both.)

We can do some cute things with a range check. Say we want to check how many digits a number has. There isn’t a special way, but 0-9 is 1-digit, 10-99 is two digits, and so on (pretend we know the number isn’t too big):

// how many digits does n have:  
int digits=0;  
if(n>=0 && n<=9) digits=1;  
if(n>=10 && n<=99) digits=2;  
if(n>=100 && n<=999) digits=3;

We can mix ranges and exact numbers. Suppose we win if we get 7 or 10-15 or 22. That’s like checking for three exact numbers, using ||’s. Except the middle one is a range:

if( n==7 || (n>=10 && n<=15) || n==22 ) { print("You win"); }

Suppose instead we win on 5-9 or 18-21. That’s “this range or that range”:

if( (n>=5 && n<=9) || (n>=18 && n<=21) ) { print("you win differently"); }

Mixed and’s/or’s can be confusing. Sometimes you can just see what they do. Other times you have to trace them. Convert each part to T/F, then combine them a little at a time:

// suppose n is 9  
if( n==7 || (n>=10 && n<=15) || n==22) { print("You win"); }  
1)   F   || (  F   &&   T  ) ||   F  
2)   F   ||        F         ||   F  
3)               F

Often we want to check whether two floats are within 0.01 of each other. If we’re sneaky, we can find the difference, then use a range-check to see it that’s small:

float diff = n2-n1;  
if(diff>-0.01f && diff<0.01f) print("close enough");

But it’s fun to try to write it directly. We can do a range check comparing n1 to “a little less than n2” and “a little more than n2”:

if( (n1 > n2-0.01f) && (n1 < n2+0.01f) ) print("close enough");

Checking not-in-range isn’t as common. It looks like a range-check, but it has to be too low or too high:

if( n<1 || n>10 ) print("n is NOT 1-10");

If you use an and by mistake it will always be false (nothing can be less than 1 and more than 10 at the same time.)

6.6 Nested ifs

if and else bodies can have more ifs inside. We usually call them nested if’s (after those nested Russian dolls.) We don’t need any new rules for this, but we do need practice thinking about how to use this trick.

Suppose we have a truck fee: 1000 pounds and under is free, up to 5000 pounds is $10, and past that is $20. This nested if prints the fee:

int fee=0;  
if(wt<=1000) { fee=0; print("No fee"); }  
else {  
  if(wt<=5000) fee=10;  
  else fee=20;  
 
  print("Truck fee is $" + fee );  
}

I think this seems pretty natural. The first if divides it into free and pay. If it’s free, we print that and are done. Inside the pay part, we need one more if to check the cost. Notice how the inside if is indented to look nice in the else body.

A trick is to check it by “exercising” each part. Run it in your head with a number from each category, like 500, 1500 and 7000, which should give every possible result, and make sure it does.

Here’s a longer one that finds the prices for animals. Most animals cost 3, except for cats and dogs, and red dogs and white and black cats are exceptions:

int cost=3; // "other" animals all cost 3  
 
if(animal=="dog") {  
  cost=8; // most dogs are 8  
  if(color=="red") cost=16; // red dogs are rare  
}  
 
if(animal=="cat") {  
  cost=10; // most cats are 10  
  if(color=="white") {  
     cost=14; // white cats are valuable  
  }  
  if(color=="black")  
    cost=5; // no one likes black cats  
}  
print("Cost of " + color + " " + animal + " is $" + cost);

Hopefully this reads pretty well. The dog if says dogs cost 8, then does one extra check for red dogs. Notice how it only checks the color – we already know it’s a dog. The cat if is the same, but longer (and I changed-up the paren style for fun.)

If you remember from school, the x/y graph is broken into quadrants around (0,0) (like the picture below.) To find the quadrant of an (x,y), we could brute force with four lines like if(x>=0 && y>=0) quadrant=1;.

But a nicer way is to make a plan: divide it into top/bottom half, and go from there:

// Standard quadrants (0,0) in the middle:  
// 2 | 1  
// -----  
// 3 | 4  
 
if(y>=0) { // top half  
  if(x>=0) quadrant=1;  
  else quadrant=2;  
}  
else { // bottom  
  if(x>=0) quadrant=4;  
  else quadrant=3;  
}

It could also be written starting with a left/right check.

We can make several plans to find the largest of three numbers. This one checks whether A is the largest. If not, it must be B or C:

// find largest of a,b,c  
if(a>b && a>c) largest=a;  
else {  
  // it wasn’t A, so check B and C:  
  if(b>c) largest=b;  
  else largest=c;  
}

A different plan, trickier than the first way, starts by comparing A and B. The smaller one can’t be the answer. Compare the larger one to C:

// find largest of a,b,c (version 2)  
if(a<b) {  
  // A is too small, must be B or C:  
  if(b>c) largest=b;  
  else largest=c;  
}  
else {  
  // B is too small. Must be A or C:  
  if(a>c) largest=a;  
  else largest=c;  
}

A simple way to test is using every combination of 1,2,3 (there are 6 of them.) Not that it matters, but this way is a little faster. It always uses two compares. The first way sometimes uses three compares.

This next one is simpler. It checks for positive, negative or 0. I thought it made sense to “knock out” 0 first, then check for +/-:

if(x==0) print("zero");  
else {  
  if(x>0) print("positive");  
  else print("negative");  
}

6.7 Common errors