25
def logical_xor(a, b): # for example, -1 and 1
    print (a < 0) # evaluates to True
    print (b < 0) # evaluates to False
    print (a < 0 != b < 0) # EVALUATES TO FALSE! why??? it's True != False
    return (a < 0 != b < 0) # returns False when it should return True

print ( logical_xor(-1, 1) ) # returns FALSE!

# now for clarification

print ( True != False) # PRINTS TRUE!

Could someone explain what is happening? I'm trying to make a one liner:

lambda a, b: (a < 0 != b < 0)
Makyen
  • 31,849
  • 12
  • 86
  • 121
  • 12
    There's a lesson to be learned here: Use parentheses. Or, alternatively, use `print (a*b < 0)`. – David Hammen Apr 22 '15 at 08:18
  • `return math.copysign(1, a) != math.copysign(1, b)` – Jacob Krall Apr 22 '15 at 15:00
  • `print ( True != False) # PRINTS TRUE!` why are you so surprised by that? It _is_ true that true is not equal to false. – Mike G Apr 22 '15 at 18:02
  • @mikeTheLiar: The OP is *not* surprised by that; it's the behavior (s)he originally expected. – ruakh Apr 23 '15 at 02:03
  • 2
    Please don't make more work for others by vandalizing your posts. By posting on the Stack Exchange (SE) network, you've granted a non-revocable right, under a [CC BY-SA license](//creativecommons.org/licenses/by-sa/4.0), for SE to distribute the content (i.e. regardless of your future choices). By SE policy, the non-vandalized version is distributed. Thus, any vandalism will be reverted. Please see: [How does deleting work? …](//meta.stackexchange.com/q/5221). If permitted to delete, there's a "delete" button below the post, on the left, but it's only in browsers, not the mobile app. – Makyen Jul 18 '20 at 22:54

4 Answers4

30

All comparison operators in Python have the same precedence. In addition, Python does chained comparisons. Thus,

(a < 0 != b < 0)

breaks down as:

(a < 0) and (0 != b) and (b < 0)

If any one of these is false, the total result of the expression will be False.

What you want to do is evaluate each condition separately, like so:

(a < 0) != (b < 0)

Other variants, from comments:

(a < 0) is not (b < 0) # True and False are singletons so identity-comparison works

(a < 0) ^ (b < 0) # bitwise-xor does too, as long as both sides are boolean

(a ^ b < 0) # or you could directly bitwise-xor the integers; 
            # the sign bit will only be set if your condition holds
            # this one fails when you mix ints and floats though

(a * b < 0) # perhaps most straightforward, just multiply them and check the sign
tzaman
  • 46,925
  • 11
  • 90
  • 115
  • Note that `(a < 0) ^ (b < 0)` also works, and it's using the xor operator. :) Alternatively, `(a < 0) is not (b < 0)`, since `True` and `False` are singletons, although some might object to that syntax: see here for a discussion: http://stackoverflow.com/q/9494404/4014959 – PM 2Ring Apr 22 '15 at 07:53
  • `^` for *xor* does work if booleans are compared but it won't work for all values like `and` and `or` would... – Antti Haapala -- Слава Україні Apr 22 '15 at 12:00
  • OP specifically tagged Python 3.x so I linked to Python 3 manuals. – Antti Haapala -- Слава Україні Apr 22 '15 at 12:04
  • I personally like a * b < 0, but one needs to keep in mind that in other languages this can cause integer overflow – Paradise Apr 22 '15 at 15:49
  • @OverlordAlex: Why could it not cause integer overflow in Python? – supercat Apr 22 '15 at 17:37
  • @supercat Python integers are unlimited precision. – tzaman Apr 22 '15 at 17:44
  • @supercat, to expand on tzaman's answer, python automatically promotes integers to longs when they would overflow, and longs in python are unlimited length (restricted only by available memory). At worst you'll get a float where you might expect an int. In python 3 there aren't any integers/longs, but rather a hybrid general "number", that is arbitrary length, so you dont even have to worry about long vs int – Paradise Apr 22 '15 at 20:17
  • @OverlordAlex: That makes sense. On the other hand, I was just noticing that while the OP's code seems to have been trying to identify if one number is negative and one number is non-negative, your formula tests for the condition described in the question title. If the question title relates what's actually desired, I don't remember if Python has a function to convert values into +1/0/-1 based on whether they're pos/neg/zero, but if so, perhaps applying that to the operands before the multiplication might be a good idea. – supercat Apr 22 '15 at 21:10
  • 1
    @supercat: Unfortunately python doesn't have a sign() method. One was proposed for the math package, but there wasn't agreement on edge-cases, so it wasn't integrated (according to a quick google). A workaround is using cmp(number, 0), which will return +1/0/-1. This would indeed work for the question – Paradise Apr 23 '15 at 08:10
7

Your code doesn't work as intended because != takes higher precedence than a < 0 and b < 0. As itzmeontv suggests in his answer, you can simply decide the precedence yourself by surrounding logical components with parentheses:

(a < 0) != (b < 0)

Your code attempts to evaluate a < (0 != b) < 0

[EDIT]

As tzaman rightly points out, the operators have the same precedence, but your code is attempting to evaluate (a < 0) and (0 != b) and (b < 0). Surrounding your logical components with parentheses will resolve this:

(a < 0) != (b < 0)

Operator precedence: https://docs.python.org/3/reference/expressions.html#operator-precedence

Comparisons (i.a. chaining): https://docs.python.org/3/reference/expressions.html#not-in

Community
  • 1
  • 1
EvenLisle
  • 4,672
  • 3
  • 24
  • 47
4

You can use this

return (a < 0) != (b < 0)

Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

So it becomes

(a < 0) and (0 != b) and (b < 0)

See https://docs.python.org/3/reference/expressions.html#not-in

itzMEonTV
  • 19,851
  • 4
  • 39
  • 49
0

In Python, comparison operators are of the same precedence, and they are non-associative. There is a separate rule for sequences of comparison operators, the chaining rule. Python documentation states about that:

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.

Further, a op1 b and b op2 c and ... y opN z evaluates left to right.

 a < 0 and 0 != b and b < 0  

a < 0 will evaluated to False, and the further evaluation will be stopped due to short-circuit evaluation. So, the whole expression will be evaluated as False.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
haccks
  • 104,019
  • 25
  • 176
  • 264
  • 1
    This is wrong! The documentation you're quoting right there states that the comparisons are chained. *NOT* the same thing as left-associative. – tzaman Apr 22 '15 at 13:56