{-# LANGUAGE HexFloatLiterals #-}
{-# LANGUAGE ScopedTypeVariables #-}
module AugmentedArithSpec where
import           Control.Monad
import           Numeric
import           Numeric.Floating.IEEE
import           Numeric.Floating.IEEE.Internal
import           RoundingSpec (RoundTiesTowardZero (..))
import           Test.Hspec
import           Test.Hspec.QuickCheck
import           Test.QuickCheck hiding (classify)
import           Util

augmentedAddition_viaRational :: (RealFloat a, Show a) => a -> a -> (a, a)
augmentedAddition_viaRational x y
  | isFinite x && isFinite y && (x /= 0 || y /= 0) =
    let z :: Rational
        z = toRational x + toRational y
        z' = roundTiesTowardZero (fromRationalR z) `asTypeOf` x
    in if isInfinite z' then
         (z', z')
       else
         let w :: Rational
             w = z - toRational z'
             w' = roundTiesTowardZero (fromRationalR w) `asTypeOf` x
         in if w == 0 then
              (z', 0 * z')
            else
              (z', w')
  | otherwise = let z = x + y
                in (z, z)

augmentedMultiplication_viaRational :: (RealFloat a, Show a) => a -> a -> (a, a)
augmentedMultiplication_viaRational x y
  | isFinite x && isFinite y && x * y /= 0 =
    let z :: Rational
        z = toRational x * toRational y
        z' = roundTiesTowardZero (fromRationalR z) `asTypeOf` x
    in if isInfinite z' then
         (z', z')
       else
         let w :: Rational
             w = z - toRational z'
             w' = roundTiesTowardZero (fromRationalR w) `asTypeOf` x
         in if w == 0 then
              (z', 0 * z')
            else
              (z', w')
  | otherwise = let z = x * y
                in (z, z)

testAugmented :: (RealFloat a, Show a) => (a -> a -> (a, a)) -> [(a, a, a, a)] -> Property
testAugmented f cases = conjoin
  [ let label = showHFloat a . showChar ' ' . showHFloat b $ ""
    in counterexample label $ f a b `sameFloatPairP` (r1,r2)
  | (a,b,r1,r2) <- cases
  ]

{-# NOINLINE spec #-}
spec :: Spec
spec = modifyMaxSuccess (* 100) $ do
  describe "Double" $ do
    do -- augmentedAddition
      prop "augmentedAddition/equality" $ forAllFloats2 $ \(x :: Double) y ->
        isFinite x && isFinite y ==>
        let (s,t) = augmentedAddition x y
        in isFinite s ==> isFinite t .&&. toRational s + toRational t === toRational x + toRational y
      let cases :: [(Double, Double, Double, Double)]
          cases = [ (-0, -0, -0, -0)
                  ]
      prop "augmentedAddition" $ testAugmented augmentedAddition cases
      prop "augmentedAddition_viaRational" $ testAugmented augmentedAddition_viaRational cases
      prop "augmentedAddition" $ forAllFloats2 $ \(x :: Double) y ->
        augmentedAddition x y `sameFloatPairP` augmentedAddition_viaRational x y

    do -- augmentedMultiplication
      let cases :: [(Double, Double, Double, Double)]
          cases = [ (-0x1.3deed726aad4p-1023, 0x1.e179bde0a1dd2p-1, -0x1.2afa79f9d38c6p-1023, 0x0p+0)
                  , (-0x1.8eb0e02044f68p-1022, -0x1.c93b83a5751c8p-2, 0x1.640b37f1b9d02p-1023,-0x0p+0)
                  , (0x1.b877a1cd61478p-1023, -0x1.7a77bb9df06dap-1, -0x1.459753aa4d2bep-1023, -0x0p+0)
                  , (-0x1.d25f2402fe726p-1, -0x1.0b42f4e9eb842p-1, 0x1.e6e335433c1f9p-2, -0x1.bb70c80f1834p-58)
                  ]
      prop "augmentedMultiplication" $ testAugmented augmentedMultiplication cases
      prop "augmentedMultiplication_viaRational" $ testAugmented augmentedMultiplication_viaRational cases
      prop "augmentedMultiplication" $ forAllFloats2 $ \(x :: Double) y ->
        augmentedMultiplication x y `sameFloatPairP` augmentedMultiplication_viaRational x y

  describe "Float" $ do
    do -- augmentedAddition
      prop "augmentedAddition/equality" $ forAllFloats2 $ \(x :: Float) y ->
        isFinite x && isFinite y ==>
        let (s,t) = augmentedAddition x y
        in isFinite s ==> isFinite t .&&. toRational s + toRational t === toRational x + toRational y
      let cases :: [(Float, Float, Float, Float)]
          cases = [(-0, -0, -0, -0)]
      prop "augmentedAddition" $ testAugmented augmentedAddition cases
      prop "augmentedAddition_viaRational" $ testAugmented augmentedAddition_viaRational cases
      prop "augmentedAddition" $ forAllFloats2 $ \(x :: Float) y ->
        augmentedAddition x y `sameFloatPairP` augmentedAddition_viaRational x y

    do -- augmentedMultiplication
      let cases :: [(Float, Float, Float, Float)]
          cases = [ (0x1.b8508p-130,  -0x1.93994p-4,  -0x1.5b17p-133,   -0x0p+0)
                  , (0x1.5433bcp-126, -0x1.69a04p-1,  -0x1.e091e8p-127, -0x0p+0)
                  , (0x1.c7363p-128,  -0x1.c5d164p-1, -0x1.937b98p-128, -0x0p+0)
                  , (-0x1.a31946p0,   -0x1p-127,       0x1.a31944p-127,  0x0p+0)
                  , (0x1.cp1,          0x1.24924ap-1,  0x1p1,            0x1.8p-24)
                  , (0x1.cp1,         -0x1.24924ap-1, -0x1p1,           -0x1.8p-24)
                  ]
      prop "augmentedMultiplication" $ testAugmented augmentedMultiplication cases
      prop "augmentedMultiplication_viaRational" $ testAugmented augmentedMultiplication_viaRational cases
      prop "augmentedMultiplication" $ forAllFloats2 $ \(x :: Float) y ->
        augmentedMultiplication x y `sameFloatPairP` augmentedMultiplication_viaRational x y
