为什么这段代码除以零?

时间:2011-12-17 07:27:05

标签: haskell divide-by-zero

我有一个小的Haskell程序,很好奇为什么在运行它时会抛出除零异常(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]

有谁能帮我理解这里发生了什么?

2 个答案:

答案 0 :(得分:17)

我们可以将其减少到

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

请注意,如果您使用Word16,Int,Integer或任意数量的类型,这会有效,但在使用Word8时失败,因为B.unpack给了我们!那为什么会失败呢?答案可在source codeCodec.Utils.toTwosComp中找到。您可以看到它调用toBase 256 (abs x),其中 x 是参数。

toBase的类型 - 未从Codec.Utils模块导出,并且源中没有显式类型签名,但您可以通过将定义放在文件中并询问GHCi的类型是什么来看到这一点(:t toBase),是

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

因此,明确注释类型,toTwosComp正在调用toBase (256 :: Word8) (abs x :: Word8)。什么是256 :: Word8

GHCi> 256 :: Word8
0

糟糕! 256> 255,所以我们不能在Word8中保存它,它会无声地溢出。 toBase,在其基本转换的过程中,除以所使用的基数,因此它最终除以零,产生你得到的行为。

解决方案是什么?将Word8s转换为fromIntegral的Ints,然后再将其传递给toTwosComp

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

就个人而言,这种行为让我有点担心,我认为toTwosComp本身应该进行这样的转换,可能是Integer,因此它适用于各种大小的整数类型;但这会导致性能损失,开发人员可能不喜欢这个想法。尽管如此,这是一个令人困惑的失败,需要潜水源才能理解。值得庆幸的是,它很容易解决。

答案 1 :(得分:5)

map (head . toTwosComp) [1, 2, 3, 4]

正常工作

map (head . toTwosComp) $ B.unpack $ B.pack [1, 2, 3, 4]

导致您描述的异常。让我们看看有什么区别。

> :t [1, 2, 3, 4]
[1, 2, 3, 4] :: Num t => [t]
> :t unpack $ pack $ [1, 2, 3, 4]
unpack $ pack $ [1,2,3,4] :: [Word8]

Word8可能导致问题。我们来看看

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

所以显然我们必须将Word8转换为其他整数类型。

> map (head . toTwosComp . fromIntegral) $ B.unpack $ B.pack [1, 2, 3, 4]
[1,2,3,4]

有效!