Simpler Is Better
When we are confronted with a large algebraic expression or lots of ifs, we normally need to slow down and analyze it carefully. Wouldn't it be better if we could write it more simply, so that it didn't cost us so much time? Because we are expensive?
Let's start with some easy ones. Assume a and b are boolean variables.
(a == true) simplifies to a (a == false) (a != true) (a != false)
Simplification from a written description.
Say I see the instructions "true when a and b are true or when a and b are false, otherwise false. This can result in the expression:
((a == true) && (b == true) || ((a == false) && (b == false)) // gah!
If you make a truth table for this expression, then there is a very obvious simplification. In general, long ugly logical expressions can often be simplified.
Simplification of an inequality.
!(a > 5)
Simplification of returns.
When you have a boolean method, often if might seem that you need several returns inside ifs and elses depending on different cases. Usually, you can rewrite to have only one return based on a single boolean expression.
if (a == true) return true; else return false;
if (a == false) return true; else return false;
De Morgan's Law
It is easiest to think of De Morgan's Law as distributing a not (!) across an && or an || by switching from && to || or vice versa.
!(p && q) is the same as (!p || !q) !(p || q) is the same as (!p && !q)
This is easy to confirm with a truth table.
Use De Morgan's Law to change this:
!(x < 0 || x >=5)
It may seem more difficult to factor out a not (!) using De Morgan's Law: see the homework.
Why use De Morgan's Law? Sometimes it simplifies the code very nicely.
Short Circuit Evaluation
Sometimes a boolean expression's value can be decided before the whole expression is evaluated. Java exploits this opportunity for optimization. For example:
(a !=0 && c == b/a)
If a is 0, then the left argument of the && is false, and no matter what the right argument is, the result is false. So Java will not even evaluate the right argument: it will just decide the expression is false. In this example, we use that feature to avoid dividing by zero. If a is not zero, the division will take place. There is a similar short circuit:
(a == 0 || c == b/a)
Here when the left argument of the || is true, we do not need to evaluate the right argument.