Search code examples
haskelldivide-by-zero

Why does this code divide by zero?


I have a small Haskell program, and am curious why a divide by zero exception gets thrown when I run it (GHC 7.0.3)

import qualified Data.ByteString.Lazy as B
import Codec.Utils

convert :: B.ByteString -> [Octet]
convert bs = map (head . toTwosComp) $ B.unpack bs

main = putStrLn $ show $ convert $ B.pack [1, 2, 3, 4]

Can anyone help me understand what's going on here?


Solution

  • We can reduce this to

    GHCi> toTwosComp (1 :: Word8)
    *** Exception: divide by zero
    

    Note that this works if you use Word16, Int, Integer, or any number of types, but fails when using Word8, as B.unpack gives us! So why does it fail? The answer is found in the source code to Codec.Utils.toTwosComp. You can see that it calls toBase 256 (abs x), where x is the argument.

    The type of toBase — not exported from the Codec.Utils module, and without an explicit type signature in the source, but you can see this by putting the definition in a file and asking GHCi what the type is (:t toBase), is

    toBase :: (Integral a, Num b) => a -> a -> [b]
    

    So, annotating types explicitly, toTwosComp is calling toBase (256 :: Word8) (abs x :: Word8). What's 256 :: Word8?

    GHCi> 256 :: Word8
    0
    

    Oops! 256 > 255, so we can't hold it in a Word8, and it overflows silently. toBase, in the process of its base conversion, divides by the base being used, so it ends up dividing by zero, producing the behaviour you're getting.

    What's the solution? Convert the Word8s to Ints with fromIntegral before passing them to toTwosComp:

    convert :: B.ByteString -> [Octet]
    convert = map convert' . B.unpack
      where convert' b = head $ toTwosComp (fromIntegral b :: Int)
    

    Personally, this behaviour worries me a bit, and I think toTwosComp should probably do such a conversion itself, likely to Integer, so that it works with integral types of every size; but this would incur a performance penalty that the developers might not like the idea of. Still, this is a pretty confusing failure that requires source-diving to understand. Thankfully, it's very easy to work around.