Original Question: Why are two different numbers equal in JavaScript?
I was playing a little with the console and it occurred to me to try the following:
var num1 = 0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
var num2 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
console.log(num1 == num2)
And I was surprised that this comparison actually evaluates to true (you can try it on console).
Why is this happening? It is clear that they are different numbers (the second has one digit less than the first; if I add an F to the second or a 0 to the first, the comparison is already false, but in the meantime, it is true).
All numbers in JavaScript are internally represented as double-precision floating-point numbers (see §4.3.19 in the spec), This means that one can represent exactly any number between 0 and 9007199254740992 (hexadecimal
0x20000000000000
, or whatever is the same 2^53). The same goes for negative numbers: they range from 0 to -9007199254740992. If one tries to add 1 (or subtract 1 for negatives) one finds that the number left is the same, but if one tries to add 2 one finds that the number changes. This is because of how the mantissa is in the IEEE754 standard.I watched:
Adding another F to the second number (when you represented it in hexa, you are changing not only the mantissa but also the exponent (the number is 4 orders greater than the other), and so it does evaluate differently.
It doesn't take as many
'0'
s and'F'
s. Actually the equality starts with only 14'0'
s and'F'
s even with the operator===
The explanation is simple, actually those two numbers are equal and therefore the comparison is correct in the same way that the following equality also produces true
They are simply two different ways of expressing the same number.
Both hexadecimal representations evaluate to the same number which can be demonstrated simply by running to the console.
Which evaluates both to:
In the case of the version with many
'0'
sos'F'
, both expressions evaluate to the following number:When you represent any IEEE754 Double Precision Floating Point Number internally you have something like:
Where (0) is the sign bit (0=positive, 1=negative), (1) the exponent bits, and (2) the math bits.
If you compare in JavaScript
0.5 == 5 * 0.1
you will gettrue
even when that operation has the imprecision characteristic of floating points (that is: you will have some error). Many languages (JavaScript among them) tolerate a certain error in the last bit of a comparison in the mantissa (of course, to the same exponent and the same sign).Normally, the floating point scheme stores the exponent as a number between 1024 and -1023 (or at least that is how it should be understood), while the mantissa must be understood as a number between 0 and 0.111111... to give a number like
0.1b * 2 ^ 12
which is equivalent to calculating0.5 * 4096
in decimal base. The mantissa, in this sense, will always be less than 1 and will be stored in such a way that the first digit (in binary base) is 1. For this to happen, a process called normalization is carried out in which we run the exponent as many necessary numbers as "commas" we want to move in the number until we can build a mantissa whose first fractional digit in binary is 1 (which sometimes cannot be done, and this is reflected by seeing that the exponent, in binary, it is000 0000 0000
and you can't "down" any more). In this sense, as long as the exponent is not000 0000 0000
the number, it will have been normalized so that the mantissa starts with0.1
(binary), and in this sense, it is redundant (inefficient) to store that 1 (the 0 before the point is never stored). Therefore, our mantissa will always allow us to store one more digit and, therefore, we can represent 2^53 exactly (instead of 2^52 because we have 52 digits in the mantissa).Let us now see that the number 0xFF... is 13 letters F or, in binary, 52 bits long. So it just occurs to us that the number could be saved as (positive)(+52)(1111... 52 "ones") but in reality because of this "shift" of the 1 that comes "implicit", the first 1 is not saved . The number is then saved as (doing the shift for the exponent in binary, of course):
The other number is a one followed by 52 zeros. Normally it would be: (positive)(+53)(10000... totaling 51 "zeros"). Again since the mantissa starts with
0.1
(because we can normalize these numbers) we save the first one.Now we have to compare the numbers on equal terms. We first make sure that, in fact, the sign and the exponent are the same. The ALU directly does this, running indices and mantissas at the same time. Then compare the mantissas and see if they are the same.
So the mantissa of 100000... and 011111... (with the respective implicit ones) will "look alike" and have that little "epsilon" difference, so they will be able to be compared as equals.
Note about the mantissa and floating point representation: Conceptually the mantissa is always less than 1. If you want to represent a larger number, you should conceive of it using exponents. Examples:
0.5 * 2 ^ 0
(considering the correct order of precedence of operators in mathematics).1 * 2 ^ 0
since the mantissa is strictly less than 1, so it must be represented as0.5 * 2 ^ 1
.(65/128) * 2 ^ 7
.These numbers will be represented as follows (remember: since the numbers are normalizable, the first "1" is implicit):
Y
Note About Exponents: Precision to more decimal places can be achieved by using negative exponents (between -1 and -1023): Exponents can look like positive numbers, but they actually have an "offset" (called bias , originally) of 1023 (this means that the exponent that appears
000 0000 0001
actually corresponds to 2^(-1022)). Translating it to powers in base 10, the smallest possible exponent is -308 (also considering where the mantissa is, because the number is a non-normalized number). The smallest positive number turns out to be:which is:
(1 * 2^-52) * 2^-1023
given the -52 for the mantissa (which just has a 1 at the end) and the -1023 for the exponent. The last one results in the position: 1 * 2^(-1075), which approximates the happy10 ^ -308
.The lowest exponent is
000 0000 0000
corresponding to (-1023). However, in this value the mantissas do not have the leading 1 implied. On the other hand, although the largest exponent can be represented as111 1111 1111
, it is not actually used as an exponent but to represent common floating-point pseudonumbers:corresponds to +Infinity, while:
corresponds to -Infinity, and any pattern with a mantissa other than 0, such as:
corresponds to NaN ( not a number ; ideal representation for results that cannot return numbers, like
log(-1)
or0/0
). The first bit (the sign bit) is irrelevant in these cases.