% Copyright (C) 2009 John Millikin % % This program is free software: you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation, either version 3 of the License, or % any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program. If not, see . \ignore{ \begin{code}
{-# 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)
\end{code} } \section{Parsing} \subsection{Public interface} \begin{code}
getFloat16be :: Get Float
getFloat16be = getFloat (ByteCount 2) splitBytes
\end{code} \begin{code}
getFloat16le :: Get Float
getFloat16le = getFloat (ByteCount 2) $ splitBytes . reverse
\end{code} \begin{code}
getFloat32be :: Get Float
getFloat32be = getFloat (ByteCount 4) splitBytes
\end{code} \begin{code}
getFloat32le :: Get Float
getFloat32le = getFloat (ByteCount 4) $ splitBytes . reverse
\end{code} \begin{code}
getFloat64be :: Get Double
getFloat64be = getFloat (ByteCount 8) splitBytes
\end{code} \begin{code}
getFloat64le :: Get Double
getFloat64le = getFloat (ByteCount 8) $ splitBytes . reverse
\end{code} \subsection{Implementation} Split the raw byte array into (sign, exponent, significand) components. The exponent and signifcand are drawn directly from the bits in the original float, and have not been unbiased or otherwise modified. \begin{code}
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
\end{code} \subsubsection{Encodings and special values} The next step depends on the value of the exponent $e$, size of the exponent field in bits $w$, and value of the significand. \begin{table}[h] \begin{center} \begin{tabular}{lrl} \toprule Exponent & Significand & Format \\ \midrule $0$ & $0$ & Zero \\ $0$ & $> 0$ & Denormalised \\ $1 \leq e \leq 2^w - 2$ & \textit{any} & Normalised \\ $2^w-1$ & $0$ & Infinity \\ $2^w-1$ & $> 0$ & NaN \\ \bottomrule \end{tabular} \end{center} \end{table} There's no built-in literals for Infinity or NaN, so they are constructed using the {\tt Read} instances for {\tt Double} and {\tt Float}. \begin{code}
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
\end{code} If a value is normalised, its significand has an implied {\tt 1} bit in its most-significant bit. The significand must be adjusted by this value before being passed to {\tt encodeField}. \begin{code}
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)
\end{code} For denormalised values, the implied {\tt 1} bit is the least-significant bit of the exponent. \begin{code}
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)
\end{code} By composing {\tt splitBytes} and {\tt merge}, the absolute value of the float is calculated. Before being returned to the calling function, it must be signed appropriately. \begin{code}
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
\end{code} \section{Serialising} \subsection{Public interface} \begin{code}
putFloat32be :: Float -> Put
putFloat32be = putFloat (ByteCount 4) id
\end{code} \begin{code}
putFloat32le :: Float -> Put
putFloat32le = putFloat (ByteCount 4) reverse
\end{code} \begin{code}
putFloat64be :: Double -> Put
putFloat64be = putFloat (ByteCount 8) id
\end{code} \begin{code}
putFloat64le :: Double -> Put
putFloat64le = putFloat (ByteCount 8) reverse
\end{code} \subsection{Implementation} Serialisation is similar to parsing. First, the float is converted to a structure representing raw bitfields. The values returned from {\tt decodeFloat} are clamped to their expected lengths before being stored in the {\tt RawFloat}. \begin{code}
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)
\end{code} Then, the {\tt RawFloat} is converted to a list of bytes by mashing all the fields together into an {\tt Integer}, and chopping up that integer in 8-bit blocks. \begin{code}
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
\end{code} {\tt clamp}, given a maximum bit count and a value, will strip any 1-bits in positions above the count. \begin{code}
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
\end{code} For merging the fields, just shift the starting integer over a bit and then \textsc{or} it with the next value. The weird parameter order allows easy composition. \begin{code}
mashBits :: (Bits a, Integral a) => a -> BitCount -> Integer -> Integer
mashBits _ 0 x = x
mashBits y n x = (x `bitShiftL` n) .|. fromIntegral y
\end{code} Given an integer, read it in 255-block increments starting from the LSB. Each increment is converted to a byte and added to the final list. \begin{code}
integerToBytes :: Integer -> ByteCount -> [Word8]
integerToBytes _ 0 = []
integerToBytes x n = bytes where
    bytes = integerToBytes (x `shiftR` 8) (n - 1) ++ [step]
    step = fromIntegral x .&. 0xFF
\end{code} Finally, the raw parsing is wrapped up in {\tt Put}. The second parameter allows the same code paths to be used for little- and big-endian serialisation. \begin{code}
putFloat :: (RealFloat a) => ByteCount -> ([Word8] -> [Word8]) -> a -> Put
putFloat width f x = putByteString $ B.pack bytes where
    bytes = f . rawToBytes . splitFloat width $ x
\end{code} \section{Raw float components} Information about the raw bit patterns in the float is stored in {\tt RawFloat}, so they don't have to be passed around to the various format cases. The exponent should be biased, and the significand shouldn't have it's implied MSB (if applicable). \begin{code}
data RawFloat = RawFloat
    { rawWidth            :: ByteCount
    , rawSign             :: Sign
    , rawExponent         :: Exponent
    , rawSignificand      :: Significand
    , rawExponentWidth    :: BitCount
    , rawSignificandWidth :: BitCount
    }
    deriving (Show)
\end{code} \section{Exponents} Calculate the proper size of the exponent field, in bits, given the size of the full structure. \begin{code}
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"
\end{code} \begin{code}
bias :: Exponent -> BitCount -> Exponent
bias e eWidth = e - (1 - (2 `pow` (eWidth - 1)))
\end{code} \begin{code}
unbias :: Exponent -> BitCount -> Exponent
unbias e eWidth = e + 1 - (2 `pow` (eWidth - 1))
\end{code} \section{Byte and bit counting} \begin{code}
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
\end{code} \section{Utility} Considering a byte list as a sequence of bits, slice it from start inclusive to end exclusive, and return the resulting bit sequence as an integer. \begin{code}
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
\end{code} Slice a single integer by start and end bit location \begin{code}
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
\end{code} Integral version of {\tt (**)} \begin{code}
pow :: (Integral a, Integral b, Integral c) => a -> b -> c
pow b e = floor $ fromIntegral b ** fromIntegral e
\end{code} Detect whether a float is {\tt $-$NaN} \begin{code}
isNegativeNaN :: RealFloat a => a -> Bool
isNegativeNaN x = isNaN x && (floor x > 0)
\end{code}