16

我有一个小型 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]

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

4

2 回答 2

17

我们可以将其减少到

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

请注意,如果您使用 Word16、Int、Integer 或任意数量的类型,这将有效,但在使用 Word8 时会失败,正如B.unpack我们所提供的!那么为什么会失败呢?答案可以在Codec.Utils.toTwosComp的源代码中找到。您可以看到它调用,其中x是参数。toBase 256 (abs 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 转换为 Ints ,fromIntegral然后将它们传递给toTwosComp

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

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

于 2011-12-17T07:48:21.667 回答
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]

有用!

于 2011-12-17T07:49:31.590 回答