6

How do you deal with relatively complex Boolean logic requirements?

Here's a simple example, of which I missed 50% of the cases because it was non-intuitive to me:

A year is a leap year if:
- it is divisible by 4
- except it is also divisible by 100
- unless it is also divisible by 400

To my intuition, the logic tree is as follows:

if (year % 4 == 0) -> true
if (year % 100 == 0) -> false
if (year % 400 == 0) -> true

so I ended up with 3 cases and I initially missed all the others until I started coding.

The full solution is:

if(year % 4 === 0) {
if(year % 100 === 0) {
if(year % 400 === 0) {
true
} else {
false
}
false
} else {
true
}
true
} else {
false
}
}

I don't like it when I don't immediately see all logic paths.

Comments
  • 0
    Darn it, I need to find out how to paste code here.
  • 8
    if y%100 == 0:

    return y%400 == 0

    else:

    return y%4 == 0
  • 0
    @hitko Interesting approach.
  • 1
    if !(a%400): return 1
    if !(a%100): return 0
    return !(a%4)
  • 2
    bool leap = !(n%400) || ( !(n%4) && (n%100) );
  • 0
    I'm not sure if my question was stated correctly because I asked not for a solution to this problem, but rather how a developer deals with complex Boolean logic in general.. as language can be a pitfall.
  • 5
    I don‘t know how you would improve finding the correct solution but I might have an advice how to make sure that the solution is correct:
    Unit tests!
  • 2
    This does not need complex boolean logic. This is a very simple case.

    For actually complex problems:
    Do not use the natural languages to think about it and it will be much easier.

    You can make a table, for 4 inputs:
    In row 0-1 input A is 0, in row 2-3 input A is 1.
    In row 0,3 input B is 0, in row 1,2 input B is 1.
    In column 0-1 input C is 0, in column 2-3 input C is 1.
    In column 0,3 input D is 0, in column 1,2 input D is 1.
    Write the output (1 or 0) in each cell.
    Combine neigthbor cells with the same output in a rectangle where possible, the rectangle can warp around from the left end to the right and and top to bottem. But the rectangle has only vertical and horizontal lines.
    From there you can get the minimum amount of logic gates or operations you need.
    I forgot how this tables are called.
  • 2
  • 4
    Easy: leap year is divisible by four, done.

    Explanation: Nobody cares because 1900 is long since and 2100 far away.
  • 0
    @Lensflare What if you work for a company who tells you not to write a single test because there's no time? :p
  • 1
    @CaptainRant None of this is complex. You've got one rule (leap years are divisible by 4), condition (is divisible by 100?), and an alternative rule (leap years are divisible by 400). All you need is to reorder the sentence so that the condition comes first, since that's how most computer languages are structured. It's more about basic semantics than boolean algebra.

    Also your initial approach would be perfectly correct if you just paid the attention to the fact that 400 is generally larger than 100:

    func leap(year){

    if (year % 400 == 0) -> return true

    if (year % 100 == 0) -> return false

    if (year % 4 == 0) -> return true

    return false

    }
  • 0
    @zlice Yes.. if a client writes specifications that are very conditional, then your if-else branches can turn quite annoying.. and sometimes non-intuitive..

    Example:

    Something you thought would be "and if it's in this range" actually forces you to write "and if NOT OR is true", which is messed up.. I've encountered it before.
  • 0
    @hitko Yeah, logic and semantics have always been my weakness. I should somehow improve on that.
  • 1
    return (year%4==0 && !year%100==0) || year%400==0
  • 4
    @Fast-Nop introducing the new Y2.1K bug scare now with extra nukes /s
  • 2
    You can just translate the given rule into an if tree. The resulting tree isn't too deep and performs well in this case.

    In the general case i still start with just translating condituions into an if tree. Then i transform that tree iteratively to reduce complexity.
  • 3
    You need to sing it like a Gregorian chant:

    If the year is evenly divisible by 400...

    It is a leap year...<whack>

    If the year is evenly divisible by 100...

    It is not a leap year... <whack>

    If the year is evenly divisible by 4...

    It is a leap year... <whack>
  • 0
    @CaptainRant Usually I would break it up.

    Avert nested condition like done in @iiii

    Simple reason to avert nested conditions is that order / priority can become a nasty bug that's near unidentifiable...

    Because with each added nested condition, the necessity for full evaluation usually decreases.

    Eg. in @iiii case, if the left brace evaluates to true, most compilers / script engines will short circuit and not evaluate the right part.

    The longer the condition, the more tricky evaluation becomes.

    A longer function might not be as short as a one liner, but it effectively prevents a complex evaluation - and while it still short circuits, you at least _know_ it does.

    E.g. in @hitko s case, you know each return terminates - without necessity to think about it.

    For some reason I know quite a lot of devs who love if / else constructs and hate early return strategy to prevent if / else... But I think early return and having a clean flow of conditions is far easier to read and understand than nested if / else conditions or a long line with several logic expressions.
Add Comment