saolof
saolof

Reputation: 1661

Is there a simple (possibly obfuscated) math expression for the number of days in a given month?

For use in cases where a standard library is not available. Assume that the month is given as an unsigned integer.

I'd be interested in seeing the shortest arithmetic expression that gives the correct answer, allowing or disallowing bitwise operators & masks but not lookup tables. Partial expressions can be saved into a variable for readability to showcase the idea used.

Upvotes: 1

Views: 157

Answers (4)

Babajan
Babajan

Reputation: 382

unsigned int m, leapyr, y ;

//m = month range is 1 to 12

//y = year range is 00 to 99

leapyr = ( ( y & 0x03 ) && 1 ); //0 means leap year and 1 means Non leap year

m = 30 + ( ( m  & 1 ) ^ ( 1 && ( m & 8 ) ) ) - ( ( !( m & 13 ) ) ) - ( ( !( m & 13 ) ) & leapyr );

My answer is considering year. If you don't want to use assign leapyr variable 0 or 1 as you wish.

Upvotes: 0

saolof
saolof

Reputation: 1661

Brute force answer in pseudocode for readability:

monthlength(month,is_leapyear) :=
    oddmonth = ( month + (month >= 8) ? 1 : 0) % 2  // Or  (month ^ (month >> 3))&1 
    feb_days_offset = (month == 2) ? 2 - is_leapyear : 0
    return 30 + oddmonth - feb_days_offset

Where month >= 8 can also be implemented with a bitshift since it's just the fourth bit in an unsigned representation, so that oddmonth is (first bit) xor (fourth bit), which can be consisely written as (month ^ (month >> 3))&1 . Similarly, subtracting the feb offset can be thought of as flipping the second bit on febuary, and flipping the first bit during leap year february.

single line with no intermediate variables:

monthlength(month,isleapyear) := 30 + ( month + (month >= 8 ? 1 : 0)) % 2 - (month==2 ? (2 - isleapyear) : 0)

Alternatively, one that uses exclusively bitwise arithmetic and shifts using the tricks discussed above:

monthlength(month,leapyear) := 30 ^ (month==2)<<1 ^ (month==2)&leapyear ^ (month^month>>3)&1

Upvotes: 0

BeeOnRope
BeeOnRope

Reputation: 65046

Here's an approach that uses only four simple arithmetic and bitwise ops and a 26-bit constant:

int days_in_month(unsigned m) {
    //              121110 9 8 7 6 5 4 3 2 1 0
    return 28 + ((0b11101110111110111011001100u >> m * 2u) & 0b11);
}

If you also want to handle leap year (no mention of it in the question), you can take a similar approach, at the cost of a few more operations and a 50-bit constant:

int days_in_month2(unsigned m, bool ly) {
    return 28 + ((0b11101110111110111011011111101110111110111011001100u >> (m + 12*ly) * 2u) & 0b11);
}

If you are willing the pass the leap year in a different way, e.g., setting a bit like month | 16 to indicate leap year, it would be more efficient.

I assume you pass the month as 1 to 12, not 0 to 11.

Tests and generated asm can be seen on godbolt.

Upvotes: 7

chux
chux

Reputation: 154169

Variation om @BeeOnRope nice answer.

#include <stdbool.h>
int DaysPerMonth(int Month, bool IsLeapYear) {
  assert(Month >= 1 && Month <= 12);
  // 0b11101110111110111011001100u
  //   3 B   B   E   E   C   C
  return (((0x3BBEECCu | (IsLeapYear << 2*2)) >> Month*2) & 3) + 28;
}

#include <stdio.h>
int main() {
  for (int ly = 0; ly <= 1; ly++) {
    for (int m = 1; m <= 12; m++) {
      printf("(%2d %2d), ", m, DaysPerMonth(m,ly));
    }
    puts("");
  }
  return 0;
}

Upvotes: 2

Related Questions