drumath
drumath

Reputation: 170

Different behaviour when defined as a function and when computed as a variable

sorry for the title, I could not think of a better one. I wanted to show the fact that in a group of 23 people it's more likely that there are two with the same birthday than not when I wrote this function:

birth :: Int -> Double
birth n = (fromIntegral . product $ [365,364..366-n]) / (fromIntegral . product . replicate n $ 365)

which does not work properly while these three do

birth1 n = (fromIntegral . product $ [365,364..366-n])  / (fromIntegral $ 365^n)
birth2 n = (fromIntegral . product $ [365,364..366-n])  / (fromIntegral . product . replicate 23 $ 365)
birth3 n = (fromIntegral . product $ [365,364..366-23]) / (fromIntegral . product . replicate n $ 365)

The results:

birth  23 == -1.7444829681771515e-41
birth1 23 == 0.4927027656760146
birth2 23 == 0.4927027656760146
birth3 23 == 0.4927027656760146

The most surprising is that the first one is negative. Why is this behaviour? I renamed everything and tried again before posting this, nothing is imported except for Prelude.

Cheers

Upvotes: 1

Views: 105

Answers (2)

bheklilr
bheklilr

Reputation: 54058

The problem is that you're using Int instead of Integer. If you change the definition of birth to be:

-- Helper function
(//) :: (Integral a, Fractional b) => a -> a -> b
x // y = fromIntegral x / fromIntegral y

birth :: Integer -> Double
birth n = product [365,364..366-n] // (product . replicate (fromIntegral n) $ 365)

You'll get the right answer.

You unfortunately can't use an Integer for the first argument of replicate, so you have to convert it to an Int. You could alternatively use birth :: Int -> Double and product [365,364..366 - fromIntegral n] // ..., which would probably be safer.


As for your other definitions, birth1 could easily have the type Integer -> Double, as could birth2 because the n is only used in the numerator, while in birth3 you get away with Int -> Double because n is only used as the argument to replicate. In fact, if you type these definitions directly into GHCi, you see that the types of these functions are

birth :: Fractional a => Int -> b
birth1 :: (Fractional a, Integral b) => b -> a
birth2 :: (Fractional a, Integral b) => b -> a
birth3 :: Fractional a => Int -> a

So it's only birth and birth3 that restrict you to using Int, but because of the way you defined birth3, you still get the right answer.


Another way to do this with only Int is to perform the divisions as you go, then do the product. This keeps you from having to convert very large numbers, but it might be slower depending on n. You could do this as

birth :: Int -> Double
birth n = product $ take n $ zipWith (//) [365,364..] $ repeat 365

Which is arguably easier to read as well (I like it better, but that's just an opinion).

Upvotes: 4

jamshidh
jamshidh

Reputation: 12060

This is caused by an overflow....

Consider just part of the numerator alone

> product [366-(22::Int)..365]
4190530200557060096

> product [366-(23::Int)..365]
-1494178958273413120

Once you get past a certain point, the value goes negative.... This is because int only goes to 2^64 (one bit is used for the sign, so going to 2^63 will cause things to go negative).

> 2^63-1::Int
9223372036854775807

> 2^63::Int
-9223372036854775808

The Int type is actually cyclical, tied to the size of values on your system. Once you cross this magical value, the values cycle back through large negative numbers....

Upvotes: 4

Related Questions