As you have noticed, non-integer numbers are not entirely accurate, and do not lend themselves well to comparison. In order to figure out why this might be the case, consider the means by which computers represent data. They use bits, or binary digits. That is, computers basically have lots and lots of on/off switches which stand for 1 and 0. Using these bits, how might we accurately represent non-integers?
As it turns out, this is actually really difficult to do, and to completely precisely represent a non-integer with a finite amount of switches is (almost always) impossible. Let’s explore why.
First, let’s look at how a computer might represent integers. Representing integers with binary is easy. Trivially, 0 is just 0 and 1 is just 1. Okay, so what about 2, 3, 4, 5, etc.? Well, let’s look at what happens in the decimal system which we use everyday! If we count from 0 to 9, how do we get the next number? We reset the digit to 0, and we add a number in front. So the next number up from 9 becomes 10! In binary, what would be the next number after 0 and 1? It would be 10 as well! And of course, after 10 comes 11. But what about after that? Well, we just reset all of the numbers to 0 and add a 1 in front. So after 11 is 100, just like 99 becomes 100 in decimal!
Because we can count with integers in this way, they are considered countable. This property means that integers can be fairly easily enumerated, or defined one after another. So let’s enumerate decimals in binary:
0 → 0
1 → 1
2 → 10
3 → 11
4 → 100
5 → 101
6 → 110
7 → 111
8 → 1000
9 → 1001
10 → 1010
11 → 1011
etc.
So, if we can represent integers so cleanly, why is it so hard to represent the numbers between the integers? For starters, we can’t enumerate all of the numbers between 0 and 1. We can have 0.1, 0.01, 0.001, 0.0001, but as you can see, there is no end in sight. This might be hard to believe, but there are actually an infinite amount of numbers between every other number, and that fact alone dooms non-integers into a realm of hopeless imprecision. But still, we have found a way to store non-integers using 1s and 0s. So how do computers do it?
The standard way of representing non-integral values is with floating points. Floating points are so named because the format allows for the movement of the decimal point. Essentially, what floating points do is store a significand and an exponent, such as what would be used in scientific notation:
1.254 * 10^6
In the number above, 1.254 is the significand, 10 is the base and 6 is the exponent. Representing a number like 0.3 is easy with a base of 10: 3 * 10^-1. But what about binary? With a base of 2, it is actually pretty hard to represent that number with a limited about of space. In order to see why, consider how many digits it takes to represent the number 1/3 in base 10. If you were to write this out, it would look like 0.3333… That’s an infinite amount of 3s! The same thing happens in binary. The only non-integral numbers that can be stored with a finite amount of space in binary are numbers that can be expressed as fractions with an integer in the numerator and a power of 2 in the denominator. Things like 1/4, 5/8, 3/16, 11/32 are all easy to represent. Things like 1/7, 4/5, 7/13, 3/10 are not. Because the absolute binary content might change depending on the format used to store the number (and because the computer can’t read your mind), directly comparing numbers which need an infinite amount of space to store might fail. That’s what happened here.
This was a long a reply, and probably not well-formatted either, but this concept is crucial to understand. I hope that this post helps you understand why your comparison might have failed where you expected it to succeed. In the future, avoid comparing decimals unless it is absolutely necessary. And if you absolutely, positively cannot avoid comparing decimals, do not compare them using the double-equals operator. Instead, subtract them and test the value against a sufficiently small “epsilon” value, like so:
function decimalEquals(a, b, epsilon)
if epsilon == nil then
epsilon = 0.00001;
end
return math.abs(a - b) < epsilon;
end
tl;dr: use the code above to compare decimals instead of ==