decimal-arithmetic-0.5.0.0: src/Numeric/Decimal/Encoding.hs
{-# LANGUAGE FlexibleInstances #-}
-- | This module implements the decimal interchange format encodings described
-- in IEEE 754-2008, including the /decimal32/, /decimal64/, and /decimal128/
-- formats, as well as arbitrary width /decimal{k}/ formats through the use of
-- 'Format' with 'KPlus32' and\/or 'KTimes2'. For example, to use a
-- /decimal96/ format:
--
-- > type Decimal96 = ExtendedDecimal (Format (KPlus32 K64) DecimalCoefficient)
--
-- Currently only a decimal encoding of coefficients is implemented, but a
-- binary encoding may be added in the future.
module Numeric.Decimal.Encoding (
-- * Primary convenience types
Decimal32
, Decimal64
, Decimal128
-- ** Precision types
, Pdecimal32
, Pdecimal64
, Pdecimal128
-- * Interchange format types
, Format
-- ** Format parameters
, Parameters
, K32
, K64
, K128
, KPlus32
, KTimes2
-- ** Coefficient encodings
-- , CoefficientEncoding
, DecimalCoefficient
, BinaryCoefficient
) where
import Prelude hiding (exponent)
import Data.Binary (Binary(get, put), Get)
import Data.Binary.Bits.Get (BitGet, getBool, getWord8, getWord16be, runBitGet)
import Data.Binary.Bits.Put (BitPut, putBool, putWord8, putWord16be, runBitPut)
import Data.Bits (bit, shiftL, shiftR, testBit, (.&.), (.|.))
import Data.Word (Word8, Word16)
import Numeric.Decimal.Number
import Numeric.Decimal.Precision
-- Decimal number types
-- | A decimal floating point number with 7 digits of precision, rounding half
-- even, and a 32-bit encoded representation using the /decimal32/ interchange
-- format (with a decimal encoding for the coefficient)
type Decimal32 = ExtendedDecimal Pdecimal32
-- | A decimal floating point number with 16 digits of precision, rounding
-- half even, and a 64-bit encoded representation using the /decimal64/
-- interchange format (with a decimal encoding for the coefficient)
type Decimal64 = ExtendedDecimal Pdecimal64
-- | A decimal floating point number with 34 digits of precision, rounding
-- half even, and a 128-bit encoded representation using the /decimal128/
-- interchange format (with a decimal encoding for the coefficient)
type Decimal128 = ExtendedDecimal Pdecimal128
-- Precision types
-- | A type with 'Precision' instance specifying /decimal32/ interchange
-- format parameters (using a decimal encoding for the coefficient) having an
-- effective precision of 7 decimal digits
type Pdecimal32 = Format K32 DecimalCoefficient
-- | A type with 'Precision' instance specifying /decimal64/ interchange
-- format parameters (using a decimal encoding for the coefficient) having an
-- effective precision of 16 decimal digits
type Pdecimal64 = Format K64 DecimalCoefficient
-- | A type with 'Precision' instance specifying /decimal128/ interchange
-- format parameters (using a decimal encoding for the coefficient) having an
-- effective precision of 34 decimal digits
type Pdecimal128 = Format K128 DecimalCoefficient
-- Format parameters
-- | Interchange format parameters used to define an encoding and derive the
-- format's /precision/ and E/max/
class Parameters k where
-- | /k//32, the primary format parameter defining the encoding width as a
-- multiple of 32 bits
paramK32 :: k -> Int
-- | /p/, precision in digits
paramP :: Parameters k => k -> Int
paramP k = 9 * paramK32 k - 2
-- | /emax/
paramEmax :: Parameters k => k -> Exponent
paramEmax k = 3 * 2^(paramK32 k * 2 + 3)
-- | /bias/, /E/ − /q/
paramBias :: Parameters k => k -> Exponent
paramBias k = paramEmax k + fromIntegral (paramP k - 2)
-- | /w/, combination field width in bits − 5
paramW :: Parameters k => k -> Int
paramW k = paramK32 k * 2 + 4
-- | /t//10, trailing significand field width in 10-bit multiples
paramT10 :: Parameters k => k -> Int
paramT10 k = 3 * paramK32 k - 1
-- | Parameters for the /decimal32/ interchange format
data K32
instance Parameters K32 where
paramK32 _ = 1
-- | Parameters for the /decimal64/ interchange format
type K64 = KPlus32 K32
-- | Parameters for the /decimal128/ interchange format
type K128 = KTimes2 K64
-- | Parameters for a /decimal{@k@ + 32}/ interchange format
data KPlus32 k
instance Parameters k => Parameters (KPlus32 k) where
paramK32 t = paramK32 (minus32 t) + 1
where minus32 :: KPlus32 k -> k
minus32 = undefined
-- | Parameters for a /decimal{@k@ × 2}/ interchange format
data KTimes2 k
instance Parameters k => Parameters (KTimes2 k) where
paramK32 t = paramK32 (div2 t) * 2
where div2 :: KTimes2 k -> k
div2 = undefined
-- | A class encapsulating coefficient encodings
class CoefficientEncoding c
-- | Specify a decimal encoding for the coefficient.
data DecimalCoefficient
instance CoefficientEncoding DecimalCoefficient
-- | Specify a binary encoding for the coefficient (currently unimplemented).
data BinaryCoefficient
instance CoefficientEncoding BinaryCoefficient
-- | A type (with a 'Precision' instance) for specifying interchange format
-- parameters @k@ and coefficient encoding @c@
data Format k c
formatK :: Format k c -> k
formatK = undefined
-- | This 'Precision' instance automatically computes the /precision/ and
-- E/max/ of decimal numbers that use this format.
instance Parameters k => Precision (Format k c) where
precision = Just . paramP . formatK
eMax = Just . paramEmax . formatK
instance Parameters k => FinitePrecision (Format k c)
-- | A 'Binary' instance is defined for interchange formats for which a
-- 'Parameters' instance exists, and covers particularly the 'Decimal32',
-- 'Decimal64', and 'Decimal128' types.
instance Parameters k => Binary (Decimal (Format k DecimalCoefficient) r) where
put d = runBitPut $ putDecimal (paramW k) (paramT10 k) (paramBias k) d
where k = formatK (decimalFormat d)
decimalFormat :: Decimal (Format k c) r -> Format k c
decimalFormat = undefined
get = result
where result = runBitGet $ getDecimal (paramW k) (paramT10 k) (paramBias k)
k = formatK (getDecimalFormat result)
getDecimalFormat :: Get (Decimal (Format k c) r) -> Format k c
getDecimalFormat = undefined
-- Densely Packed Decimal
dpd2bcd :: Word16 -> (Word8, Word8, Word8)
dpd2bcd dpd = case mask 0 0xe of
0xe -> case mask 4 0x6 of
0x6 -> ( c7, f4, i0)
0x4 -> (a9b8c7, f4, i0)
0x2 -> ( c7, d9e8f4, i0)
_ -> ( c7, f4, g9h8i0)
0xc -> ( c7, d6e5f4, g9h8i0)
0xa -> (a9b8c7, f4, g6h5i0)
0x8 -> (a9b8c7, d6e5f4, i0)
_ -> (a9b8c7, d6e5f4, g2h1i0)
where a9b8c7 = mask 7 7
d6e5f4 = mask 4 7
d9e8f4 = mask 7 6 .|. mask 4 1
g2h1i0 = mask 0 7
g6h5i0 = mask 4 6 .|. mask 0 1
g9h8i0 = mask 7 6 .|. mask 0 1
i0 = 8 .|. mask 0 1
f4 = 8 .|. mask 4 1
c7 = 8 .|. mask 7 1
mask :: Int -> Word8 -> Word8
mask s m = fromIntegral (shiftR dpd s) .&. m
bcd2dpd :: Word8 -> Word8 -> Word8 -> Word16
bcd2dpd d2 d1 d0 = case (d2 < 8, d1 < 8, d0 < 8) of
(True , True , True ) -> a9b8c7 .|. d6e5f4 .|. g2h1i0
(True , True , False) -> a9b8c7 .|. d6e5f4 .|. 0x08 .|. i0
(True , False, True ) -> a9b8c7 .|. g6h5 .|. f4 .|. 0x0a .|. i0
(False, True , True ) -> g9h8 .|. c7 .|. d6e5f4 .|. 0x0c .|. i0
(False, False, True ) -> g9h8 .|. c7 .|. f4 .|. 0x0e .|. i0
(False, True , False) -> d9e8 .|. c7 .|. f4 .|. 0x2e .|. i0
(True , False, False) -> a9b8c7 .|. f4 .|. 0x4e .|. i0
(False, False, False) -> c7 .|. f4 .|. 0x6e .|. i0
where a9b8c7 = isolate d2 7 7
c7 = isolate d2 1 7
d6e5f4 = isolate d1 7 4
d9e8 = isolate d1 6 7
f4 = isolate d1 1 4
g2h1i0 = isolate d0 7 0
g6h5 = isolate d0 6 4
g9h8 = isolate d0 6 7
i0 = isolate d0 1 0
isolate :: Word8 -> Word8 -> Int -> Word16
isolate d m = shiftL (fromIntegral $ d .&. m)
-- Low-level encoding/decoding
data CombinationField = Finite { exponentMSBs :: Word8
, coefficientMSD :: Word8 }
| Infinity
| NotANumber
getCommon :: BitGet (Sign, CombinationField)
getCommon = do
sign <- toEnum . fromEnum <$> getBool
bits <- getWord8 5
let cf = case bits of
0x1e -> Infinity
0x1f -> NotANumber
_ -> let ab = shiftR bits 3 in case ab of
0x03 -> Finite { exponentMSBs = shiftR bits 1 .&. 0x03
, coefficientMSD = 0x08 .|. (bits .&. 0x01)
}
_ -> Finite { exponentMSBs = ab
, coefficientMSD = bits .&. 0x07
}
return (sign, cf)
putCommon :: Sign -> CombinationField -> BitPut ()
putCommon sign cf = do
putBool (toEnum . fromEnum $ sign)
let bits = case cf of
Finite { exponentMSBs = msbs, coefficientMSD = msd }
| msd < 8 -> shiftL msbs 3 .|. msd
| otherwise -> 0x18 .|. shiftL msbs 1 .|. (msd .&. 0x01)
Infinity -> 0x1e
NotANumber -> 0x1f
putWord8 5 bits
getCoefficient :: CombinationField -> Int -> BitGet Coefficient
getCoefficient = getCoefficient' . getMSD
where getCoefficient' :: Coefficient -> Int -> BitGet Coefficient
getCoefficient' ic 0 = return ic
getCoefficient' ic n = do
(a, b, c) <- dpd2bcd <$> getWord16be 10
let v = fromIntegral a * 100 + fromIntegral b * 10 + fromIntegral c
getCoefficient' (ic * 1000 + v) (pred n)
getMSD :: CombinationField -> Coefficient
getMSD Finite { coefficientMSD = msd } = fromIntegral msd
getMSD _ = 0
getDecimal :: Int -> Int -> Exponent -> BitGet (Decimal p r)
getDecimal ecbits cclen bias = do
(sign, cf) <- getCommon
ec <- getWord16be ecbits
coefficient <- getCoefficient cf cclen
return $ case cf of
Finite { exponentMSBs = msbs } ->
let ee = shiftL (fromIntegral msbs) ecbits .|. fromIntegral ec
in Num { sign = sign, coefficient = coefficient, exponent = ee - bias }
Infinity -> Inf { sign = sign }
NotANumber ->
let s = testBit ec (ecbits - 1)
in NaN { sign = sign, signaling = s, payload = coefficient }
putDecimal :: Int -> Int -> Exponent -> Decimal p r -> BitPut ()
putDecimal ecbits cclen bias x = do
let msd : cc = digits x
(cf, ee) = case x of
Num { exponent = e } ->
let cf = Finite { exponentMSBs = fromIntegral (shiftR ee ecbits)
, coefficientMSD = msd
}
in (cf, fromIntegral $ e + bias)
Inf{} -> (Infinity, 0)
NaN { signaling = s } -> (NotANumber, if s then bit (ecbits - 1) else 0)
putCommon (sign x) cf
putWord16be ecbits (ee .&. (bit ecbits - 1))
putDigits cc
where digits :: Decimal p r -> [Word8]
digits x = let ds = case x of
Num { coefficient = c } -> digits' c
NaN { payload = p } -> digits' p
Inf { } -> []
in replicate (1 + cclen * 3 - length ds) 0 ++ ds
digits' :: Coefficient -> [Word8]
digits' = go []
where go ds 0 = ds
go ds c = let (q, r) = c `quotRem` 10
in go (fromIntegral r : ds) q
putDigits :: [Word8] -> BitPut ()
putDigits (a : b : c : rest) = do
putWord16be 10 (bcd2dpd a b c)
putDigits rest
putDigits [] = return ()
putDigits _ = error "putDigits: invalid # digits"