% 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}