{-# LANGUAGE ScopedTypeVariables #-}
module IntervalArithmeticSpec where
import           Data.Proxy
import           Numeric.Rounded.Hardware.Internal
import           Numeric.Rounded.Hardware.Interval
import           Numeric.Rounded.Hardware.Interval.Class (makeInterval, equalAsSet)
import           Test.Hspec
import           Test.Hspec.QuickCheck (prop)
import           Test.QuickCheck

data OrdPair a = OrdPair a a deriving (Eq, Show)

instance (Arbitrary a, Ord a) => Arbitrary (OrdPair a) where
  arbitrary = do x <- arbitrary
                 y <- arbitrary
                 return $ OrdPair (min x y) (max x y)

verifyImplementation :: forall a. (Arbitrary a, Ord a, Show a, RoundedFractional a, RoundedSqrt a, RealFloatConstants a, RealFloat a) => Proxy a -> Spec
verifyImplementation _ = do
  prop "intervalAdd" $ \(OrdPair (x :: a) y) (OrdPair x' y') ->
    let iv1, iv2 :: Interval a
        iv1 = makeInterval (Rounded x) (Rounded y) + makeInterval (Rounded x') (Rounded y')
        iv2 = makeInterval (Rounded $ roundedAdd TowardNegInf x x') (Rounded $ roundedAdd TowardInf y y')
    in iv1 `equalAsSet` iv2
  prop "intervalSub" $ \(OrdPair (x :: a) y) (OrdPair x' y') ->
    let iv1, iv2 :: Interval a
        iv1 = makeInterval (Rounded x) (Rounded y) - makeInterval (Rounded x') (Rounded y')
        iv2 = makeInterval (Rounded $ roundedSub TowardNegInf x y') (Rounded $ roundedSub TowardInf y x')
    in iv1 `equalAsSet` iv2
  prop "intervalSqrt" $ \(OrdPair (NonNegative (x :: a)) (NonNegative y)) ->
    let iv1, iv2 :: Interval a
        iv1 = sqrt (makeInterval (Rounded x) (Rounded y))
        iv2 = makeInterval (Rounded $ roundedSqrt TowardNegInf x) (Rounded $ roundedSqrt TowardInf y)
    in iv1 `equalAsSet` iv2

spec :: Spec
spec = do
  describe "Double" $ verifyImplementation (Proxy :: Proxy Double)
  describe "Float" $ verifyImplementation (Proxy :: Proxy Float)
