module RoundToIntegralSpec where import Data.Proxy import Numeric.Floating.IEEE import Numeric.Floating.IEEE.Internal import Test.Hspec import Test.Hspec.QuickCheck import Test.QuickCheck hiding (classify) import Util prop_roundToIntegral :: (RealFloat a, Show a) => Proxy a -> a -> Property prop_roundToIntegral _ x = isFinite x ==> let tiesToEven = round' x tiesToEvenInt = round x :: Integer tiesToAway = roundAway' x tiesToAwayInt = roundAway x :: Integer towardPositive = ceiling' x towardPositiveInt = ceiling x :: Integer towardNegative = floor' x towardNegativeInt = floor x :: Integer towardZero = truncate' x towardZeroInt = truncate x :: Integer sameInteger f i = round f === i .&&. f === fromInteger i in conjoin [ counterexample "tiesToEven" $ isFinite tiesToEven .&&. sameInteger tiesToEven tiesToEvenInt , counterexample "tiesToAway" $ isFinite tiesToAway .&&. sameInteger tiesToAway tiesToAwayInt , counterexample "towardPositive" $ isFinite towardPositive .&&. sameInteger towardPositive towardPositiveInt , counterexample "towardNegative" $ isFinite towardNegative .&&. sameInteger towardNegative towardNegativeInt , counterexample "towardZero" $ isFinite towardZero .&&. sameInteger towardZero towardZeroInt , counterexample "towardNegative <= original value" $ towardNegative <= x , counterexample "towardNegative <= tiesToEven" $ towardNegative <= tiesToEven , counterexample "towardNegative <= tiesToAway" $ towardNegative <= tiesToAway , counterexample "towardNegative <= towardPositive" $ towardNegative <= towardPositive , counterexample "towardNegative <= towardZero" $ towardNegative <= towardZero , counterexample "original value <= towardPositive" $ x <= towardPositive , counterexample "tiesToEven <= towardPositive" $ tiesToEven <= towardPositive , counterexample "tiesToAway <= towardPositive" $ tiesToAway <= towardPositive , counterexample "towardZero <= towardPositive" $ towardZero <= towardPositive , counterexample "abs towardZero <= abs (original value)" $ abs towardZero <= abs x , counterexample "abs towardZero <= abs tiesToEven" $ abs towardZero <= abs tiesToEven , counterexample "abs towardZero <= abs tiesToAway" $ abs towardZero <= abs tiesToAway , counterexample "abs towardZero <= abs towardPositive" $ abs towardZero <= abs towardPositive , counterexample "abs towardZero <= abs towardNegative" $ abs towardZero <= abs towardNegative ] data RoundResult a = RoundResult { resultTiesToEven :: a , resultTiesToAway :: a , resultTowardPositive :: a , resultTowardNegative :: a , resultTowardZero :: a } checkBehavior :: RealFloat a => Proxy a -> a -> RoundResult a -> RoundResult Integer -> Spec checkBehavior _ x result resultI = do it "tiesToEven" $ round' x `sameFloatP` resultTiesToEven result it "tiesToEven (Integer)" $ round x `shouldBe` resultTiesToEven resultI it "tiesToAway" $ roundAway' x `sameFloatP` resultTiesToAway result it "tiesToAway (Integer)" $ roundAway x `shouldBe` resultTiesToAway resultI it "ceiling" $ ceiling' x `sameFloatP` resultTowardPositive result it "ceiling (Integer)" $ ceiling x `shouldBe` resultTowardPositive resultI it "floor" $ floor' x `sameFloatP` resultTowardNegative result it "floor (Integer)" $ floor x `shouldBe` resultTowardNegative resultI it "truncate" $ truncate' x `sameFloatP` resultTowardZero result it "truncate (Integer)" $ truncate x `shouldBe` resultTowardZero resultI checkCases :: RealFloat a => Proxy a -> Spec checkCases proxy = do describe "0.5" $ checkBehavior proxy 0.5 RoundResult { resultTiesToEven = 0.0 , resultTiesToAway = 1.0 , resultTowardPositive = 1.0 , resultTowardNegative = 0.0 , resultTowardZero = 0.0 } RoundResult { resultTiesToEven = 0 , resultTiesToAway = 1 , resultTowardPositive = 1 , resultTowardNegative = 0 , resultTowardZero = 0 } describe "0.25" $ checkBehavior proxy 0.25 RoundResult { resultTiesToEven = 0.0 , resultTiesToAway = 0.0 , resultTowardPositive = 1.0 , resultTowardNegative = 0.0 , resultTowardZero = 0.0 } RoundResult { resultTiesToEven = 0 , resultTiesToAway = 0 , resultTowardPositive = 1 , resultTowardNegative = 0 , resultTowardZero = 0 } describe "-0.25" $ checkBehavior proxy (-0.25) RoundResult { resultTiesToEven = -0.0 , resultTiesToAway = -0.0 , resultTowardPositive = -0.0 , resultTowardNegative = -1.0 , resultTowardZero = -0.0 } RoundResult { resultTiesToEven = 0 , resultTiesToAway = 0 , resultTowardPositive = 0 , resultTowardNegative = -1 , resultTowardZero = 0 } describe "-0.5" $ checkBehavior proxy (-0.5) RoundResult { resultTiesToEven = -0.0 , resultTiesToAway = -1.0 , resultTowardPositive = -0.0 , resultTowardNegative = -1.0 , resultTowardZero = -0.0 } RoundResult { resultTiesToEven = 0 , resultTiesToAway = -1 , resultTowardPositive = 0 , resultTowardNegative = -1 , resultTowardZero = 0 } describe "4.5" $ checkBehavior proxy 4.5 RoundResult { resultTiesToEven = 4.0 , resultTiesToAway = 5.0 , resultTowardPositive = 5.0 , resultTowardNegative = 4.0 , resultTowardZero = 4.0 } RoundResult { resultTiesToEven = 4 , resultTiesToAway = 5 , resultTowardPositive = 5 , resultTowardNegative = 4 , resultTowardZero = 4 } describe "-5.5" $ checkBehavior proxy (-5.5) RoundResult { resultTiesToEven = -6.0 , resultTiesToAway = -6.0 , resultTowardPositive = -5.0 , resultTowardNegative = -6.0 , resultTowardZero = -5.0 } RoundResult { resultTiesToEven = -6 , resultTiesToAway = -6 , resultTowardPositive = -5 , resultTowardNegative = -6 , resultTowardZero = -5 } describe "-6.5" $ checkBehavior proxy (-6.5) RoundResult { resultTiesToEven = -6.0 , resultTiesToAway = -7.0 , resultTowardPositive = -6.0 , resultTowardNegative = -7.0 , resultTowardZero = -6.0 } RoundResult { resultTiesToEven = -6 , resultTiesToAway = -7 , resultTowardPositive = -6 , resultTowardNegative = -7 , resultTowardZero = -6 } {-# NOINLINE spec #-} spec :: Spec spec = do describe "Double" $ do let proxy :: Proxy Double proxy = Proxy prop "roundToIntegral" $ prop_roundToIntegral proxy checkCases proxy describe "Float" $ do let proxy :: Proxy Double proxy = Proxy prop "roundToIntegral" $ prop_roundToIntegral proxy checkCases proxy