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