Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 502
JBallin Avatar asked Dec 07 '22 21:12

JBallin


2 Answers

Include the parentheses. In English, the rule is:

  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.

like image 41
John Kugelman Avatar answered Dec 10 '22 09:12

John Kugelman


The parens affect what order your booleans take. ands are grouped together and resolved before ors are, so:

a and b or c

becomes:

(a and b) or c

if either BOTH a and b are truthy, OR if c is truthy, we get True.

With the parentheses you get:

a and (b or c)

Now you get True if both a is truthy and either b OR c is truthy.


As far as "correctness," as long as your code derives the correct result then "more correct" is only a matter of opinion. I would include parens where you feel like it makes the result more clear. For instance:

if (a and b) or c:

is more clear than

if a and b or c:

However it is NOT more clear (in my opinion) than:

if some_long_identifier and some_other_long_identifier or \
   some_third_long_identifier_on_another_line:

Your guide when writing Python code should be PEP8. PEP8 is quiet on when you should include stylistic parentheses (read: parens that follow the natural order of operations), so use your best judgement.


For leap years specifically, the logic is:

  1. If the year is evenly divisible by 4, go to step 2. ...
  2. If the year is evenly divisible by 100, go to step 3. ...
  3. If the year is evenly divisible by 400, go to step 4. ...
  4. The year is a leap year (it has 366 days).
  5. The year is not a leap year (it has 365 days).

In other words: 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)
like image 133
Adam Smith Avatar answered Dec 10 '22 10:12

Adam Smith