92 lines
4.8 KiB
Markdown
92 lines
4.8 KiB
Markdown
|
---
|
|||
|
title: "The various ways to check if an integer is even"
|
|||
|
date: "2021-04-27"
|
|||
|
---
|
|||
|
|
|||
|
You have probably seen this post on Twitter by now:
|
|||
|
|
|||
|
<blockquote class="twitter-tweet"><p dir="ltr" lang="en">God I wish there was an easier way to do this <a href="https://t.co/8UrBNKdTRW">pic.twitter.com/8UrBNKdTRW</a></p>— Kat Maddox (@ctrlshifti) <a href="https://twitter.com/ctrlshifti/status/1288745146759000064?ref_src=twsrc%5Etfw">July 30, 2020</a></blockquote>
|
|||
|
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
|||
|
|
|||
|
But actually, the way most people test whether a number is even is _wrong._ It's not your fault, computers think differently than we do. And in most cases, the compiler fixes your mistake for you. But it's been a long day of talking about Alpine governance, so I thought I would have some fun.
|
|||
|
|
|||
|
However, a quick note: for these examples, I am using ML, specifically the OCaml dialect of it. Translating these expressions to your language however should not be difficult, and I will provide C-like syntax for the right answer below too.
|
|||
|
|
|||
|
## Using the modulus operator and bitwise math
|
|||
|
|
|||
|
The usual way people test whether a number is even is the way they teach you in grade school: `x mod 2 == 0`. In a C-like language, this would be represented as `x % 2 == 0`. However, this is actually quite slow, as the `div` instruction is quite expensive on most CPUs.
|
|||
|
|
|||
|
There is a much faster way to check if a number is even or odd, but to understand why it is faster, we should discuss some number theory first. Whether a number is even or odd ultimately comes down to a single number: `1`.
|
|||
|
|
|||
|
There are two numbers in the entire universe that have the property that they are the same number in _any_ number system we use today: `0` is always zero, and `1` is always one. This holds true for binary (base 2), octal (base 8), decimal (base 10), and hexadecimal (base 16).
|
|||
|
|
|||
|
Accordingly, we can use binary logic to test whether a number is even or not by testing whether it ends in `1` when represented as binary. But many programmers probably don't actually know how to do this -- it doesn't usually come up when you're writing a web app, after all.
|
|||
|
|
|||
|
The answer is to use logical and: `x land 1 == 0` (or in C, `x & 1 == 0`). We can prove that both expressions are functionally equivalent, by defining both testing functions and testing for the same output:
|
|||
|
|
|||
|
\# let evenMod x = x mod 2 == 0;;
|
|||
|
val evenMod : int -> bool = <fun>
|
|||
|
# let evenAnd x = x land 1 == 0;;
|
|||
|
val evenAnd : int -> bool = <fun>
|
|||
|
# let evenMatches x = evenMod(x) == evenAnd(x);;
|
|||
|
val evenMatches : int -> bool = <fun>
|
|||
|
# evenMatches(0);;
|
|||
|
- : bool = true
|
|||
|
# evenMatches(1);;
|
|||
|
- : bool = true
|
|||
|
# evenMatches(2);;
|
|||
|
- : bool = true
|
|||
|
# evenMatches(3);;
|
|||
|
- : bool = true
|
|||
|
|
|||
|
As you can see, both are equivalent. And to be clear, **this is the right way to test whether an integer is even**. The other ways below are intended to be a joke. Also, most compilers will optimize `x mod 2 == 0` to `x land 1 == 0`.
|
|||
|
|
|||
|
## Using functional programming
|
|||
|
|
|||
|
The nice thing about math is that there's always one way to prove something, especially when there's more than one way. Modulus operator? Bitwise logic? Please. We're going to solve this problem [the way Alonzo Church intended](https://en.wikipedia.org/wiki/Lambda_calculus). But to do that, we need to think about what _actually makes a number even_. The answer is simple, of course: an even number is _one which is not odd_. But what is an odd number? Well, one that isn't even of course.
|
|||
|
|
|||
|
But can we really apply this circular logic to code? Of course we can!
|
|||
|
|
|||
|
\# let rec isEven x =
|
|||
|
x = 0 || isOdd (x - 1)
|
|||
|
and isOdd x =
|
|||
|
x <> 0 && isEven (x - 1);;
|
|||
|
val isEven : int -> bool = <fun>
|
|||
|
val isOdd : int -> bool = <fun>
|
|||
|
# isEven(0);;
|
|||
|
- : bool = true
|
|||
|
# isEven(1);;
|
|||
|
- : bool = false
|
|||
|
# isEven(2);;
|
|||
|
- : bool = true
|
|||
|
# isEven(3);;
|
|||
|
- : bool = false
|
|||
|
|
|||
|
As you can see, we've succeeded in proving that an even number is clearly not odd!
|
|||
|
|
|||
|
## Using pattern matching
|
|||
|
|
|||
|
In 1962, Bell Labs [invented pattern matching, as part of the SNOBOL language](https://en.wikipedia.org/wiki/SNOBOL). Pattern matching has become a popular programming language feature, being implemented in not just SNOBOL, but also Erlang, Elixir, Haskell, ML, Rust and many more. But can we use it to determine if a number is even or odd? Absolutely.
|
|||
|
|
|||
|
\# let rec isEven x =
|
|||
|
match x with
|
|||
|
| 0 -> true
|
|||
|
| 1 -> false
|
|||
|
| 2 -> true
|
|||
|
| x -> isEven(x - 2);;
|
|||
|
val isEven : int -> bool = <fun>
|
|||
|
# isEven(0);;
|
|||
|
- : bool = true
|
|||
|
# isEven(1);;
|
|||
|
- : bool = false
|
|||
|
# isEven(2);;
|
|||
|
- : bool = true
|
|||
|
# isEven(3);;
|
|||
|
- : bool = false
|
|||
|
# isEven(4);;
|
|||
|
- : bool = true
|
|||
|
# isEven(5);;
|
|||
|
- : bool = false
|
|||
|
|
|||
|
As you can see, we have demonstrated many ways to test if a number is even or not. Some are better than others, but others are more amusing.
|