Sunday 26 April 2009

The Mod Operator and Zeller's Congruence

Java's built in mod operator (%) behaves differently than the Clojure mod function with negative operators

Java:System.out.println("5 % 2 = " + (5 % 2)); // 1
Clojure:(mod -7 2) ; 1

Java:System.out.println("-2 % 7" + (-2 % 7)); // -2
Clojure:(mod -2 7) ; 5
Clojure:(rem -2 7) ; -2


The Java behaviour is explained more clearly by the JLS, here. rem should be the Clojure equivalent if you want to support the same behaviour as Java.

Why would the behaviour of negative numbers and the mod operator ever be useful? One algorithm that uses this is Zeller's Congruence which allows you to determine the day of the week if you know the date.

Defining the algorithm in Clojure is simple:


(def days
{0 'Saturday
1 'Sunday
2 'Monday
3 'Tuesday
4 'Wednesday
5 'Thursday
6 'Friday})

(defn zeller
"Zeller's congruence"
[day-of-month month year century]
(get days
(mod (+ day-of-month
(int (/ (* 26 (inc month)) 10))
year
(int (/ year 4))
(int (/ century 4))
(- (* 2 century))) 7)))


The algorithm is slightly strange in that January and February need to be treated as the 13th and 14th of the previous month. We can encapsulate this ugliness behind a nicer interface.


(def months
['January
'February
'March
'April
'May
'June
'July
'August
'September
'October
'November
'December])

(def month-to-number
(zipmap months [13 14 3 4 5 6 7 8 9 10 11 12]))


(defn day-of-week
[day month year]
(let [month-num (get month-to-number month)
year-int (if (or (= month-num 13) (= month-num 14)) (dec year) year)]


Now we can try this out:


user> (day-of-week 22 'November 1963) ; JFK assassination
Friday

user> (day-of-week 20 'July 1969) ; Apollo Moon landing
Sunday

user> (day-of-week 27 'April 2009)
Monday


Of course, any sane person would actually use a built in library function to do this! My rambling point is just that, Java % is not the same as Clojure mod!

No comments:

Post a Comment