Почему этот код делится на ноль? - PullRequest
16 голосов
/ 17 декабря 2011

У меня небольшая программа на 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 ]

17 голосов
/ 17 декабря 2011

Мы можем уменьшить это до

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

Обратите внимание, что это работает, если вы используете Word16, Int, Integer или любое количество типов, но не работает при использовании Word8, как нам дает B.unpack! Так почему же это не удается? Ответ находится в исходном коде до Codec.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

Oops! 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, чтобы оно работало с целочисленными типами любого размера; но это повлечет за собой снижение производительности, которое разработчикам может не понравиться. Тем не менее, это довольно запутанный сбой, который требует понимания исходного кода. К счастью, это очень легко обойти.

5 голосов
/ 17 декабря 2011
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]

Это работает!

...