JBallin JBallin - 3 months ago 17
Python Question

Leap Year Boolean Logic: Include Parentheses?

Which is "more correct (logically)"? Specific to Leap Year, not in general.


  1. With Parentheses

    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

  2. Without

    return year % 4 == 0 and year % 100 != 0 or year % 400 == 0






Additional Info

Parentheses change the order in which the booleans are evaluated (
and
goes before
or
w/o parenthesis).


Given that all larger numbers are divisible by smaller numbers in this problem, it returns the correct result either way but I'm still curious.

Observe the effects of parentheses:


  1. False and True or True
    #True

    False and (True or True)
    #False

  2. False and False or True
    #True

    False and (False or True)
    #False



Without parentheses, there are scenarios where even though year is not divisible by 4 (first bool) it still returns True (I know that's impossible in this problem)! Isn't being divisible by 4 a MUST and therefore it's more correct to include parenthesis? Anything else I should be paying attention to here? Can someone explain the theoretical logic of not/including parentheses?

Answer

Answer: Include Parentheses


John Kugelman explains why they are 2 separate logical tests as opposed to 3, last 2 should be grouped together:

  1. Year must be divisible by 4.
  2. Year must not be visible by 100, unless it's divisible by 400.

The version with parentheses matches this two-pronged rule best.

return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
       ^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            (1)                          (2)

As it happens, removing the parentheses does not break the code, but it leads to an unnatural version of the rules:

  1. Year must be divisible by 4, but not by 100; or
  2. Year must be divisible by 400.

That's not the way I think of the leap year rule.


Inspired by mrdomoboto, 100/400 are the exception!:

Year must be divisible by 4, 100 is an exception and 400 is an exception of the exception but they are still one exception in total (see above). This means that if year is not divisible by 4 then the whole thing must be False. The only way to ensure this is to put parens around the exception because False and bool will always return False.

See below examples of this from JBallin

  1. False and True or True
    #True
    
    False and (True or True)
    #False
    
  2. False and False or True
    #True
    
    False and (False or True)
    #False
    

Adam Smith translated the english into code:

All years divisible by 4 are leap years, unless they're divisible by 100 and NOT divisible by 400, which translates to:

return y % 4 == 0 and not (y % 100 == 0 and y % 400 != 0)

JBallin cited De Morgan's Laws:

not(a and b) = (not a or not b)

To convert the parens into the desired answer:

#move "not" inside parens
return y % 4 == 0 and (not y % 100 == 0 or not y % 400 != 0)
#convert parens using "DML"
return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)