packages feed

deka-0.2.0.0: test/DataDir/DekaDir/QuadTest.hs

-- | Tests for the Quad module.
--
-- The object of these tests is not to test decNumber but, rather,
-- to test Deka to ensure there are no transposed arguments or other
-- glaring errors.  Also, ensures that the FFI binding behaves as it
-- should and that there are no side effects where there shouldn't
-- be any.
--
-- Every function that takes a Quad as an argument is tested to
-- ensure it does not modify that Quad.
--
-- encoding and decoding must also be thoroughly tested as this can
-- be quite error prone.
module DataDir.DekaDir.QuadTest where

import Control.Applicative
import Control.Exception (evaluate)
import qualified Data.ByteString.Char8 as BS8
import Control.Monad
import Test.Tasty
import qualified Data.Deka.Quad as E
import Test.Tasty.QuickCheck (testProperty)
import Test.QuickCheck hiding (maxSize)
import Test.QuickCheck.Monadic
import Data.Deka.Internal
import Data.Deka.Decnumber
import Data.Maybe
import Foreign

isLeft :: Either a b -> Bool
isLeft e = case e of { Left _ -> True; _ -> False }

isRight :: Either a b -> Bool
isRight e = case e of { Right _ -> True; _ -> False }

lenCoeff :: E.Decoded -> Maybe Int
lenCoeff dcd = fmap length . fmap E.unCoefficient
  $ case E.dValue dcd of
      E.Finite c _ -> Just c
      _ -> Nothing

-- | Maximum Integer for testing purposes.
maxInteger :: Integer
maxInteger = 10 ^ (100 :: Int)

-- | Minimum Integer for testing purposes.
minInteger :: Integer
minInteger = negate (10 ^ (100 :: Int))

-- | The largest number with the given number of digits.
biggestDigs :: Int -> Integer
biggestDigs i = 10 ^ i - 1

-- | The smallest positive number with the given number of digits.
smallestDigs :: Int -> Integer
smallestDigs i = 10 ^ (i - 1)

maxSize :: Int -> Gen a -> Gen a
maxSize s g = sized $ \o -> resize (min o s) g

numDigits :: (Num a, Show a) => a -> Int
numDigits = length . show . abs

increaseAbs :: E.Quad -> E.Ctx E.Quad
increaseAbs q = do
    let neg = E.isNegative q
    if neg
      then E.nextMinus q
      else E.nextPlus q

decreaseAbs :: E.Quad -> E.Ctx E.Quad
decreaseAbs q = do
  let neg = E.isNegative q
  if neg
    then E.nextPlus q
    else E.nextMinus q

-- # Generators

genSign :: Gen E.Sign
genSign = elements [ minBound..maxBound ]

genBinaryMSD :: Gen E.Digit
genBinaryMSD = return E.D1

genBinaryNonMSD :: Gen E.Digit
genBinaryNonMSD = elements [E.D0, E.D1]

binaryDigs :: (Gen E.Digit, Gen E.Digit)
binaryDigs = (genBinaryMSD, genBinaryNonMSD)

genDecimalMSD :: Gen E.Digit
genDecimalMSD = elements [ E.D1, E.D2, E.D3, E.D4, E.D5,
                           E.D6, E.D7, E.D8, E.D9 ]

genDecimalNonMSD :: Gen E.Digit
genDecimalNonMSD = elements
  [ E.D0, E.D1, E.D2, E.D3, E.D4, E.D5,
    E.D6, E.D7, E.D8, E.D9 ]

decimalDigs :: (Gen E.Digit, Gen E.Digit)
decimalDigs = (genDecimalMSD, genDecimalNonMSD)

-- | Given a length, generate a list of digits.  All lists generated
-- will be exactly the length given.
genDigits
  :: Int
  -- ^ Length
  -> (Gen E.Digit, Gen E.Digit)
  -- ^ Generate MSD, remaining digits
  -> Gen [E.Digit]
genDigits l (gm, gr) = do
  msd <- gm
  rs <- vectorOf (l - 1) gr
  return $ msd : rs

-- | Given a maximum length, generate lists of digits that are no
-- longer than the length given.  The list will be of a random
-- length, but it will be no longer than the larger of the size
-- parameter and the given maximum length.  The list will always be
-- at least one element long regardless of the maximum length passed
-- in.
sizedDigits
  :: Int
  -- ^ Maximum length. (Size parameter determines the maximum
  -- length, but it will not exceed this amount.)
  -> (Gen E.Digit, Gen E.Digit)
  -- ^ Generate MSD, remaining digits
  -> Gen [E.Digit]
sizedDigits m (gm, gr) = sized $ \s -> do
  let sz = max 1 s
      maxLen = min sz m
  len <- choose (1, maxLen)
  genDigits len (gm, gr)

-- ## Finite number generators

coeffDigits :: (Gen E.Digit, Gen E.Digit) -> Gen [E.Digit]
coeffDigits p = sized f
  where
    f x | x == 0 = oneof [ sizedDigits 0 p, return [E.D0] ]
        | otherwise = sizedDigits E.coefficientLen p

genFiniteDcd
  :: Gen E.Sign
  -> Gen [E.Digit]
  -- ^ Generate coefficient
  -> (E.Coefficient -> Gen Int)
  -- ^ Generate exponent
  -> Gen E.Decoded
genFiniteDcd gs gc ge = do
  s <- gs
  ds <- gc
  let coe = case E.coefficient ds of
        Nothing -> error "genFinite: coefficient failed"
        Just r -> r
  e <- ge coe
  let ex = case E.exponent e of
        Nothing -> error "genFiniteDcd: exponent failed"
        Just r -> r
  return $ E.Decoded s (E.Finite coe ex)

rangedExponent
  :: (Int, Int)
  -- ^ Minimum and maximum exponent.  Exponent will never exceed
  -- allowable values.
  -> Gen Int
rangedExponent (em, ex) = do
  let (mPE, xPE) = E.minMaxExp
      (mR, xR) = (max em mPE, min ex xPE)
  choose (mR, xR)

sizedExponent :: Gen Int
sizedExponent = sized $ \s ->
  let x = s ^ (2 :: Int)
  in rangedExponent (negate x, x)

fullExpRange :: Gen Int
fullExpRange = rangedExponent E.minMaxExp

-- ## Infinite number generators

genInfinite :: Gen E.Sign -> Gen E.Decoded
genInfinite gs = do
  s <- gs
  return $ E.Decoded s E.Infinite

-- ## NaN number generators

payloadDigits :: (Gen E.Digit, Gen E.Digit) -> Gen [E.Digit]
payloadDigits = sizedDigits E.payloadLen

genNaN :: Gen E.NaN
genNaN = elements [ E.Quiet, E.Signaling ]

genNaNDcd
  :: Gen E.Sign
  -> Gen E.NaN
  -> Gen [E.Digit]
  -- ^ Generate payload
  -> Gen E.Decoded
genNaNDcd gs gn gd = do
  s <- gs
  ds <- gd
  n <- gn
  let pay = case E.payload ds of
        Nothing -> error "genNaNDcd: payload failed"
        Just r -> r
  return $ E.Decoded s (E.NaN n pay)

-- ## Decoded generators

-- | Most general Decoded generator.  Generates throughout the
-- possible range of Decoded.  Depends on the size parameter.
genDecoded :: Gen E.Decoded
genDecoded = frequency [(4, genFinite), (1, inf), (1, nan)]
  where
    inf = genInfinite genSign
    nan = genNaNDcd genSign genNaN (payloadDigits decimalDigs)

-- | Generates finite decoded numbers.
genFinite :: Gen E.Decoded
genFinite = genFiniteDcd genSign (coeffDigits decimalDigs)
            (const sizedExponent)
 

-- ## Specialized finite generators

-- | Generates positive and negative zeroes.
genZero :: Gen E.Decoded
genZero = genFiniteDcd genSign (return [E.D0]) (const fullExpRange)

genNegZero :: Gen E.Decoded
genNegZero = genFiniteDcd (return E.Sign1) (return [E.D0])
  (const fullExpRange)

genPosZero :: Gen E.Decoded
genPosZero = genFiniteDcd (return E.Sign0) (return [E.D0])
  (const fullExpRange)

-- | Generates positive one.
genOne :: Gen E.Decoded
genOne = genFiniteDcd (return E.Sign0) gDigs gExp
  where
    gDigs = sizedDigits E.coefficientLen (return E.D1, return E.D0)
    gExp co = return . negate $ length (E.unCoefficient co) - 1

genSmallFinite :: Gen E.Decoded
genSmallFinite = maxSize 5 genFinite

-- | Generates two values that are equivalent, but with
-- different exponents.

genEquivalent :: Gen (E.Decoded, E.Decoded)
genEquivalent = do
  let genCoeff1 = sizedDigits (E.coefficientLen - 1) decimalDigs
      genExp1 c =
        let (l, h) = E.minMaxExp
            l' = l + (E.coefficientLen - (length . E.unCoefficient $ c))
        in choose (l', h)
  d1 <- genFiniteDcd genSign genCoeff1 genExp1
  let (c1, e1) = case E.dValue d1 of
        E.Finite c e -> (E.unCoefficient c, E.unExponent e)
        _ -> error "genEquivalent failed"
      maxMore = E.coefficientLen - length c1
  more <- choose (1, maxMore)
  let coeff2 = case E.coefficient (c1 ++ replicate more E.D0) of
        Nothing -> error "genEquivalent: coefficient failed"
        Just r -> r
      exp2 = case E.exponent (e1 - more) of
        Nothing -> error "genEquivalent: exponent failed"
        Just r -> r
      d2 = E.Decoded (E.dSign d1) (E.Finite coeff2 exp2)
  b <- arbitrary
  let r = if b then (d1, d2) else (d2, d1)
  return r



genNonZeroSmallFinite :: Gen E.Decoded
genNonZeroSmallFinite = maxSize 5 $ genFiniteDcd genSign
  gd ge
  where
    gd = sizedDigits E.coefficientLen decimalDigs
    ge = (const sizedExponent)

genInteger :: Gen E.Decoded
genInteger = genFiniteDcd genSign
  (coeffDigits decimalDigs) (const . return $ 0)

genLogical :: Gen E.Decoded
genLogical = genFiniteDcd (return E.Sign0)
  (coeffDigits binaryDigs) (const . return $ 0)

genNormal :: Gen E.Sign -> Gen [E.Digit] -> Gen E.Decoded
genNormal gs gc = genFiniteDcd gs gc ge
  where
    ge c = do
      let minNrml = E.unExponent $ E.minNormalExp c
          maxE = snd E.minMaxExp
      choose (minNrml, maxE)

genSubnormal :: Gen E.Sign -> Gen [E.Digit] -> Gen E.Decoded
genSubnormal gs gd = genFiniteDcd gs gd ge
  where
    ge c =
      let minNrml = E.unExponent . E.minNormalExp $ c
          minE = fst E.minMaxExp
          f | minE > minNrml - 1 = error "genSubnormal failed"
            | otherwise = choose (minE, minNrml - 1)
      in f

genPositive :: Gen E.Decoded
genPositive = genFiniteDcd (return E.Sign0) gd ge
  where
    gd = sizedDigits E.coefficientLen decimalDigs
    ge = (const sizedExponent)

genNegative :: Gen E.Decoded
genNegative = genFiniteDcd (return E.Sign1) gd ge
  where
    gd = sizedDigits E.coefficientLen decimalDigs
    ge = (const sizedExponent)

-- ## Specialized other generators

genSignaling :: Gen E.Decoded
genSignaling = genNaNDcd genSign (return E.Signaling)
  (payloadDigits decimalDigs)

genSigned :: Gen E.Decoded
genSigned = oneof
  [ genFiniteDcd (return E.Sign1) (coeffDigits decimalDigs) (const sizedExponent)
  , genNaNDcd (return E.Sign1) genNaN (payloadDigits decimalDigs)
  , genInfinite (return E.Sign1)
  ]

-- ## Other generators

genRound :: Gen E.Round
genRound = elements [ E.roundCeiling, E.roundUp, E.roundHalfUp,
  E.roundHalfEven, E.roundHalfDown, E.roundDown, E.roundFloor,
  E.round05Up ]

allFlags :: [E.Flag]
allFlags = [ E.divisionUndefined, E.divisionByZero,
  E.divisionImpossible, E.invalidOperation, E.inexact,
  E.underflow, E.overflow, E.conversionSyntax ]

genFlag :: Gen E.Flag
genFlag = elements allFlags

onePointFive :: E.Quad
onePointFive = E.evalCtx . E.fromByteString . BS8.pack $ "1.5"

-- # Test builders

associativity
  :: String
  -- ^ Name
  -> (E.Quad -> E.Quad -> E.Ctx E.Quad)
  -> TestTree
associativity n f = testProperty desc $
  forAll genSmallFinite $ \ dx ->
  forAll genSmallFinite $ \ dy ->
  forAll genSmallFinite $ \ dz ->
  let (noFlags, resIsZero) = E.evalCtx $ do
        let x = E.fromBCD dx
            y = E.fromBCD dy
            z = E.fromBCD dz
        r1 <- f x y >>= f z
        r2 <- f y z >>= f x
        let c = E.evalCtx $ E.compare r1 r2
            isZ = E.isZero c
        fl <- E.getStatus
        return (fl == E.emptyFlags, isZ)
  in noFlags ==> resIsZero
  where
    desc = n ++ " is associative on finite numbers"

commutativity
  :: String
  -- ^ Name
  -> (E.Quad -> E.Quad -> E.Ctx E.Quad)
  -> TestTree
commutativity n f = testProperty desc $
  forAll genSmallFinite $ \dx ->
  forAll genSmallFinite $ \dy ->
  let (noFlags, resIsZero) = E.evalCtx $ do
        let x = E.fromBCD dx
            y = E.fromBCD dy
        r1 <- f x y
        r2 <- f y x
        let isZ = E.compareTotal r1 r2 == EQ
        fl <- E.getStatus
        return (fl == E.emptyFlags, isZ)
  in noFlags ==> resIsZero
  where
    desc = n ++ " is commutative where there are no flags"

-- # Immutability test builders


inContext :: (Ptr C'decContext -> IO Bool) -> PropertyM IO Bool
inContext f =
  run $ alloca $ \pCtx -> do
    _ <- unsafe'c'decContextDefault pCtx c'DEC_INIT_DECQUAD
    f pCtx

{- Also for below, consider this code snippet:

module Main where

import Control.Exception (evaluate)
import System.IO.Unsafe (unsafePerformIO)

myThing :: String -> Int
myThing s = unsafePerformIO $ putStrLn s >> return 2

main :: IO ()
main = do
  x <- return . Just $ myThing "this will NOT be printed"
  _ <- evaluate x
  y <- return $ myThing "this will be printed"
  _ <- evaluate y
  _ <- evaluate $ myThing "this will be printed too"
  putStrLn "Done"

-}

-- | These functions assume that reducing the return type of the
-- subject function to WHNF will force any associated IO to occur.
-- For example, imuUni will work as intended if you apply it
-- like so:
--
-- > imuUni "okay" (fmap (fmap return) E.decClass)
--
-- In this case, the function passed as an argument to imuUni is
-- run, and the result (Quad) is reduced to WHNF.  This works as
-- intended because it forces the underlying function to perform its
-- IO.
--
-- This would not work, even though it is well-typed:
--
-- > imuUni "broken" (fmap (fmap (return . Just)))
--
-- because in this case, the value returned from the computation is
-- a Ctx Maybe.  Reducing the Maybe to WHNF will not force any
-- underlying IO to occurr, as this just gives you either a Maybe
-- data constructor or _|_.
imuUni
  :: String
  -- ^ Name
  -> (E.Quad -> E.Ctx a)
  -> TestTree
imuUni n f = testProperty desc $
  forAll genDecoded $ \dx ->
  monadicIO $
  let k cPtr = do
        d <- evaluate $ E.fromBCD dx
        dcd1 <- withForeignPtr (unQuad d) peek
        x <- unCtx (f d) cPtr
        _ <- evaluate x
        dcd2 <- withForeignPtr (unQuad d) peek
        return $ dcd1 == dcd2
  in inContext k >>= assert
  where
    desc = n ++ " (unary function) does not mutate only argument"


imuBinary1st
  :: Show a
  => String
  -- ^ Name
  -> (Gen a, a -> c)
  -> (E.Quad -> c -> E.Ctx b)
  -> TestTree
imuBinary1st n (genA, getC) f = testProperty desc $
  forAll genDecoded $ \dx ->
  forAll genA $ \a ->
  monadicIO $
  let k cPtr = do 
        d <- evaluate $ E.fromBCD dx
        dcd1 <- withForeignPtr (unQuad d) peek
        x <- unCtx (f d (getC a)) cPtr
        _ <- evaluate x
        dcd2 <- withForeignPtr (unQuad d) peek
        return $ dcd1 == dcd2
  in inContext k >>= assert
  where
    desc = n ++ " (binary function) does not mutate first argument"

imuBinary2nd
  :: Show a
  => String
  -- ^ Name
  -> (Gen a, a -> c)
  -> (c -> E.Quad -> E.Ctx b)
  -> TestTree
imuBinary2nd n (genA, getC) f = testProperty desc $
  forAll genDecoded $ \dx ->
  forAll genA $ \a ->
  monadicIO $
  let k cPtr = do
        d <- evaluate $ E.fromBCD dx
        dcd1 <- withForeignPtr (unQuad d) peek
        x <- unCtx (f (getC a) d) cPtr
        _ <- evaluate x
        dcd2 <- withForeignPtr (unQuad d) peek
        return $ dcd1 == dcd2
  in inContext k >>= assert
  where
    desc = n ++ " (binary function) does not mutate second argument"

imuBinary
  :: String
  -> (E.Quad -> E.Quad -> E.Ctx a)
  -> TestTree
imuBinary n f = testGroup ("immutability - " ++ n)
  [ imuBinary1st n (genDecoded, E.fromBCD) f
  , imuBinary2nd n (genDecoded, E.fromBCD) f
  ]

imuTernary
  :: String
  -> (E.Quad -> E.Quad -> E.Quad -> E.Ctx a)
  -> TestTree
imuTernary n f = testGroup (n ++ " (ternary function) - immutability")
  [ testProperty "first argument" $
    forAll gen3 $ \(ga, gb, gc) ->
    monadicIO $
    let k cPtr = do
          a <- evaluate $ E.fromBCD ga
          b <- evaluate $ E.fromBCD gb
          c <- evaluate $ E.fromBCD gc 
          dcd1 <- withForeignPtr (unQuad a) peek
          x <- unCtx (f a b c) cPtr
          _ <- evaluate x
          dcd2 <- withForeignPtr (unQuad a) peek
          return $ dcd1 == dcd2
    in inContext k >>= assert

  , testProperty "second argument" $
    forAll gen3 $ \(ga, gb, gc) ->
    monadicIO $
    let k cPtr = do
          a <- evaluate $ E.fromBCD ga
          b <- evaluate $ E.fromBCD gb
          c <- evaluate $ E.fromBCD gc 
          dcd1 <- withForeignPtr (unQuad b) peek
          x <- unCtx (f a b c) cPtr
          _ <- evaluate x
          dcd2 <- withForeignPtr (unQuad b) peek
          return $ dcd1 == dcd2
    in inContext k >>= assert

  , testProperty "third argument" $
    forAll gen3 $ \(ga, gb, gc) ->
    monadicIO $
    let k cPtr = do
          a <- evaluate $ E.fromBCD ga
          b <- evaluate $ E.fromBCD gb
          c <- evaluate $ E.fromBCD gc 
          dcd1 <- withForeignPtr (unQuad c) peek
          x <- unCtx (f a b c) cPtr
          _ <- evaluate x
          dcd2 <- withForeignPtr (unQuad c) peek
          return $ dcd1 == dcd2
    in inContext k >>= assert
  ]
  where
    gen3 = (,,) <$> genDecoded <*> genDecoded <*> genDecoded

identity
  :: String
  -- ^ Name of thing that is identity (e.g. zero)
  -> Gen E.Decoded
  -> (E.Quad -> E.Quad -> E.Ctx E.Quad)
  -> TestTree
identity n g f = testProperty name $
  forAll genFinite $ \ad ->
  forAll g $ \bd -> E.evalCtx $ do
    let a = E.fromBCD ad
        b = E.fromBCD bd
    r <- f a b
    c <- E.compare a r
    return $ E.isZero c
  where
    name = n ++ " is the identity for finite numbers"

eitherToOrd :: Either E.Quad Ordering -> Ordering
eitherToOrd = either toOrd id
  where
    toOrd x | E.isNegative x = LT
            | E.isZero x = EQ
            | E.isPositive x = GT
            | otherwise = error "eitherToOrd: unrecognized value"

comparison
  :: String
  -- ^ Name of function
  -> (E.Quad -> E.Ctx E.Quad)
  -- ^ How to make a larger Quad
  -> (E.Quad -> E.Ctx E.Quad)
  -- ^ How to make a smaller Quad
  -> (E.Quad -> E.Quad -> E.Ctx (Either E.Quad Ordering))
  -> TestTree

comparison n fB fS fC = testGroup (n ++ " comparisons")
  [ testProperty "x > y" $ forAll genNonZeroSmallFinite $
    \da -> E.evalCtx $ do
      let a = E.fromBCD da
      b <- fB a
      c <- fC b a
      return $ eitherToOrd c == GT

  , testProperty "x < y" $ forAll genNonZeroSmallFinite $
    \da -> E.evalCtx $ do
      let a = E.fromBCD da
      b <- fS a
      c <- fC b a
      return $ eitherToOrd c == LT

  , testProperty "x == x" $ forAll genNonZeroSmallFinite $
    \da -> E.evalCtx $ do
      let a = E.fromBCD da
      c <- fC a a
      return $ eitherToOrd c == EQ

  , testProperty "transitive" $ forAll genNonZeroSmallFinite $
    \da ->
    forAll genNonZeroSmallFinite $ \db -> E.evalCtx $ do
      let a = E.fromBCD da
          b = E.fromBCD db
      c <- fC a b
      case eitherToOrd c of
        EQ -> do
          c' <- fC b a
          return $ eitherToOrd c' == EQ
        o -> do
          c' <- fC b a
          let cOrd = eitherToOrd c'
          return $ case cOrd of
            LT -> o == GT
            GT -> o == LT
            EQ -> False
  ]

testMinMax
  :: String
  -> Bool
  -- ^ True if testing absolute values
  -> (E.Quad -> E.Quad -> E.Ctx E.Quad)
  -> TestTree
testMinMax n ab f = testProperty (n ++ " and compare") $
  forAll genSmallFinite $ \da ->
  forAll genSmallFinite $ \db -> E.evalCtx $ do
    let aa = E.fromBCD da
        bb = E.fromBCD db
    (a, b) <- if ab
      then do
        aaa <- E.abs aa
        bbb <- E.abs bb
        return $ (aaa, bbb)
      else return (aa, bb)
    r <- E.compare a b
    m <- f a b
    let z = E.isZero r
    if z
      then do
        r' <- E.compare m a
        r'' <- E.compare m b
        let zr' = E.isZero r'
            zr'' = E.isZero r''
        return $ zr' && zr''
      else do
        nw <- f b a
        r' <- E.compare nw m
        return $ E.isZero r' 


decodedSameQuantum :: E.Decoded -> E.Decoded -> Bool
decodedSameQuantum x y = case (E.dValue x, E.dValue y) of
  (E.Finite _ e1, E.Finite _ e2) -> e1 == e2
  (E.Infinite, E.Infinite) -> True
  (E.NaN _ _, E.NaN _ _) -> True
  _ -> False

-- | Tests that a boolean function succeeds and fails as it should.

testBoolean
  :: String
  -- ^ Name
  -> Gen E.Decoded
  -- ^ Generates decodes that should succeed
  -> (E.Decoded -> Bool)
  -- ^ This predicate returns True on successful decodes
  -> (E.Quad -> Bool)
  -- ^ Function to test
  -> TestTree
testBoolean n g pd f = testGroup n
  [ testProperty "predicate returns true on generated decodes" $
    forAll g $ \d -> pd d
  
  , testProperty "succeeds when it should" $
    forAll g $ \dcd ->
      let q = E.fromBCD dcd
      in f q

  , testProperty "fails when it should" $
    forAll (genDecoded `suchThat` (not . pd)) $ \dcd ->
      let q = E.fromBCD dcd
      in not $ f q

  , testProperty "decNumber and Deka predicate return same result"
    $ forAll genDecoded $ \dcd ->
      let q = E.fromBCD dcd
          b = f q
      in b == pd dcd
  ]

-- | Tests functions that deal with DecClass.
testDecClass
  :: E.DecClass
  -- ^ Class being tested
  -> Gen E.Decoded
  -- ^ Generates Decoded that are in this class
  -> (E.Decoded -> Bool)
  -- ^ This function should return True on Decoded that are in the
  -- class
  -> TestTree

testDecClass c ge f = testGroup (show c)
  [ testProperty "predicate returns True on generated decodes" $
    forAll ge f

  , testProperty "decClass returns matching class" $
    forAll ge $ \dcd -> let q = E.fromBCD dcd in E.decClass q == c

  , testProperty "decClass does not return matching class otherwise" $
    forAll (genDecoded `suchThat` (not . f)) $ \dcd ->
    let q = E.fromBCD dcd in E.decClass q /= c
  ]

genInt32 :: Gen C'int32_t
genInt32 = choose (minBound, maxBound)

genUInt32 :: Gen C'uint32_t
genUInt32 = choose (minBound, maxBound)

intConversion
  :: (Show a, Eq a)
  => String
  -- ^ Name
  -> Gen a
  -> (a -> E.Quad)
  -- ^ Convert from C int
  -> (E.Round -> E.Quad -> E.Ctx a)
  -- ^ Convert to C int
  -> TestTree
intConversion n gen fr to = testGroup (n ++ " conversions")
  [ testProperty "convert from C integer to Quad and back" $
    forAll genRound $ \r ->
    forAll gen $ \i ->
    let q = fr i
        (i', fl) = E.runCtx $ to r q
    in fl == E.emptyFlags && i' == i
  ]

-- | Tests that what is returned by an operation has the same
-- exponent and sign of the first operand.
sameSignExp
  :: (E.Quad -> E.Quad -> E.Ctx E.Quad)
  -> TestTree
sameSignExp f = testProperty
  "result has same sign and exponent as first argument" $
  forAll genFinite $ \d -> E.evalCtx $ do
    let x = E.fromBCD d
    r <- f x E.one
    let d' = E.toBCD r
        sameExp = case (E.dValue d, E.dValue d') of
          (E.Finite _ e, E.Finite _ e') -> e == e'
          _ -> False
    return $ E.dSign d == E.dSign d' && sameExp

-- # Tests

tests :: TestTree
tests = testGroup "Quad"
  [ testGroup "helper functions"
    [ testGroup "biggestDigs"
      [ testProperty "generates correct number of digits" $
        forAll (choose (1, 500)) $ \i ->
        numDigits (biggestDigs i) == i

      , testProperty "adding one increases number of digits" $
        forAll (choose (1, 500)) $ \i ->
        let r = biggestDigs i
            n = numDigits r
            n' = numDigits (r + 1)
        in n' == n + 1
      ]

      , testGroup "smallestDigs"
        [ testProperty "generates correct number of digits" $
          forAll (choose (1, 500)) $ \i ->
          numDigits (smallestDigs i) == i

        , testProperty "subtracting one decreases number of digits" $
          forAll (choose (1, 500)) $ \i ->
          let r = smallestDigs i
          in r > 1 ==> numDigits r - 1 == numDigits (r - 1)
        ]
    ]


  , testGroup "immutability"
    [ testGroup "conversions"
      [ imuUni "decClass" (fmap return E.decClass)
      , imuUni "toBCD" (fmap return E.toBCD)
      , imuUni "toByteString" (fmap return E.toByteString)
      , imuUni "toEngByteString" (fmap return E.toEngByteString)
      , imuBinary2nd "toInt32" (genRound, id) E.toInt32
      , imuBinary2nd "toInt32Exact" (genRound, id) E.toInt32Exact
      , imuBinary2nd "toUInt32" (genRound, id) E.toUInt32
      , imuBinary2nd "toUInt32Exact" (genRound, id) E.toUInt32Exact
      , imuUni "toIntegralExact" E.toIntegralExact
      , imuBinary2nd "toIntegralValue" (genRound, id) E.toIntegralValue
      ]

    , testGroup "arithmetic"
      [ imuBinary "add" E.add
      , imuBinary "subtract" E.subtract
      , imuBinary "multiply" E.multiply
      , imuTernary "fma" E.fma
      , imuBinary "divide" E.divide
      , imuBinary "divideInteger" E.divideInteger
      , imuBinary "remainder" E.remainder
      , imuBinary "remainderNear" E.remainderNear
      ]

    , testGroup "exponent and coefficient adjustment"
      [ imuBinary "quantize" E.quantize
      , imuUni "reduce" E.reduce
      ]

    , testGroup "comparisons"
      [ imuBinary "compare" E.compare
      , imuBinary "compareSignal" E.compareSignal
      , imuBinary "compareTotal"
        (fmap (fmap return) E.compareTotal)
      , imuBinary "compareTotalMag"
        (fmap (fmap return) E.compareTotalMag)
      , imuBinary "max" E.max
      , imuBinary "maxMag" E.maxMag
      , imuBinary "min" E.min
      , imuBinary "minMag" E.minMag
      , imuBinary "sameQuantum"
        (fmap (fmap return) E.sameQuantum)
      ]

    , let f s k = imuUni s (fmap return k) in
      testGroup "tests"
      [ f "isFinite" E.isFinite
      , f "isInfinite" E.isInfinite
      , f "isInteger" E.isInteger
      , f "isLogical" E.isLogical
      , f "isNaN" E.isNaN
      , f "isNegative" E.isNegative
      , f "isNormal" E.isNormal
      , f "isPositive" E.isPositive
      , f "isSignaling" E.isSignaling
      , f "isSigned" E.isSigned
      , f "isSubnormal" E.isSubnormal
      , f "isZero" E.isZero
      ]

    , testGroup "signs"
      [ imuUni "plus" E.plus
      , imuUni "minus" E.minus
      , imuUni "abs" E.abs
      , imuBinary "copySign" (fmap (fmap return) E.copySign)
      ]

    , testGroup "increment and decrement"
      [ imuUni "nextMinus" E.nextMinus
      , imuUni "nextPlus" E.nextPlus
      , imuBinary "nextToward" E.nextToward
      ]

    , testGroup "logical, bitwise, digit shifting"
      [ imuBinary "and" E.and
      , imuBinary "or" E.or
      , imuBinary "shift" E.shift
      , imuBinary "xor" E.xor
      , imuBinary "rotate" E.rotate
      , imuUni "invert" E.invert
      ]

    , testGroup "log and scale"
      [ imuUni "logB" E.logB
      , imuBinary "scaleB" E.scaleB
      ]

    , testGroup "attributes"
      [ imuUni "digits" (fmap return E.digits)
      ]
    ] -- immutability

  , testGroup "rounding"
    [ testProperty "default rounding is half even" $
      once . E.evalCtx $ do
        r <- E.getRound
        return $ r == E.roundHalfEven

    , testProperty "setRound works" $
      forAll genRound $ \r -> E.evalCtx $ do
        E.setRound r
        r' <- E.getRound
        return $ r == r'

    ] -- rounding

  , testGroup "flags"
    [ testProperty "no flags set initially" . once
      . E.evalCtx $ do
        fl <- E.getStatus
        return $ fl == E.emptyFlags
    ]

  , testGroup "classes"
    [ testDecClass E.sNan
      (genNaNDcd genSign (return E.Signaling) (payloadDigits decimalDigs))
      E.dIsNaN

    , testDecClass E.qNan
      (genNaNDcd genSign (return E.Quiet) (payloadDigits decimalDigs))
      E.dIsNaN

    , testDecClass E.negInf
      (genInfinite (return E.Sign1)) E.dIsNegInf

    , testDecClass E.negNormal
      (genNormal (return E.Sign1)
        (sizedDigits E.coefficientLen decimalDigs)) E.dIsNegNormal

    , testDecClass E.negSubnormal
      (genSubnormal (return E.Sign1)
        (sizedDigits (E.coefficientLen - 1) decimalDigs))
        E.dIsNegSubnormal

    , testDecClass E.negZero genNegZero E.dIsNegZero
    , testDecClass E.posZero genPosZero E.dIsPosZero

    , testDecClass E.posSubnormal
      (genSubnormal (return E.Sign0)
        (sizedDigits (E.coefficientLen - 1) decimalDigs))
        E.dIsPosSubnormal

    , testDecClass E.posNormal
      (genNormal (return E.Sign0)
        (sizedDigits E.coefficientLen decimalDigs)) E.dIsPosNormal

    , testDecClass E.posInf
      (genInfinite (return E.Sign0)) E.dIsPosInf

    ] -- classes

  , testGroup "string conversions"
    [ testProperty ("Decoded -> Quad -> ByteString"
        ++ " -> Quad -> Decoded") $
      forAll genDecoded $ \d ->
        let q = E.fromBCD d
            bs = E.toByteString q
            q' = E.evalCtx $ E.fromByteString bs
            d' = E.toBCD q'
            desc = "toByteString: " ++ BS8.unpack bs
              ++ " toBCD: " ++ show d'
        in printTestCase desc $ d' == d

    , testProperty ("fromBCD and (fromByteString . scientific) "
        ++ "give same result") $
      forAll genDecoded $ \d ->
      let qD = E.fromBCD d
          (qS, fl) = E.runCtx . E.fromByteString
                      . BS8.pack . E.scientific $ d
          compared = E.compareTotal qD qS == EQ
      in compared && fl == E.emptyFlags

    , testProperty ("fromBCD and (fromByteString . ordinary) "
        ++ "give results that compare equal") $
      forAll genDecoded $ \d ->
      let qD = E.fromBCD d
          str = E.ordinary d
          (qS, fl) = E.runCtx . E.fromByteString
                      . BS8.pack $ str
          cmpResult 
            | E.isNormal qD = E.compareOrd qD qS == Just EQ
            | otherwise = E.compareTotal qD qS == EQ
          noFlags f = f == E.emptyFlags
          desc = "string: " ++ str
            ++ " fromByteString result: " ++ show qS
      in noFlags fl ==> printTestCase desc cmpResult

    , testProperty "toByteString -> fromByteString" $
      forAll genDecoded $ \d ->
      let q = E.fromBCD d
          bs = E.toByteString q
          (q', fl) = E.runCtx . E.fromByteString $ bs
          cmpRes = E.compareTotal q q' == EQ
      in cmpRes && fl == E.emptyFlags

    , testProperty "toEngByteString -> fromByteString" $
      forAll genDecoded $ \d ->
      let q = E.fromBCD d
          bs = E.toEngByteString q
          (q', fl) = E.runCtx . E.fromByteString $ bs
          cmpRes = E.compareOrd q q' == Just EQ
          cmpResTot = E.compareTotal q q' == EQ
          res = if E.isFinite q then cmpRes else cmpResTot
      in fl == E.emptyFlags ==> res
    ] -- string conversions

  , testGroup "integer conversions"
    [ intConversion "int32" genInt32 E.fromInt32 E.toInt32
    , intConversion "uint32" genUInt32 E.fromUInt32 E.toUInt32
    , intConversion "int32 exact" genInt32 E.fromInt32 E.toInt32Exact
    , intConversion "uint32 exact" genUInt32 E.fromUInt32 E.toUInt32Exact
    ] -- integer conversions

  , testGroup "arithmetic"
    [ testGroup "add"
      [ associativity "add" E.add
      , commutativity "add" E.add
      , identity "zero" genZero E.add
      ]

    , testGroup "multiply"
      [ associativity "multiply" E.multiply
      , commutativity "multiply" E.multiply
      , identity "one" genOne E.multiply
      ]

    , testGroup "subtract"
      [ testProperty "is the inverse of add" $
        forAll genSmallFinite $ \da ->
        forAll genSmallFinite $ \db ->
        let (r, fl) = E.runCtx $ do
              let a = E.fromBCD da
                  b = E.fromBCD db
              r1 <- E.add a b
              r2 <- E.subtract r1 b
              c <- E.compare r2 a
              return $ E.isZero c
        in fl == E.emptyFlags ==> r

      , identity "zero" genZero E.subtract
      ]

    , testGroup "fused multiply add"
      [ testProperty "is same as multiply and add" $
        forAll genSmallFinite $ \da ->
        forAll genSmallFinite $ \db ->
        forAll genSmallFinite $ \dc ->
        let (r, fl) = E.runCtx $ do
              let a = E.fromBCD da
                  b = E.fromBCD db
                  c = E.fromBCD dc
              r1 <- E.multiply a b
              r2 <- E.add r1 c
              r2' <- E.fma a b c
              cm <- E.compare r2 r2'
              return $ E.isZero cm
        in fl == E.emptyFlags ==> r
      ]

    , testGroup "divide"
      [ identity "one" genOne E.divide ]

    , testGroup "divideInteger"
      [ testProperty "result has exponent 0" $
        forAll genSmallFinite $ \da ->
        forAll genSmallFinite $ \db ->
        let (e, fl) = E.runCtx $ do
              let a = E.fromBCD da
                  b = E.fromBCD db
              c <- E.divideInteger a b
              return $ E.isInteger c
        in fl == E.emptyFlags ==> e
      ]

    , testGroup "remainder"
      [ testProperty "x = int * y + rem" $
        forAll genSmallFinite $ \dx ->
        forAll genSmallFinite $ \dy ->
        let (r, fl) = E.runCtx $ do
              let x = E.fromBCD dx
                  y = E.fromBCD dy
              it <- E.divideInteger x y
              rm <- E.remainder x y
              i1 <- E.multiply it y
              i2 <- E.add i1 rm
              c <- E.compare i2 x
              return $ E.isZero c
        in fl == E.emptyFlags ==> r
      ]
      -- remainderNear - no test - not sure I understand the
      -- semantics

    ] -- arithmetic

  , testGroup "exponent and coefficient adjustment"
    [ testGroup "quantize"
      [ testProperty "result has same quantum" $
        forAll genSmallFinite $ \dx ->
        forAll genSmallFinite $ \dy ->
        let (r, fl) = E.runCtx $ do
              let x = E.fromBCD dx
                  y = E.fromBCD dy
              c <- E.quantize x y
              let getExp a = do
                    let dcd = E.toBCD a
                    return $ case E.dValue dcd of
                      E.Finite _ e -> Just e
                      _ -> Nothing
              exC <- getExp c
              exY <- getExp y
              let fin = E.isFinite c
              return $ fin && exC == exY
        in fl == E.emptyFlags ==> r
      ]

    , testGroup "reduce"
      [ testProperty "result is equivalent" $
        forAll genSmallFinite $ \dx -> E.evalCtx $ do
            let x = E.fromBCD dx
            r <- E.reduce x
            c <- E.compare r x
            return $ E.isZero c

      , testProperty "result has no trailing zeroes" $
        forAll genSmallFinite $ \dx -> E.evalCtx $ do
            let x = E.fromBCD dx
            r <- E.reduce x
            let dcd = E.toBCD r
            return $ case E.dValue dcd of
              E.Infinite -> False
              E.NaN _ _ -> False
              E.Finite c _ ->
                let digs = E.unCoefficient c
                in all (== E.D0) digs || last digs /= E.D0
      ]
    ] -- exponent and coefficient adjustment

  , testGroup "comparisons"
    [ comparison "compare" E.nextPlus E.nextMinus
        (fmap (fmap (fmap Left)) E.compare)

    , comparison "compareSignal" E.nextPlus
        E.nextMinus (fmap (fmap (fmap Left )) E.compareSignal)

    , comparison "compareTotal" E.nextPlus E.nextMinus
        (fmap (fmap (return . Right)) E.compareTotal)

    , comparison "compareTotalMag" increaseAbs decreaseAbs
          (fmap (fmap (return . Right)) E.compareTotalMag)

    , testMinMax "min" False E.min
    , testMinMax "max" False E.max
    , testMinMax "maxMag" True E.maxMag
    , testMinMax "minMag" True E.minMag

    , testGroup "sameQuantum"
      [ testProperty "is true for same Decoded" $
        forAll genDecoded $ \d ->
          let x = E.fromBCD d
          in E.sameQuantum x x

      , testProperty "is false for different Decoded" $
        forAll ( liftM2 (,) genDecoded genDecoded
                  `suchThat` (not . uncurry decodedSameQuantum))
        $ \p -> let qx = E.fromBCD . fst $ p
                    qy = E.fromBCD . snd $ p
                in not $ E.sameQuantum qx qy
      ]
    ] -- comparisons

  , testGroup "tests"
    [ testBoolean "isFinite" genFinite E.dIsFinite E.isFinite

    , testBoolean "isInfinite" (genInfinite genSign)
        E.dIsInfinite E.isInfinite

    , testGroup "isInteger"
      [ testBoolean "isInteger" genInteger E.dIsInteger E.isInteger

      , let e = fromMaybe (error "isInteger exponent failed")
              . E.exponent $ 2
            c = fromMaybe (error "isInteger coefficient failed")
              . E.coefficient $ [E.D3]
            dcd = E.Decoded E.Sign0 (E.Finite c e)
            d = E.fromBCD dcd
        in testProperty "returns False on 3 * 10 ^ 2" . once
            . not . E.isInteger $ d
      ]

    , testBoolean "isLogical" genLogical
        E.dIsLogical E.isLogical

    , testBoolean "isNaN"
      (genNaNDcd genSign genNaN (payloadDigits decimalDigs))
        E.dIsNaN E.isNaN

    , testBoolean "isNegative" genNegative
      E.dIsNegative E.isNegative

    , testBoolean "isNormal"
      (genNormal genSign (sizedDigits E.coefficientLen decimalDigs))
        E.dIsNormal E.isNormal

    , testBoolean "isPositive" genPositive
        E.dIsPositive E.isPositive

    , testBoolean "isSignaling" genSignaling
        E.dIsSignaling E.isSignaling

    , testBoolean "isSigned" genSigned
        E.dIsSigned E.isSigned

    , testBoolean "isSubnormal"
        (genSubnormal genSign (sizedDigits (E.coefficientLen - 1) decimalDigs))
        E.dIsSubnormal E.isSubnormal

    , testBoolean "isZero" genZero E.dIsZero E.isZero

    ] -- tests

  , testGroup "signs"
    [ testGroup "plus"
      [ testProperty "same as 0 + x where 0 has same exponent" $
        forAll genDecoded $ \d ->
        let e = case E.dValue d of
              E.Finite _ ex -> ex
              _ -> E.zeroExponent
            z = E.fromBCD $ E.Decoded E.Sign0
                  (E.Finite E.zeroCoefficient e)
            q = E.fromBCD d
            rAdd = E.evalCtx $ E.add z q
            rPlus = E.evalCtx $ E.plus q
        in E.compareTotal rAdd rPlus == EQ
      ]

    , testGroup "minus"
      [ testProperty "same as 0 - x where 0 has same exponent" $
        forAll genDecoded $ \d ->
        let e = case E.dValue d of
              E.Finite _ ex -> ex
              _ -> E.zeroExponent
            z = E.fromBCD $ E.Decoded E.Sign0
                  (E.Finite E.zeroCoefficient e)
            q = E.fromBCD d
            rSubt = E.evalCtx $ E.subtract z q
            rMinus = E.evalCtx $ E.minus q
        in E.compareTotal rSubt rMinus == EQ
      ]

    , testGroup "abs"
      [ testProperty "sign is correctly set" $
        forAll genDecoded $ \d ->
        let expected = case E.dValue d of
              E.Finite _ _ -> E.Sign0
              E.Infinite -> E.Sign0
              E.NaN _ _ -> E.dSign d
            q = E.fromBCD d
            actual = E.dSign . E.toBCD . E.evalCtx . E.abs $ q
        in actual == expected
      ]

    , testGroup "copySign"
      [ testProperty "z is copy of x with sign of y" $
        forAll genDecoded $ \dx ->
        forAll genDecoded $ \dy ->
        let expected = dx { E.dSign = E.dSign dy }
            (x, y) = (E.fromBCD dx, E.fromBCD dy)
            r = E.toBCD $ E.copySign x y
        in r == expected
      ]
    ] -- signs

  , testGroup "increment and decrement"
    [ testProperty "nextMinus returns smaller result" $
      forAll genFinite $ \d ->
      let q = E.fromBCD d
          (r, fl) = E.runCtx $ E.nextMinus q
          cmp = E.evalCtx $ E.compare r q
      in fl == E.emptyFlags ==> E.isNegative cmp

    , testProperty "nextPlus returns larger result" $
      forAll genFinite $ \d ->
      let q = E.fromBCD d
          (r, fl) = E.runCtx $ E.nextPlus q
          cmp = E.evalCtx $ E.compare r q
      in fl == E.emptyFlags ==> E.isPositive cmp

    , testProperty "nextToward does not change sign of comparison" $
      forAll genFinite $ \dx ->
      forAll genFinite $ \dy ->
      let x = E.fromBCD dx
          y = E.fromBCD dy
          cmp1 = E.evalCtx $ E.compare x y
          x' = E.evalCtx $ E.nextToward x y
          cmp2 = E.evalCtx $ E.compare x' y
          r | E.isNegative cmp1 = E.isNegative cmp2 || E.isZero cmp2
            | E.isZero cmp1 = E.isZero cmp2
            | otherwise = E.isPositive cmp2 || E.isZero cmp2
      in r

    ] -- increment and decrement

  , testGroup "digit-wise"
    [ testGroup "and"
      [ testProperty "x & 0 == 0" $
        forAll genLogical $ \d ->
        let q = E.fromBCD d
            r = E.evalCtx $ E.and q E.zero
        in E.isZero r
      ]

    , testGroup "or"
      [ testProperty "x | 0 == x" $
        forAll genLogical $ \d ->
        let r = E.evalCtx $ E.or x E.zero
            x = E.fromBCD d
        in E.compareOrd x r == Just EQ

      , testProperty "x | x == x" $
        forAll genLogical $ \d ->
        let r = E.evalCtx $ E.or x x
            cmp = E.compareTotal r x
            x = E.fromBCD d
        in cmp == EQ
      ]

    , testGroup "xor"
      [ testProperty "x XOR 0 == x" $
        forAll genLogical $ \d ->
        let r = E.evalCtx $ E.xor x E.zero
            cmp = E.compareTotal r x
            x = E.fromBCD d
        in cmp == EQ

      , testProperty "x XOR x == 0" $
        forAll genLogical $ \d ->
        let r = E.evalCtx $ E.xor x x
            x = E.fromBCD d
        in E.isZero r

      ]

    , testGroup "invert"
      [ testProperty "invert twice is idempotent" $
        forAll genLogical $ \d -> E.evalCtx $ do
          let q = E.fromBCD d
          r1 <- E.invert q
          r2 <- E.invert r1
          return $ E.compareOrd r2 q == Just EQ
      ]

    , testGroup "shift"
      [ sameSignExp E.shift
      ] -- shift

    , testGroup "rotate"
      [ sameSignExp E.rotate
      ]
    ] -- digit-wise

  , testGroup "log and scale"
    [ testGroup "logB"
      [ testProperty "returns adjusted exponent of finite numbers" $
        forAll genFinite $ \d -> E.evalCtx $ do
          let q = E.fromBCD d
          lg <- E.logB q
          i <- E.toInt32 E.roundUp lg
          let e = fromIntegral i
              r = case E.dValue d of
                E.Finite c ex ->
                  E.unAdjustedExp (E.adjustedExp c ex) == e
                _ -> False
          return r
      ]

    , testGroup "scaleB"
      [ testProperty "scaleB x 0 == x" $
        forAll genFinite $ \d -> E.evalCtx $ do
          let q = E.fromBCD d
          b <- E.scaleB q E.zero
          return $ E.compareOrd q b == Just EQ
      ]
    ] -- log and scale

  , testGroup "attributes"
    [ testGroup "digits"
      [ testProperty "gets same result as length of decoded coeff" $
        forAll genFinite $ \d ->
        let digs = E.digits . E.fromBCD $ d
        in case E.dValue d of
            E.Finite c _ -> length (E.unCoefficient c) == digs
            _ -> False
      ]
    ] -- attributes

  , testGroup "conversions"
    [ testGroup "decode and encode"
      [ testProperty "round trip from Decoded" $
        forAll genDecoded $ \d ->
        let r = E.toBCD . E.fromBCD $ d
        in printTestCase ("result: " ++ show r) (r == d)
      ]
    ] -- conversions

  ]  -- Quad