Question

While converting a project from Python to C#, I found some interesting differences in the syntax families. Yet I got stuck, still unable to understand and comprehend the dissimilar behavior of comparison operator in C#.

During the course of curing this curiosity, I considered few languages of C-syntax family; C, C++, C#, Java, Javascript.. and verified the behavior. Here is how it transpired:

Let a=2, b=3, c=4, d=5;

Now, consider the following expressions:

a < a < a  // returns true
c < b < a  // returns true
c > b > a  // returns false
a < c > b  // returns false

If it was due to the right-associativity, then the following code in JavaScript shouldn't act like:

console.info(a < false);   // returns false
console.info(a < a);       // returns false
console.info(a < a < a);   // returns true, as opposed to returning false

Here is the C/C++ version

int main(){
    int a=2, b=3, c=4, d=5;

    printf("%s\n","false\0true"+6*(a < a < a));  // returns true
    printf("%s\n","false\0true"+6*(c < b < a));  // returns true
    printf("%s\n","false\0true"+6*(c > b > a));  // returns false
    printf("%s\n","false\0true"+6*(a < c > b));  // returns false

  return 0;
}

Except in Python, where

a < a < a  // returns false
c < b < a  // returns false
c > b > a  // returns true
a < c > b  // returns true

Can anyone explain why C-family of languages and Python are computing the expressions differently?

Was it helpful?

Solution

Because Python uses a slightly interpretation of your input:

Formally, if a, b, c, ..., y, z are expressions and op1, op2, ..., opN are comparison operators, then a op1 b op2 c ... y opN z is equivalent to a op1 b and b op2 c and ... y opN z, except that each expression is evaluated at most once.

This means your lines will be interpreted as

a < a < a = a < a and a < a // returns false
c < b < a = c < b and b < a // returns false
c > b > a = c > b and b > a // returns true
a < c > b = a < c and c > b // returns true

In C-style languages, an comparison expression will evaluate to either false (integer value 0) or true (integer value 1). So in C it will behave like

a < a < a = (a < a) < a = 0 < a  // returns true
c < b < a = (c < b) < a = 0 < a  // returns true
c > b > a = (c > b) > a = 1 > a // returns false
a < c > b = (a < c) > b = 0 > b // returns false

Note that almost all languages define operators with a boolean return value, but since boolean values can be implicit converted to zero or one the proposition above is still valid:

// C++ example
struct myComparableObject{
    int data;
    bool operator<(const myComparableObject& o){
       return data < o.data;
    }
};

myComparableObject a, b;
a.data = 2;
b.data = 3;
int c = 5;

a < b; // true
a < c; // error, a cannot be converted to int / unknown operator
a.data < c; // true
a < b < c; // true, as this is equal to
           //   (a < b) < c = false < c = 0 < c

For example JavaScript will actually use ToNumber in order to compare two non-string objects, see [ECMAScript p78, 11.8.5 The Abstract Relational Comparison Algorithm], where ToNumber(false) is zero and ToNumber(true) === 1.

The comparison x < y, where x and y are values, produces true, false, or undefined[...]

  • Let px be the result of calling ToPrimitive(x, hint Number).
  • Let py be the result of calling ToPrimitive(y, hint Number).

    1. If it is not the case that both Type(px) is String and Type(py) is String, then

      a. Let nx be the result of calling ToNumber(px). Because px and py are primitive values evaluation order is not important.
      b. Let ny be the result of calling ToNumber(py).
      c. If nx is NaN, return undefined.
      d. If ny is NaN, return undefined.
      e. If nx and ny are the same Number value, return false.
      f. If nx is +0 and ny is -0, return false.
      g. If nx is -0 and ny is +0, return false.
      h. If nx is +infty, return false.
      i. If ny is +infty, return true.
      j. If ny is -infty, return false.
      k. If nx is -infty, return true.
      l. If the mathematical value of nx is less than the mathematical value of ny —note that these mathematical values are both finite and not both zero— return true. Otherwise, return false.

OTHER TIPS

It's because a < a < a is evaluated like ((a < a) < a) which then becomes 0 < a which is true (when a >= 0)

If you run the following, the first expression changes to false.

int main(){
    int a=0, b=3, c=4, d=5;

    printf("%s\n","false\0true"+6*(a < a < a));  // returns false
    printf("%s\n","false\0true"+6*(c < b < a));  // returns true
    printf("%s\n","false\0true"+6*(c > b > a));  // returns false
    printf("%s\n","false\0true"+6*(a < c > b));  // returns false

  return 0;
}

Whereas in Python, a < a < a becomes a < a and a < a. So it's not comparing the result of a < a. If you add parentheses to your statements, you'll get the C-like behavior again.

(a < a) < a  ## returns true
(c < b) < a  ## returns true
(c > b) > a  ## returns false
(a < c) > b  ## returns false
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top