-- -- Copyright (c) 2009-2011, ERICSSON AB -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright notice, -- this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- * Neither the name of the ERICSSON AB nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- {-# LANGUAGE UndecidableInstances #-} -- | Bounded integer ranges module Feldspar.Range where -- TODO This module should be broken up into smaller pieces. Since most -- functions seem to be useful not only for Feldspar, it would probably be good -- to make a separate package. In any case, the modules should go under -- `Data.Range`. If there are functions that are very Feldspar specific, these -- should go into `Feldspar.Core.Constructs.*` (or whereever suitable). import Data.Bits import Data.Int import Data.Word import Data.Typeable import System.Random -- Should maybe be exported from QuickCheck import Test.QuickCheck hiding ((.&.)) import qualified Test.QuickCheck as QC import Text.Printf import Feldspar.Lattice -------------------------------------------------------------------------------- -- * Definition -------------------------------------------------------------------------------- -- | A bounded range of values of type @a@ data Range a = Range { lowerBound :: a , upperBound :: a } deriving (Eq, Show) -- | Convenience alias for bounded integers class (Ord a, Num a, Bounded a, Integral a, Bits a) => BoundedInt a instance (Ord a, Num a, Bounded a, Integral a, Bits a) => BoundedInt a -- | A convenience function for defining range propagation. -- @handleSign propU propS@ chooses @propU@ for unsigned types and -- @propS@ for signed types. handleSign :: forall a b . BoundedInt a => (Range a -> b) -> (Range a -> b) -> (Range a -> b) handleSign u s | isSigned (undefined::a) = s | otherwise = u -- | Shows a bound. showBound :: BoundedInt a => a -> String showBound a | a `elem` [maxBound,minBound] = "*" | otherwise = show a -- | A textual representation of ranges. showRange :: BoundedInt a => Range a -> String showRange r@(Range l u) | isEmpty r = "[]" | isSingleton r = show u | otherwise = "[" ++ showBound l ++ "," ++ showBound u ++ "]" -- | Requires a monotonic function mapMonotonic :: (a -> b) -> Range a -> Range b mapMonotonic f (Range l u) = Range (f l) (f u) -- | Requires a monotonic function mapMonotonic2 :: (a -> b -> c) -> Range a -> Range b -> Range c mapMonotonic2 f (Range l1 u1) (Range l2 u2) = Range (f l1 l2) (f u1 u2) -------------------------------------------------------------------------------- -- * Lattice operations -------------------------------------------------------------------------------- instance BoundedInt a => Lattice (Range a) where empty = emptyRange universal = fullRange (\/) = rangeUnion (/\) = rangeIntersection -- | The range containing no elements emptyRange :: BoundedInt a => Range a emptyRange = Range maxBound minBound -- | The range containing all elements of a type fullRange :: BoundedInt a => Range a fullRange = Range minBound maxBound -- | Construct a range range :: a -> a -> Range a range = Range -- | The range containing one element singletonRange :: a -> Range a singletonRange a = Range a a -- | The range from @0@ to the maximum element naturalRange :: BoundedInt a => Range a naturalRange = Range 0 maxBound -- | The range from the smallest negative element to @-1@. -- Undefined for unsigned types negativeRange :: forall a . BoundedInt a => Range a negativeRange | isSigned (undefined::a) = Range minBound (-1) | otherwise = emptyRange -- | The size of a range. Beware that the size may not always be representable -- for signed types. For instance -- @rangeSize (range minBound maxBound) :: Int@ gives a nonsense answer. rangeSize :: BoundedInt a => Range a -> a rangeSize (Range l u) = u-l+1 -- | Checks if the range is empty isEmpty :: BoundedInt a => Range a -> Bool isEmpty (Range l u) = u < l -- | Checks if the range contains all values of the type isFull :: BoundedInt a => Range a -> Bool isFull = (==fullRange) -- | Checks is the range contains exactly one element isSingleton :: BoundedInt a => Range a -> Bool isSingleton (Range l u) = l==u -- | @r1 \`isSubRangeOf\` r2@ checks is all the elements in @r1@ are included -- in @r2@ isSubRangeOf :: BoundedInt a => Range a -> Range a -> Bool isSubRangeOf r1@(Range l1 u1) r2@(Range l2 u2) | isEmpty r1 = True | isEmpty r2 = False | otherwise = (l1>=l2) && (u1<=u2) -- | Checks whether a range is a sub-range of the natural numbers. isNatural :: BoundedInt a => Range a -> Bool isNatural = (`isSubRangeOf` naturalRange) -- | Checks whether a range is a sub-range of the negative numbers. isNegative :: BoundedInt a => Range a -> Bool isNegative = (`isSubRangeOf` negativeRange) -- | @a \`inRange\` r@ checks is @a@ is an element of the range @r@. inRange :: BoundedInt a => a -> Range a -> Bool inRange a r = singletonRange a `isSubRangeOf` r -- | A convenience function for defining range propagation. If the input -- range is empty then the result is also empty. rangeOp :: BoundedInt a => (Range a -> Range a) -> (Range a -> Range a) rangeOp f r = if isEmpty r then r else f r -- | See 'rangeOp'. rangeOp2 :: BoundedInt a => (Range a -> Range a -> Range a) -> (Range a -> Range a -> Range a) rangeOp2 f r1 r2 | isEmpty r1 = r1 | isEmpty r2 = r2 | otherwise = f r1 r2 -- | Union on ranges. rangeUnion :: BoundedInt a => Range a -> Range a -> Range a r1 `rangeUnion` r2 | isEmpty r1 = r2 | isEmpty r2 = r1 | otherwise = union r1 r2 where union (Range l1 u1) (Range l2 u2) = Range (min l1 l2) (max u1 u2) -- | Intersection on ranges. rangeIntersection :: BoundedInt a => Range a -> Range a -> Range a rangeIntersection = rangeOp2 intersection where intersection (Range l1 u1) (Range l2 u2) = Range (max l1 l2) (min u1 u2) -- | @disjoint r1 r2@ returns true when @r1@ and @r2@ have no elements in -- common. disjoint :: BoundedInt a => Range a -> Range a -> Bool disjoint r1 r2 = isEmpty (r1 /\ r2) -- | @rangeGap r1 r2@ returns a range of all the elements between @r1@ and -- @r2@ including the boundary elements. If @r1@ and @r2@ have elements in -- common the result is an empty range. rangeGap :: BoundedInt a => Range a -> Range a -> Range a rangeGap = rangeOp2 gap where gap (Range l1 u1) (Range l2 u2) | u1 < l2 = range u1 l2 | u2 < l1 = range u2 l1 gap _ _ = emptyRange -- If the result is non-empty, it will include the boundary elements from the -- two ranges. -- | @r1 \`rangeLess\` r2:@ -- -- Checks if all elements of @r1@ are less than all elements of @r2@. rangeLess :: BoundedInt a => Range a -> Range a -> Bool rangeLess r1 r2 | isEmpty r1 || isEmpty r2 = True rangeLess (Range _ u1) (Range l2 _) = u1 < l2 -- | @r1 \`rangeLessEq\` r2:@ -- -- Checks if all elements of @r1@ are less than or equal to all elements of -- @r2@. rangeLessEq :: BoundedInt a => Range a -> Range a -> Bool rangeLessEq (Range _ u1) (Range l2 _) = u1 <= l2 -------------------------------------------------------------------------------- -- * Propagation -------------------------------------------------------------------------------- -- | @rangeByRange ra rb@: Computes the range of the following set -- -- > {x | a <- ra, b <- rb, x <- Range a b} rangeByRange :: BoundedInt a => Range a -> Range a -> Range a rangeByRange r1 r2 | isEmpty r1 = emptyRange | isEmpty r2 = emptyRange | otherwise = Range (lowerBound r1) (upperBound r2) -- | Implements 'fromInteger' as a 'singletonRange', and implements correct -- range propagation for arithmetic operations. instance BoundedInt a => Num (Range a) where fromInteger = singletonRange . fromInteger abs = rangeAbs signum = rangeSignum negate = rangeNeg (+) = rangeAdd (*) = rangeMul -- | Propagates range information through @abs@. rangeAbs :: BoundedInt a => Range a -> Range a rangeAbs = rangeOp $ \r -> case r of Range l u | isNatural r -> r | r == singletonRange minBound -> r | minBound `inRange` r -> range minBound maxBound | isNegative r -> range (abs u) (abs l) | otherwise -> range 0 (abs l `max` abs u) -- | Propagates range information through 'signum'. rangeSignum :: BoundedInt a => Range a -> Range a rangeSignum = handleSign rangeSignumUnsigned rangeSignumSigned -- | Signed case for 'rangeSignum'. rangeSignumSigned :: BoundedInt a => Range a -> Range a rangeSignumSigned = rangeOp sign where sign r | range (-1) 1 `isSubRangeOf` r = range (-1) 1 | range (-1) 0 `isSubRangeOf` r = range (-1) 0 | range 0 1 `isSubRangeOf` r = range 0 1 | inRange 0 r = 0 | isNatural r = 1 | isNegative r = -1 -- | Unsigned case for 'rangeSignum'. rangeSignumUnsigned :: BoundedInt a => Range a -> Range a rangeSignumUnsigned = rangeOp sign where sign r | r == singletonRange 0 = r | not (0 `inRange` r) = singletonRange 1 | otherwise = range 0 1 -- | Propagates range information through negation. rangeNeg :: BoundedInt a => Range a -> Range a rangeNeg = handleSign rangeNegUnsigned rangeNegSigned -- | Unsigned case for 'rangeNeg'. rangeNegUnsigned :: BoundedInt a => Range a -> Range a rangeNegUnsigned (Range l u) | l == 0 && u /= 0 = fullRange | otherwise = range (-u) (-l) -- Code from Hacker's Delight -- | Signed case for 'rangeNeg'. rangeNegSigned :: BoundedInt a => Range a -> Range a rangeNegSigned (Range l u) | l == minBound && u == minBound = singletonRange minBound | l == minBound = fullRange | otherwise = range (-u) (-l) -- Code from Hacker's Delight -- | Propagates range information through addition. rangeAdd :: BoundedInt a => Range a -> Range a -> Range a rangeAdd = handleSign rangeAddUnsigned rangeAddSigned -- | Unsigned case for 'rangeAdd'. rangeAddUnsigned :: BoundedInt a => Range a -> Range a -> Range a rangeAddUnsigned (Range l1 u1) (Range l2 u2) | s >= l1 && t < u1 = fullRange | otherwise = range s t where s = l1 + l2 t = u1 + u2 -- Code from Hacker's Delight -- | Signed case for 'rangeAdd'. rangeAddSigned :: BoundedInt a => Range a -> Range a -> Range a rangeAddSigned (Range l1 u1) (Range l2 u2) | (u .|. v) < 0 = fullRange | otherwise = range s t where s = l1 + l2 t = u1 + u2 u = l1 .&. l2 .&. complement s .&. complement (u1 .&. u2 .&. complement t) v = ((xor l1 l2) .|. complement (xor l1 s)) .&. (complement u1 .&. complement u2 .&. t) -- Code from Hacker's Delight -- | Propagates range information through subtraction. rangeSub :: BoundedInt a => Range a -> Range a -> Range a rangeSub = handleSign rangeSubUnsigned (-) -- | Unsigned case for 'rangeSub'. rangeSubUnsigned :: BoundedInt a => Range a -> Range a -> Range a rangeSubUnsigned (Range l1 u1) (Range l2 u2) | s > l1 && t <= u1 = fullRange | otherwise = range s t where s = l1 - u2 t = u1 - l2 -- Note: This is more accurate than the default definition using 'negate', -- because 'negate' always overflows for unsigned numbers. -- Code from Hacker's Delight -- | Saturating unsigned subtraction subSat :: BoundedInt a => a -> a -> a subSat a b = a - min a b -- | Range propagation for 'subSat' rangeSubSat :: BoundedInt a => Range a -> Range a -> Range a rangeSubSat r1 r2 = Range (subSat (lowerBound r1) (upperBound r2)) (subSat (upperBound r1) (lowerBound r2)) -- | Propagates range information through multiplication rangeMul :: BoundedInt a => Range a -> Range a -> Range a rangeMul = handleSign rangeMulUnsigned rangeMulSigned -- | Signed case for 'rangeMul'. rangeMulSigned :: forall a . BoundedInt a => Range a -> Range a -> Range a rangeMulSigned r1 r2 | r1 == singletonRange 0 || r2 == singletonRange 0 = singletonRange 0 -- The following case is important because the 'maxAbs' function doesn't -- work for 'minBound' on signed numbers. | lowerBound r1 == minBound || lowerBound r2 == minBound = range minBound maxBound | bits (maxAbs r1) + bits (maxAbs r2) <= bitSize (undefined :: a) - 1 = range (minimum [b1,b2,b3,b4]) (maximum [b1,b2,b3,b4]) | otherwise = range minBound maxBound where maxAbs (Range l u) = max (abs l) (abs u) b1 = lowerBound r1 * lowerBound r2 b2 = lowerBound r1 * upperBound r2 b3 = upperBound r1 * lowerBound r2 b4 = upperBound r1 * upperBound r2 -- | Unsigned case for 'rangeMul'. rangeMulUnsigned :: forall a . BoundedInt a => Range a -> Range a -> Range a rangeMulUnsigned r1 r2 | bits (upperBound r1) + bits (upperBound r2) <= bitSize (undefined :: a) = mapMonotonic2 (*) r1 r2 | otherwise = universal -- | Returns the position of the highest bit set to 1. Counting starts at 1. -- Beware! It doesn't terminate for negative numbers. bits :: Bits b => b -> Int bits b = loop b 0 where loop 0 c = c loop n c = loop (n `shiftR` 1) (c+1) -- | Propagates range information through exponentiation. rangeExp :: BoundedInt a => Range a -> Range a -> Range a rangeExp = handleSign rangeExpUnsigned rangeExpSigned -- | Unsigned case for 'rangeExp'. rangeExpUnsigned :: BoundedInt a => Range a -> Range a -> Range a rangeExpUnsigned m@(Range l1 u1) e@(Range l2 u2) | toInteger (bits u1) * toInteger u2 > toInteger (bitSize l1) + 1 = universal | toInteger u1 ^ toInteger u2 > toInteger (maxBound `asTypeOf` l1) = universal | 0 `inRange` m && 0 `inRange` e = range 0 (max b1 b2) | otherwise = range b1 b2 where b1 = (l1 ^ l2) b2 = (u1 ^ u2) -- | Sigend case for 'rangeExp' rangeExpSigned :: BoundedInt a => Range a -> Range a -> Range a rangeExpSigned m e | m == singletonRange (-1) = range (-1) 1 rangeExpSigned _ _ = universal -- | Propagates range information through '.|.'. rangeOr :: forall a . BoundedInt a => Range a -> Range a -> Range a rangeOr = handleSign rangeOrUnsignedAccurate (\_ _ -> universal) -- | Cheap and inaccurate range propagation for '.|.' on unsigned numbers. rangeOrUnsignedCheap :: BoundedInt a => Range a -> Range a -> Range a rangeOrUnsignedCheap (Range l1 u1) (Range l2 u2) = range (max l1 l2) (maxPlus u1 u2) -- Code from Hacker's Delight. -- | @a \`maxPlus\` b@ adds @a@ and @b@ but if the addition overflows then -- 'maxBound' is returned. maxPlus :: BoundedInt a => a -> a -> a maxPlus b d = if sum < b then maxBound else sum where sum = b + d -- | Accurate lower bound for '.|.' on unsigned numbers. minOrUnsigned :: BoundedInt a => a -> a -> a -> a -> a minOrUnsigned a b c d = loop (bit (bitSize a - 1)) where loop 0 = a .|. c loop m | complement a .&. c .&. m > 0 = let temp = (a .|. m) .&. negate m in if temp <= b then temp .|. c else loop (shiftR m 1) | a .&. complement c .&. m > 0 = let temp = (c .|. m) .&. negate m in if temp <= d then a .|. temp else loop (shiftR m 1) | otherwise = loop (shiftR m 1) -- Code from Hacker's Delight. -- | Accurate upper bound for '.|.' on unsigned numbers. maxOrUnsigned :: BoundedInt a => a -> a -> a -> a -> a maxOrUnsigned a b c d = loop (bit (bitSize a - 1)) where loop 0 = b .|. d loop m | b .&. d .&. m > 0 = let temp = (b - m) .|. (m - 1) in if temp >= a then temp .|. d else let temp = (d - m) .|. (m - 1) in if temp >= c then b .|. temp else loop (shiftR m 1) | otherwise = loop (shiftR m 1) -- Code from Hacker's Delight. -- | Accurate range propagation through '.|.' for unsigned types. rangeOrUnsignedAccurate :: BoundedInt a => Range a -> Range a -> Range a rangeOrUnsignedAccurate (Range l1 u1) (Range l2 u2) = range (minOrUnsigned l1 u1 l2 u2) (maxOrUnsigned l1 u1 l2 u2) -- Code from Hacker's Delight. -- | Propagating range information through '.&.'. rangeAnd :: forall a . BoundedInt a => Range a -> Range a -> Range a rangeAnd = handleSign rangeAndUnsignedCheap (\_ _ -> universal) -- | Cheap and inaccurate range propagation for '.&.' on unsigned numbers. rangeAndUnsignedCheap :: BoundedInt a => Range a -> Range a -> Range a rangeAndUnsignedCheap (Range l1 u1) (Range l2 u2) = range 0 (min u1 u2) -- Code from Hacker's Delight. -- | Propagating range information through 'xor'. rangeXor :: forall a . BoundedInt a => Range a -> Range a -> Range a rangeXor = handleSign rangeXorUnsigned (\_ _ -> universal) -- | Unsigned case for 'rangeXor'. rangeXorUnsigned :: BoundedInt a => Range a -> Range a -> Range a rangeXorUnsigned (Range l1 u1) (Range l2 u2) = range 0 (maxPlus u1 u2) -- Code from Hacker's Delight. -- | Propagating range information through 'shiftLU'. rangeShiftLU :: (BoundedInt a, BoundedInt b) => Range a -> Range b -> Range a rangeShiftLU = handleSign rangeShiftLUUnsigned (\_ _ -> universal) -- TODO: improve accuracy -- | Unsigned case for 'rangeShiftLU'. rangeShiftLUUnsigned (Range l1 u1) (Range l2 u2) | toInteger (bits u1) + fromIntegral u2 > toInteger (bitSize u1) = universal rangeShiftLUUnsigned (Range l1 u1) (Range l2 u2) = range (shiftL l1 (fromIntegral l2)) (shiftL u1 (fromIntegral u2)) -- | Propagating range information through 'shiftRU'. rangeShiftRU :: (BoundedInt a, BoundedInt b) => Range a -> Range b -> Range a rangeShiftRU = handleSign rangeShiftRUUnsigned (\_ _ -> universal) -- TODO: improve accuracy -- | Unsigned case for 'rangeShiftRU'. rangeShiftRUUnsigned (Range l1 u1) (Range l2 u2) = range (correctShiftRU l1 u2) (correctShiftRU u1 l2) -- | This is a replacement fror Haskell's shiftR. If we carelessly use -- Haskell's variant then we will get left shifts for very large shift values. correctShiftRU :: (Bits a, BoundedInt b) => a -> b -> a correctShiftRU a i | i > fromIntegral (maxBound :: Int) = 0 correctShiftRU a i = shiftR a (fromIntegral i) -- | Propagates range information through 'max'. rangeMax :: BoundedInt a => Range a -> Range a -> Range a rangeMax r1 r2 | isEmpty r1 = r2 | isEmpty r2 = r1 | r1 `rangeLess` r2 = r2 | r2 `rangeLess` r1 = r1 | otherwise = mapMonotonic2 max r1 r2 -- | Analogous to 'rangeMax' rangeMin :: BoundedInt a => Range a -> Range a -> Range a rangeMin r1 r2 | isEmpty r1 = r2 | isEmpty r2 = r1 | r1 `rangeLess` r2 = r1 | r2 `rangeLess` r1 = r2 | otherwise = mapMonotonic2 min r1 r2 instance BoundedInt a => Ord (Range a) where compare = error "compare: I don't make sense for (Range a)" min = rangeMin max = rangeMax -- | Propagates range information through 'mod'. -- Note that we assume Haskell semantics for 'mod'. rangeMod :: BoundedInt a => Range a -> Range a -> Range a rangeMod d r | isSigned (lowerBound d) && minBound `inRange` d && (-1) `inRange` r = fullRange | d `rangeLess` r && isNatural r && isNatural d = d | isNatural r = range 0 (pred (upperBound r)) | r `rangeLess` d && isNeg r && isNeg d = d | isNeg r = range (succ (lowerBound r)) 0 where isNeg = (`isSubRangeOf` negs) negs = negativeRange \/ 0 rangeMod d (Range l u) = Range (succ l) (pred u) -- | Propagates range information through 'rem'. -- Note that we assume Haskell semantics for 'rem'. rangeRem :: BoundedInt a => Range a -> Range a -> Range a rangeRem d r | isSigned (lowerBound d) && minBound `inRange` d && (-1) `inRange` r = fullRange | d `rangeLessAbs` r && isNatural d = d | isNatural d = range 0 (pred (upperBound (abs r))) | d `absRangeLessAbs` r && isNeg d = d | isNeg d = range (negate (upperBound (abs r))) 0 where isNeg = (`isSubRangeOf` negs) negs = negativeRange \/ 0 rangeRem d r@(Range l u) | abs l >= abs u || l == minBound = range (succ $ negate $ abs l) (predAbs l) | otherwise = range (succ $ negate $ abs u) (predAbs u) predAbs l | l == minBound = abs (succ l) | otherwise = pred (abs l) rangeDiv :: BoundedInt a => Range a -> Range a -> Range a rangeDiv = handleSign rangeDivU (\_ _ -> universal) rangeDivU :: BoundedInt a => Range a -> Range a -> Range a rangeDivU (Range l1 u1) (Range l2 u2) | l2 == 0 || u2 == 0 = universal rangeDivU (Range l1 u1) (Range l2 u2) = Range (l1 `quot` u2) (u1 `quot`l2) -- | Propagates range information through 'quot'. rangeQuot :: BoundedInt a => Range a -> Range a -> Range a rangeQuot = handleSign rangeQuotU (\_ _ -> universal) -- | Unsigned case for 'rangeQuot'. rangeQuotU :: BoundedInt a => Range a -> Range a -> Range a rangeQuotU (Range l1 u1) (Range l2 u2) | l2 == 0 || u2 == 0 = universal rangeQuotU (Range l1 u1) (Range l2 u2) = Range (l1 `quot` u2) (u1 `quot` l2) -- | Writing @d \`rangeLess\` abs r@ doesn't mean what you think it does because -- 'r' may contain minBound which doesn't have a positive representation. -- Instead, this function should be used. rangeLessAbs d r | r == singletonRange minBound = lowerBound d /= minBound | lowerBound r == minBound = d `rangeLess` (abs (range (succ (lowerBound r)) (upperBound r))) | otherwise = d `rangeLess` (abs r) -- | Similar to 'rangeLessAbs' but replaces the expression -- @abs d \`rangeLess\` abs r@ instead. absRangeLessAbs d r | lowerBound d == minBound = False | otherwise = abs d `rangeLessAbs` r -------------------------------------------------------------------------------- -- * Products of ranges -------------------------------------------------------------------------------- {- These functions are used to compute the ranges of DefaultInt and DefaultWord. The size information of these two types is the union of the sizes for all possible IntX/WordX that we support as defaults -} liftR :: (BoundedInt b, BoundedInt c, BoundedInt d) => (forall a. (BoundedInt a) => Range a) -> (Range b,Range c,Range d) liftR r = (r,r,r) binopR :: (BoundedInt a, BoundedInt b, BoundedInt c) => (forall a. BoundedInt a => Range a -> Range a -> Range a) -> (Range a, Range b, Range c) -> (Range a, Range b, Range c) -> (Range a, Range b, Range c) binopR bop (a1,b1,c1) (a2,b2,c2) = (bop a1 a2, bop b1 b2, bop c1 c2) mapR :: (BoundedInt a, BoundedInt b, BoundedInt c) => (forall a . BoundedInt a => Range a -> Range a) -> (Range a, Range b, Range c) -> (Range a, Range b, Range c) mapR f (a,b,c) = (f a, f b, f c) approx :: (BoundedInt a, BoundedInt b, BoundedInt c, BoundedInt d) => (Range a, Range b, Range c) -> Range d approx (r1,r2,r3) | isFull r1 || isFull r2 || isFull r3 = fullRange approx (r1,r2,r3) = mapMonotonic fromIntegral r1 \/ mapMonotonic fromIntegral r2 \/ mapMonotonic fromIntegral r3 instance (BoundedInt a, BoundedInt b, BoundedInt c) => Num (Range a,Range b,Range c) where (+) = binopR (+) (-) = binopR (-) (*) = binopR (*) signum = mapR rangeSignum fromInteger i = liftR (fromInteger i) abs = mapR abs negate = mapR negate -------------------------------------------------------------------------------- -- * Testing -------------------------------------------------------------------------------- instance (BoundedInt a, Arbitrary a) => Arbitrary (Range a) where arbitrary = do [bound1,bound2] <- vectorOf 2 $ oneof [ arbitrary , elements [minBound,-1,0,1,maxBound]] frequency [ (10, return $ Range (min bound1 bound2) (max bound1 bound2)) , (1 , return $ Range (max bound1 bound2) (min bound1 bound2)) -- Empty , (1 , return $ Range bound1 bound1) -- Singleton ] shrink (Range x y) = [ Range x' y | x' <- shrink x ] ++ [ Range x y' | y' <- shrink y ] instance Random Word32 where random g = (fromIntegral i,g') where (i :: Int,g') = random g randomR (l,u) g = (fromIntegral i,g') where (i :: Integer, g') = randomR (fromIntegral l,fromIntegral u) g instance Random Int8 where random g = (fromIntegral i,g') where (i :: Int,g') = random g randomR (l,u) g = (fromIntegral i,g') where (i :: Integer, g') = randomR (fromIntegral l,fromIntegral u) g instance Random Word8 where random g = (fromIntegral i,g') where (i :: Int,g') = random g randomR (l,u) g = (fromIntegral i,g') where (i :: Integer, g') = randomR (fromIntegral l,fromIntegral u) g fromRange :: BoundedInt a => Random a => Range a -> Gen a fromRange r | isEmpty r = error "fromRange: empty range" | otherwise = choose (lowerBound r, upperBound r) rangeTy :: Range t -> t -> Range t rangeTy r t = r -- | Applies a (monadic) function to all the types we are interested in testing -- with for Feldspar. -- -- Example usage: 'atAllTypes (quickCheck . prop_mul)' atAllTypes :: (Monad m) => (forall t . (BoundedInt t, Random t, Arbitrary t, Typeable t) => t -> m a) -> m () atAllTypes test = sequence_ [test (undefined :: Int) ,test (undefined :: Int8) ,test (undefined :: Word32) ,test (undefined :: Word8) ] -- | Test if a operation is "strict" wrt. empty ranges prop_isStrict1 t op ra = isEmpty ra ==> isEmpty (op ra) where _ = ra `rangeTy` t -- | Test if an operation is "strict" wrt. empty ranges prop_isStrict2 t op ra rb = isEmpty ra || isEmpty rb ==> isEmpty (op ra rb) where _ = ra `rangeTy` t -- TODO Think about strictness of range operations (in the sense of `isStrict1` -- and `isStrict2`). Probably all range propagation operations should be strict, -- but many of them are currently not: -- -- *Feldspar.Range> quickCheck (prop_isStrict2 (undefined :: Int) (+)) -- *** Failed! Falsifiable (after 1 test and 1 shrink): -- Range {lowerBound = 0, upperBound = 1} -- Range {lowerBound = 1, upperBound = 0} -------------------------------------------------------------------------------- -- ** Lattice operations -------------------------------------------------------------------------------- prop_empty t = isEmpty (emptyRange `rangeTy` t) prop_full t = isFull (fullRange `rangeTy` t) prop_isEmpty t r = isEmpty r ==> (upperBound r < lowerBound (r `rangeTy` t)) prop_singletonRange t a = isSingleton (singletonRange (a `asTypeOf` t)) prop_singletonSize t r = isSingleton (r `rangeTy` t) ==> (rangeSize r == 1) prop_emptySubRange1 t r1 r2 = isEmpty (r1 `rangeTy` t) ==> (not (isEmpty r2) ==> not (r2 `isSubRangeOf` r1)) prop_emptySubRange2 t r1 r2 = isEmpty (r1 `rangeTy` t) ==> (not (isEmpty r2) ==> (r1 `isSubRangeOf` r2)) prop_rangeGap t r1 r2 = (isEmpty gap1 && isEmpty gap2) || (gap1 == gap2) where gap1 = rangeGap r1 r2 gap2 = rangeGap r2 r1 _ = r1 `rangeTy` t prop_union1 t x r1 r2 = ((x `inRange` r1) || (x `inRange` r2)) ==> (x `inRange` (r1\/r2)) where _ = x `asTypeOf` t prop_union2 t x r1 r2 = (x `inRange` (r1\/r2)) ==> ((x `inRange` r1) || (x `inRange` r2) || (x `inRange` rangeGap r1 r2)) where _ = x `asTypeOf` t prop_union3 t r1 r2 = (r1 `rangeTy` t) `isSubRangeOf` (r1\/r2) prop_union4 t r1 r2 = (r2 `rangeTy` t) `isSubRangeOf` (r1\/r2) prop_intersect1 t x r1 r2 = ((x `inRange` r1) && (x `inRange` r2)) ==> (x `inRange` (r1/\r2)) where _ = x `asTypeOf` t prop_intersect2 t x r1 r2 = (x `inRange` (r1/\r2)) ==> ((x `inRange` r1) && (x `inRange` r2)) where _ = x `asTypeOf` t prop_intersect3 t r1 r2 = (r1/\r2) `isSubRangeOf` (r1 `rangeTy` t) prop_intersect4 t r1 r2 = (r1/\r2) `isSubRangeOf` (r2 `rangeTy` t) prop_intersect5 t r1 r2 = isEmpty r1 || isEmpty r2 ==> isEmpty (r1/\r2) where _ = r1 `rangeTy` t prop_disjoint t x r1 r2 = disjoint r1 r2 ==> (x `inRange` r1) ==> not (x `inRange` r2) where _ = x `asTypeOf` t prop_rangeLess1 t r1 r2 = rangeLess r1 r2 ==> disjoint r1 (r2 `rangeTy` t) prop_rangeLess2 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> forAll (fromRange r1) $ \x -> forAll (fromRange r2) $ \y -> rangeLess r1 r2 ==> x < y where _ = r1 `rangeTy` t prop_rangeLessEq t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> forAll (fromRange r1) $ \x -> forAll (fromRange r2) $ \y -> rangeLessEq r1 r2 ==> x <= y where _ = r1 `rangeTy` t -------------------------------------------------------------------------------- -- ** Propagation -------------------------------------------------------------------------------- prop_propagation1 :: (BoundedInt t, Random t) => t -> (forall a . Num a => a -> a) -> Range t -> Property prop_propagation1 t op r = not (isEmpty r) ==> forAll (fromRange r) $ \x -> op x `inRange` op r -- | This function is useful for range propagation functions like -- 'rangeMax', 'rangeMod' etc. -- It takes two ranges, picks an element out of either ranges and -- checks if applying the operation to the individual elements is in -- the resulting range after range propagation. -- -- The third argument is a precondition that is satisfied before the test is -- run. A good example is to make sure that the second argument is non-zero -- when testing division. rangePropagationSafetyPre :: (Random t, BoundedInt t, BoundedInt a) => t -> (t -> t -> a) -> (Range t -> Range t -> Range a) -> (t -> t -> Bool) -> Range t -> Range t -> Property rangePropagationSafetyPre t op rop pre r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> forAll (fromRange r1) $ \v1 -> forAll (fromRange r2) $ \v2 -> pre v1 v2 ==> op v1 v2 `inRange` rop r1 r2 rangePropagationSafetyPre2 :: (Random t, BoundedInt t, Random t2, BoundedInt t2, BoundedInt a) => t -> t2 -> (t -> t2 -> a) -> (Range t -> Range t2 -> Range a) -> (t -> t2 -> Bool) -> Range t -> Range t2 -> Property rangePropagationSafetyPre2 t1 t2 op rop pre r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> forAll (fromRange r1) $ \v1 -> forAll (fromRange r2) $ \v2 -> pre v1 v2 ==> op v1 v2 `inRange` rop r1 r2 rangePropagationSafety t op rop = rangePropagationSafetyPre t op rop noPre where noPre _ _ = True rangePropSafety1 t op rop ran = not (isEmpty ran) ==> forAll (fromRange ran) $ \val -> op val `inRange` rop ran where _ = ran `rangeTy` t prop_propagation2 :: (BoundedInt t, Random t) => t -> (forall a . Num a => a -> a -> a) -> Range t -> Range t -> Property prop_propagation2 t op r1 r2 = rangePropagationSafety t op op r1 r2 prop_rangeByRange1 t ra rb = forAll (fromRange ra) $ \a -> forAll (fromRange rb) $ \b -> forAll (fromRange (Range a b)) $ \x -> not (isEmpty ra) && not (isEmpty rb) && not (isEmpty (Range a b)) ==> inRange x (rangeByRange ra rb) where _ = ra `rangeTy` t prop_rangeByRange2 t = prop_isStrict2 t rangeByRange prop_fromInteger t a = isSingleton (fromInteger a `rangeTy` t) prop_abs t = prop_propagation1 t abs prop_sign t = prop_propagation1 t signum prop_neg t = prop_propagation1 t negate prop_add t = prop_propagation2 t (+) prop_sub t = prop_propagation2 t (-) prop_mul t = prop_propagation2 t (*) prop_exp t = rangePropagationSafetyPre t (^) rangeExp (\_ e -> e >= 0) prop_mulU t = rangePropagationSafety t (*) rangeMulUnsigned prop_subSat t = rangePropagationSafety t subSat rangeSubSat prop_isNegative t r = not (isEmpty r) && not (r == Range minBound minBound) ==> isNegative r ==> not (isNegative $ negate r) where _ = rangeTy r t prop_abs2 t r = lowerBound r /= (minBound `asTypeOf` t) ==> isNatural (abs r) prop_or t = rangePropagationSafety t (.|.) rangeOr prop_and t = rangePropagationSafety t (.&.) rangeAnd prop_xor t = rangePropagationSafety t xor rangeXor prop_shiftLU t1 t2 = rangePropagationSafetyPre2 t1 t2 fixShiftL rangeShiftLU (\_ _ -> True) where fixShiftL a b = shiftL a (fromIntegral b) prop_shiftRU t1 t2 = rangePropagationSafetyPre2 t1 t2 fixShiftR rangeShiftRU (\_ _ -> True) where fixShiftR a b = correctShiftRU a b prop_rangeMax1 t r1 = rangeMax r1 r1 == (r1 `rangeTy` t) prop_rangeMax2 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> upperBound r1 <= upperBound max && upperBound r2 <= upperBound max where max = rangeMax r1 (r2 `rangeTy` t) prop_rangeMax3 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> lowerBound (rangeMax r1 r2) == max (lowerBound r1) (lowerBound r2) where _ = r1 `rangeTy` t prop_rangeMax4 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> rangeMax r1 r2 == rangeMax r2 r1 where _ = r1 `rangeTy` t prop_rangeMax5 t r1 r2 = (isEmpty r1 && not (isEmpty r2) ==> rangeMax r1 r2 == r2) QC..&. (isEmpty r2 && not (isEmpty r1) ==> rangeMax r1 r2 == r1) where _ = r1 `rangeTy` t prop_rangeMax6 t v1 v2 = max v1 v2 `inRange` rangeMax (singletonRange v1) (singletonRange v2) where _ = v1 `asTypeOf` t prop_rangeMax7 a r1 r2 = rangePropagationSafety a max rangeMax r1 r2 prop_rangeMin1 t r1 = rangeMin r1 r1 == (r1 `rangeTy` t) prop_rangeMin2 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> lowerBound min <= lowerBound r1 && lowerBound min <= lowerBound r2 where min = rangeMin r1 (r2 `rangeTy` t) prop_rangeMin3 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> upperBound (rangeMin r1 r2) == min (upperBound r1) (upperBound r2) where _ = r1 `rangeTy` t prop_rangeMin4 t r1 r2 = not (isEmpty r1) && not (isEmpty r2) ==> rangeMin r1 r2 == rangeMin r2 r1 where _ = r1 `rangeTy` t prop_rangeMin5 t r1 r2 = (isEmpty r1 && not (isEmpty r2) ==> rangeMin r1 r2 == r2) QC..&. (isEmpty r2 && not (isEmpty r1) ==> rangeMin r1 r2 == r1) where _ = r1 `rangeTy` t prop_rangeMin6 t v1 v2 = min v1 v2 `inRange` rangeMin (singletonRange v1) (singletonRange v2) where _ = v1 `asTypeOf` t prop_rangeMin7 t r1 r2 = rangePropagationSafety t min rangeMin r1 r2 prop_rangeMod1 t v1 v2 = v2 /= 0 ==> mod v1 v2 `inRange` rangeMod (singletonRange v1) (singletonRange v2) where _ = v1 `asTypeOf` t prop_rangeMod2 t = rangePropagationSafetyPre t mod rangeMod divPre prop_rangeMod3 t = isFull $ rangeMod (singletonRange (minBound `asTypeOf` t)) (singletonRange (-1)) prop_rangeRem t = rangePropagationSafetyPre t rem rangeRem divPre prop_rangeRem1 t = isFull $ rangeRem (singletonRange (minBound `asTypeOf` t)) (singletonRange (-1)) prop_rangeQuot t = rangePropagationSafetyPre t quot rangeQuot divPre prop_rangeQuot1 t = isFull $ rangeQuot (singletonRange (minBound `asTypeOf` t)) (singletonRange (-1)) -- | Precondition for division like operators. -- Avoids division by zero and arithmetic overflow. divPre v1 v2 = v2 /= 0 && not (v1 == minBound && v2 == (-1))