Is It A Leap Year? And, Number Of Days In February

  Needed a leap year function for a project. Then thought getting 28 or 29 days for a particular February would save selecting 28 or 29 based on true or false (cut the "middle-man"). The deciding arithmetic is the same for either. So in code snippets below you'll see both. Found 3 on PowerBASIC Forum, and one I wrote more then 15 years ago in my collection.

  They are discussed slowest to fastest. My old one with AND 3 as pseudo MOD 4, and the bit of assembly I've learned since, inspired the winner.

  Simplist part of a project can involve a greater effort. In the other project, that needed leap year, has a COMBOBOX for days in the month. By populating the COMBOBOX with 1 to 28 everytime, then then adding 29 to February in leap years, ans 29 and 30, or 29, 30 and 32 for other months, the leap code was reduced to do nothing or COMBOBOX ADD "29".

  FASTPROC and FUNCTION source code on popup page IsLeap-FebDays_Procs (that opens a new window or tab).


  The first found code snip I'll call "upside down" because it tests if years evenly divide by 400 first, then 100, finally 4. It is also the slowest. I'll go to next fastest all the way dowen. The coding for "upside down" is:

    IF Year MOD 400 = 0 THEN
      IsLeap = %True
    ELSEIF Year MOD 100 = 0 THEN
      IsLeap = %False
    ELSEIF Year MOD 4 = 0 THEN
      IsLeap = %True
    ELSE
      IsLeap = %False
    END IF
For a consecutive sequence of 400 years MOD 400 is used 400 times, MOD 100 is used 399 times, and MOD 4 is used 396 times.
400 + 399 + 396 = 1195 MODulo operations.
Comparison calculation is for method after the next.


  This method uses the "short-circuit evaluation" characteristic of PowerBasic's IF, in the order MOD 4, MOD 100 then MOD 400. It has the fewest lines of BASIC code and is faster than "upside down" algorithm, but slower than the "customary". Code:

    if ((Year mod 4) = 0) and _
       ((Year mod 100) > 0) or _
       ((Year mod 400) = 0) then
      FebDays = 29
    else
      FebDays = 28
    end if
There are implied IFs for Year MOD 100 > 0 and Year MOD 400, making the same number as "customary". OR and AND are not needed in "customary", which goes to nested IF on failure of "=" or ">". I believe this is why it is a bit slower than the next method.


  A more customary method starts with MOD 4, then MOD 100, doing MOD 400 last. This may be coded as:

    IF Year MOD 4 = 0 THEN
      IF Year MOD 100 = 0 THEN
        IF Year MOD 400 = 0 THEN
          IsLeap = %True
        ELSE
          IsLeap = %False
        END IF
      ELSE
        IsLeap = %True
      END IF
    ELSE
      IsLeap = %False
    END IF
For the same sequence, MOD 4 is used 400 times, MOD 100 100 times, and MOD 400 4 times.
400 + 100 + 4 = 504 MODulo operations; less than half the number in "upside‑down".

First Runner-up Source Code

(for those who are allergic to even copying assembly code )

  A significant speed improvement over the "more customary" method is because 4 is a power of 2 (is bit 2). An AND operation with 3 (both bits 0 and 1) gives the same result as MOD 4. For about three quarters of years the MOD 4 resolves leap year state, with the AND 3 which is significanly quicker.

  if (Year and 3) then       'conditionally equivilent to MOD op, 
    FebDays = 28             '(<> 0) not a leap year
  else                       '(= 0) it maybe leap year
    if (Year mod 100) then   '(<> 0) is a leap year
       FebDays = 29
    else                     '(= 0)
      if (Year mod 400) then '(<> 0) it is not a leap year
        FebDays = 28
      else                   '(= 0) it is a leap year
        FebDays = 29
      end if
    end if
  end if
(the "condition" is the divisor must be a power of 2)

"Winner" Source Code

  This assembly version would not be fastest (maybe by a tiny amount) except that the assembly DIV gives quotient and remainder in single operation. The divide by 100 quotient remains in register EAX, allowing the MOD 400 to be replaced by another AND 3. It becomes even quicker if the code it is being used with is also assembly, or put in a procedure alone because the PUSHes and POPs may be removed (procedure overhead is slower than those PUSHes and POPs).

  ! push eax          'for pseudo MOD, dividend and quotient
  ! mov FebDays, 28&  'not a leap year, pre-set to 28
  ! mov eax, 3???
  ! and eax, Year     'conditionally equivalent MOD 4
  ! jz MOD100         '0 is possible leap year
  ! jmp Done
  MOD100:
  ! push ebx          'for divisor
  ! push edx          'for high part of dividend, remainder (MOD)
  ! xor edx, edx
  ! mov eax, Year
  ! mov ebx, 100???
  ! div ebx
  ! cmp edx, 0???     'does MOD 100 = 0 ?
  ! pop edx
  ! pop ebx
  ! jz MOD400         '0 is possibly not a leap year
  ! jmp Is29Days      'non 0 is a leap year
  MOD400:
  ! and eax, 3???     'EAX has Year\100, so conditionally equivalent MOD 400
  ! jnz Done
  ! Is29Days:
  ! mov FebDays, 29&
  Done:
  ! pop eax
Usually I put the PUSHes together at the beginning and at the POPs at the end. A bit more speed is had by only PUSHing and POPing EBX and EDX for possible leap years (25% of years).
CMP not needed with AND because the ANDs set the flags, DIV doesn't.


Created on 07 September 2024.

To Domain Home.

home
To Dale's Notebook index.
notebook
To Programs index.
programs