The Inequality in Being Equal

27 March, 2024

All things being equal, they (equal things) are not always so in PHP. It's more of...it depends on what you mean by 'equal.' 

Equality depends on context. And nowhere, in the developer world at least, is that more true than in a loosely typed language. So, let us look at four contexts of equality:

  1. equivalent in value
  2. identical in value
  3. identical in all ways that matter at the time
  4. completely identical

The most common context of 'is equal to' is that of value. In PHP and most other languages, this equality is tested with the == operator:

Tests of equality using the == operator
Test   Result
1 == 1   TRUE
3 - 2 == 1 * 1   TRUE
1 == '1'   TRUE
1 == TRUE   TRUE
0 == FALSE   TRUE

The thing of it is that in all examples the values are equal, but looking beyond just the values we can see that the final three examples are not really equal. With these examples, the difference is in the implied data type, and that extends to the internal representation.

Comparison of internal storage
Value Type Storage   Storage Type Value
1 Int 00000000 00000001   00000000 00000001 Int 1
3-2 Int 00000000 00000001   0000000000000001 Int 1*1
1 Int 00000000 00000001   00110001 Str '1'
1 Int 00000000 00000001   see below1 Bool3 1
0 Int 00000000 00000000   see below2 Bool3 0
1 Stored as 00000000 00000000 00000000 00000001
2 Stored as 00000000 00000000 00000000 00000000
3 Boolean zval is stored as a long int with the IS_BOOL flag set

As you can see, the value pairs are equivalent, but different beyond that. So, why does PHP report them as being equal? Because when using the == operator, you are asking PHP to compare values, and so it kindly does type conversion in order to accommodate your request.

Does it matter? Are there cases where being equivalent is not good enough? Sure. For example, if you're standing at a candy machine that accepts only coins and you have a $1 bill, and ask a passerby for change of a dollar, were you to be handed 100 pennies, it would be equivalent to the four quarters you were hoping for, but as far as a machine that doesn't accept pennies is concerned, equivalent isn't good enough.

There is another operator, ===. You can think of this as 'very equal' to, or better, identical in value to. What makes its operation different than == is that no type conversion is done. This behavior can be forced with the == operator as well, by casting one or both sides, because when you cast, PHP will not override your cast with a default type:

1 == 1 evaluates as TRUE, because both are treated as integers

1 == (long) 1 evaluates as FALSE, because the type of each is different

The following table shows the result of each of the earlier tests:

Tests of equality using the === operator
Test   Result
1 === 1   TRUE
3 - 2 === 1 * 1   TRUE
1 === '1'   FALSE
1 === TRUE   FALSE
0 === FALSE   FALSE

That covers two of the contexts of equality, but how about the other two? What does 'identical in all ways that matter at the time' mean? Let's use an example similar to the earlier one. You're sitting at a car racing game in a video arcade. The machine costs 50 cents, and only accepts quarters. You have five dimes. If we were to look at this as test of equality, it would be:

5 * 10 == 2 * 25

As far as PHP is concerned, this is true. As far as the video game is concerned,  no such luck. How can you test this scenario? If you consider what makes the two different, you'll see that while the values are identical, and the storage of the values is identical, what is different is a property, in this case the type of coin...the component values. One way to test for this would be for each side of the operator to be represented by an object, with the coin types and quantity of each being an array as a property. A utility class could provide an isEqual() method, which when passed two objects compares the arrays:

Comparison of a property
Coin Obj 1 Qty Obj 2 Qty
0 0
0 0
10¢ 5 0
25¢ 0 2
50¢ 0 0
$1 0 0

In comparing the two arrays:

[0, 0, 5, 0, 0, 0] === [0, 0, 0, 2, 0, 0]

The result will be false. 

Ok, but what does the '...all ways that matter at the time' mean? Well, if the utility method were set up as mentioned earlier, it would only be comparing the quantities of each type of coin. There could be other properties in the object as well, such as the year each coin was minted, their condition, etc. Since we are only concerned with the coin denominations, we ignore the other properties in comparing the two because the only properties don't matter at the time. This means that we can establish whether the two are identical in a particular way, but that leaves the possibility that they're not identical in every way, our final context.

Another example. You purchase a ticket to a concert. There are many properties associated with the ticket:

  • the date
  • the time (there might be more than one performance on that date)
  • the venue
  • the section
  • the row
  • the seat

It will be unsatisfactory if any of those properties are different than what is expected, and so in such a case it would be best to compare all the properties of the expected and actual object. In PHP this is accomplished with:

$obj1 == $obj2

Why not the === operator, if we want to ensure that they are identical? Because in the case of objects a comparison with that operator will only evaluate to true if both are the same instance, while == will ensure that all properties and their values are identical.

Login or Register to Comment!