2011-12-17 23 views
16

que tienen un pequeño programa Haskell, y tengo curiosidad por qué una división por cero excepción es lanzada cuando lo ejecuto (GHC 7.0.3)¿Por qué este código se divide por cero?

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] 

Puede alguien ayudarme a entender lo que está pasando aquí?

+2

Es un error en 'toTwosComp'. – augustss

Respuesta

17

Podemos reducir esto a

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

Tenga en cuenta que esto funciona si se utiliza Word16, Int, Integer, o cualquier número de tipos, pero falla al utilizar Word8, como B.unpack nos da! Entonces, ¿por qué falla? La respuesta se encuentra en el source code a Codec.Utils.toTwosComp. Puede ver que llama al toBase 256 (abs x), donde x es el argumento.

El tipo de toBase - no exportó desde el módulo Codec.Utils, y sin una firma de tipo explícito en la fuente, pero se puede ver esto poniendo la definición en un archivo y pidiendo GHCi lo que el tipo es (:t toBase) , es

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

Por lo tanto, la anotación de tipos explícita, toTwosComp está llamando toBase (256 :: Word8) (abs x :: Word8). ¿Cuál es 256 :: Word8?

GHCi> 256 :: Word8 
0 

¡Uy! 256> 255, por lo que no podemos mantenerlo en Word8 y se desborda silenciosamente. toBase, en el proceso de conversión de base, se divide por la base que se está utilizando, por lo que termina dividiéndose por cero, produciendo el comportamiento que está obteniendo.

¿Cuál es la solución? Convertir los Word8s a INT con fromIntegral antes de pasarlos a toTwosComp:

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

Personalmente, este comportamiento me preocupa un poco, y creo que toTwosComp probablemente deba realizar dicha conversión en sí, probablemente entero, para que funcione con tipos integrales de todos los tamaños; pero esto incurriría en una pena de rendimiento que a los desarrolladores podría no gustarles. Aún así, se trata de una falla bastante confusa que requiere comprensión de buceo. Afortunadamente, es muy fácil evitarlo.

+0

Super útil! Muchas gracias: D – Litherum

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

funciona bien mientras

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

provoca la excepción que has descrito. Veamos cuál es la diferencia.

> :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 puede estar causando el problema. Veamos

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

Así que aparentemente tenemos que convertir Word8 a otro tipo entero.

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

¡Funciona!

Cuestiones relacionadas