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?
Because what happens is that if we can say that zero, 5, or basically any number, then that means that that "c" is not unique. So, in this scenario the first part doesn't work. So, that means that this is going to be undefined. So zero divided by zero is undefined.
So, when something is divided by zero, mathematicians simply state that the answer is 'undefined'. This is not a totally abstract idea as you can see it in action in the real world. Computer programmers who accidentally divide by zero will get their code stuck in an infinite loop, for instance.
The short answer is that 0 has no multiplicative inverse, and any attempt to define a real number as the multiplicative inverse of 0 would result in the contradiction 0 = 1.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With