IEEE754.lhs

Plain text version of IEEE754.lhs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Binary.IEEE754 (
    -- * Parsing
      getFloat16be, getFloat16le
    , getFloat32be, getFloat32le
    , getFloat64be, getFloat64le

    -- * Serializing
    , putFloat32be, putFloat32le
    , putFloat64be, putFloat64le
) where

import Data.Bits ((.&.), (.|.), shiftL, shiftR, Bits)
import Data.Word (Word8)
import Data.List (foldl')

import qualified Data.ByteString as B
import Data.Binary.Get (Get, getByteString)
import Data.Binary.Put (Put, putByteString)








getFloat16be :: Get Float
getFloat16be = getFloat (ByteCount 2) splitBytes



getFloat16le :: Get Float
getFloat16le = getFloat (ByteCount 2) $ splitBytes . reverse



getFloat32be :: Get Float
getFloat32be = getFloat (ByteCount 4) splitBytes



getFloat32le :: Get Float
getFloat32le = getFloat (ByteCount 4) $ splitBytes . reverse



getFloat64be :: Get Double
getFloat64be = getFloat (ByteCount 8) splitBytes



getFloat64le :: Get Double
getFloat64le = getFloat (ByteCount 8) $ splitBytes . reverse









splitBytes :: [Word8] -> RawFloat
splitBytes bs = RawFloat width sign exp' sig expWidth sigWidth where
    width = ByteCount (length bs)
    nBits = bitsInWord8 bs
    sign = if head bs .&. 0x80 == 0x80
             then Negative
             else Positive

    expStart = 1
    expWidth = exponentWidth nBits
    expEnd = expStart + expWidth
    exp' = Exponent . fromIntegral $ bitSlice bs expStart expEnd

    sigWidth = nBits - expEnd
    sig  = Significand $ bitSlice bs expEnd nBits




























merge :: (Read a, RealFloat a) => RawFloat -> a
merge f@(RawFloat _ _ e sig eWidth _)
    | e == 0 = if sig == 0
                 then 0.0
                 else denormalised f
    | e == eMax - 1 = if sig == 0
                        then read "Infinity"
                        else read "NaN"
    | otherwise = normalised f
    where eMax = 2 `pow` eWidth







normalised :: RealFloat a => RawFloat -> a
normalised f = encodeFloat fraction exp' where
    Significand sig = rawSignificand f
    Exponent exp' = unbiased - sigWidth

    fraction = sig + (1 `bitShiftL` rawSignificandWidth f)

    sigWidth = fromIntegral $ rawSignificandWidth f
    unbiased = unbias (rawExponent f) (rawExponentWidth f)






denormalised :: RealFloat a => RawFloat -> a
denormalised f = encodeFloat sig exp' where
    Significand sig = rawSignificand f
    Exponent exp' = unbiased - sigWidth + 1

    sigWidth = fromIntegral $ rawSignificandWidth f
    unbiased = unbias (rawExponent f) (rawExponentWidth f)







getFloat :: (Read a, RealFloat a) => ByteCount
            -> ([Word8] -> RawFloat) -> Get a
getFloat (ByteCount width) parser = do
    raw <- fmap (parser . B.unpack) $ getByteString width
    let absFloat = merge raw
    return $ case rawSign raw of
               Positive ->  absFloat
               Negative -> -absFloat







putFloat32be :: Float -> Put
putFloat32be = putFloat (ByteCount 4) id



putFloat32le :: Float -> Put
putFloat32le = putFloat (ByteCount 4) reverse



putFloat64be :: Double -> Put
putFloat64be = putFloat (ByteCount 8) id



putFloat64le :: Double -> Put
putFloat64le = putFloat (ByteCount 8) reverse










splitFloat :: RealFloat a => ByteCount -> a -> RawFloat
splitFloat width x = raw where
    raw = RawFloat width sign clampedExp clampedSig expWidth sigWidth
    sign = if isNegativeNaN x || isNegativeZero x || x < 0
             then Negative
             else Positive
    clampedExp = clamp expWidth exp'
    clampedSig = clamp sigWidth sig
    (exp', sig) = case (dFraction, dExponent, biasedExp) of
                    (0, 0, _) -> (0, 0)
                    (_, _, 0) -> (0, Significand $ truncatedSig + 1)
                    _         -> (biasedExp, Significand truncatedSig)
    expWidth = exponentWidth $ bitCount width
    sigWidth = bitCount width - expWidth - 1 -- 1 for sign bit

    (dFraction, dExponent) = decodeFloat x

    rawExp = Exponent $ dExponent + fromIntegral sigWidth
    biasedExp = bias rawExp expWidth
    truncatedSig = abs dFraction - (1 `bitShiftL` sigWidth)







rawToBytes :: RawFloat -> [Word8]
rawToBytes raw = integerToBytes mashed width where
    RawFloat width sign exp' sig expWidth sigWidth = raw
    sign' :: Word8
    sign' = case sign of
              Positive -> 0
              Negative -> 1
    mashed = mashBits sig sigWidth .
             mashBits exp' expWidth .
             mashBits sign' 1 $ 0






clamp :: (Num a, Bits a) => BitCount -> a -> a
clamp = (.&.) . mask where
    mask 1 = 1
    mask n | n > 1 = (mask (n - 1) `shiftL` 1) + 1
    mask _ = undefined







mashBits :: (Bits a, Integral a) => a -> BitCount -> Integer -> Integer
mashBits _ 0 x = x
mashBits y n x = (x `bitShiftL` n) .|. fromIntegral y






integerToBytes :: Integer -> ByteCount -> [Word8]
integerToBytes _ 0 = []
integerToBytes x n = bytes where
    bytes = integerToBytes (x `shiftR` 8) (n - 1) ++ [step]
    step = fromIntegral x .&. 0xFF







putFloat :: (RealFloat a) => ByteCount -> ([Word8] -> [Word8]) -> a -> Put
putFloat width f x = putByteString $ B.pack bytes where
    bytes = f . rawToBytes . splitFloat width $ x










data RawFloat = RawFloat
    { rawWidth            :: ByteCount
    , rawSign             :: Sign
    , rawExponent         :: Exponent
    , rawSignificand      :: Significand
    , rawExponentWidth    :: BitCount
    , rawSignificandWidth :: BitCount
    }
    deriving (Show)








exponentWidth :: BitCount -> BitCount
exponentWidth k
    | k == 16         = 5
    | k == 32         = 8
    | k `mod` 32 == 0 = ceiling (4 * logBase 2 (fromIntegral k)) - 13
    | otherwise       = error "Invalid length of floating-point value"



bias :: Exponent -> BitCount -> Exponent
bias e eWidth = e - (1 - (2 `pow` (eWidth - 1)))



unbias :: Exponent -> BitCount -> Exponent
unbias e eWidth = e + 1 - (2 `pow` (eWidth - 1))





data Sign = Positive | Negative
    deriving (Show)

newtype Exponent = Exponent Int
    deriving (Show, Eq, Num, Ord, Real, Enum, Integral, Bits)

newtype Significand = Significand Integer
    deriving (Show, Eq, Num, Ord, Real, Enum, Integral, Bits)

newtype BitCount = BitCount Int
    deriving (Show, Eq, Num, Ord, Real, Enum, Integral)

newtype ByteCount = ByteCount Int
    deriving (Show, Eq, Num, Ord, Real, Enum, Integral)

bitCount :: ByteCount -> BitCount
bitCount (ByteCount x) = BitCount (x * 8)

bitsInWord8 :: [Word8] -> BitCount
bitsInWord8 = bitCount . ByteCount . length

bitShiftL :: (Bits a) => a -> BitCount -> a
bitShiftL x (BitCount n) = shiftL x n

bitShiftR :: (Bits a) => a -> BitCount -> a
bitShiftR x (BitCount n) = shiftR x n









bitSlice :: [Word8] -> BitCount -> BitCount -> Integer
bitSlice bs = sliceInt (foldl' step 0 bs) bitCount' where
    step acc w     = shiftL acc 8 + fromIntegral w
    bitCount'      = bitsInWord8 bs





sliceInt :: Integer -> BitCount -> BitCount -> BitCount -> Integer
sliceInt x xBitCount s e = fromIntegral sliced where
    sliced = (x .&. startMask) `bitShiftR` (xBitCount - e)
    startMask = n1Bits (xBitCount - s)
    n1Bits n  = (2 `pow` n) - 1





pow :: (Integral a, Integral b, Integral c) => a -> b -> c
pow b e = floor $ fromIntegral b ** fromIntegral e





isNegativeNaN :: RealFloat a => a -> Bool
isNegativeNaN x = isNaN x && (floor x > 0)