packages feed

quote-quot 0.2.1.0 → 0.2.2.0

raw patch · 5 files changed

+227/−95 lines, 5 filesdep +template-haskell-liftdep −template-haskelldep ~basedep ~tasty-benchPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: template-haskell-lift

Dependencies removed: template-haskell

Dependency ranges changed: base, tasty-bench

API changes (from Hackage documentation)

+ Numeric.QuoteQuot: instance Numeric.QuoteQuot.MulHi GHC.Int.Int64
+ Numeric.QuoteQuot: instance Numeric.QuoteQuot.MulHi GHC.Types.Int
+ Numeric.QuoteQuot: quoteDiv :: forall a (m :: Type -> Type). (MulHi a, Lift a, MulHi (Unsigned a), Lift (Unsigned a), Quote m) => a -> Code m (a -> a)
+ Numeric.QuoteQuot: quoteDivMod :: forall a (m :: Type -> Type). (MulHi a, Lift a, MulHi (Unsigned a), Lift (Unsigned a), Quote m) => a -> Code m (a -> (a, a))
+ Numeric.QuoteQuot: quoteMod :: forall a (m :: Type -> Type). (MulHi a, Lift a, MulHi (Unsigned a), Lift (Unsigned a), Quote m) => a -> Code m (a -> a)
+ Numeric.QuoteQuot: type family Unsigned t
- Numeric.QuoteQuot: quoteAST :: (MulHi a, Lift a) => AST a -> Q (TExp (a -> a))
+ Numeric.QuoteQuot: quoteAST :: forall a (m :: Type -> Type). (MulHi a, Lift a, Quote m) => AST a -> Code m (a -> a)
- Numeric.QuoteQuot: quoteQuot :: (MulHi a, Lift a) => a -> Q (TExp (a -> a))
+ Numeric.QuoteQuot: quoteQuot :: forall a (m :: Type -> Type). (MulHi a, Lift a, Quote m) => a -> Code m (a -> a)
- Numeric.QuoteQuot: quoteQuotRem :: (MulHi a, Lift a) => a -> Q (TExp (a -> (a, a)))
+ Numeric.QuoteQuot: quoteQuotRem :: forall a (m :: Type -> Type). (MulHi a, Lift a, Quote m) => a -> Code m (a -> (a, a))
- Numeric.QuoteQuot: quoteRem :: (MulHi a, Lift a) => a -> Q (TExp (a -> a))
+ Numeric.QuoteQuot: quoteRem :: forall a (m :: Type -> Type). (MulHi a, Lift a, Quote m) => a -> Code m (a -> a)

Files

bench/Bench.hs view
@@ -6,6 +6,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}  module Main (main) where@@ -44,18 +45,33 @@     $ measureInt "quoteQuot" $$(quoteQuot (n :: Int)) \     ] +#define benchDivInt(n) \+  bgroup (show (n :: Int)) \+    [ measureInt "div" (`div` (n :: Int)) \+    , bcompare ("$NF == \"div\" && $(NF-1) == \"" ++ show (n :: Int) ++ "\" && $(NF-2) == \"Int\"") \+    $ measureInt "quoteDiv" $$(quoteDiv (n :: Int)) \+    ]+ main :: IO () main = defaultMain   [ bgroup "Word"     [ benchWord(3)     , benchWord(5)     , benchWord(7)+    , benchWord(10000)     ] #if MIN_VERSION_base(4,15,0)   , bgroup "Int"     [ benchInt(3)     , benchInt(5)     , benchInt(7)+    , benchInt(10000)+    ]+  , bgroup "DivInt"+    [ benchDivInt(3)+    , benchDivInt(5)+    , benchDivInt(7)+    , benchDivInt(10000)     ] #endif   ]
changelog.md view
@@ -1,3 +1,8 @@+## 0.2.2.0++* Add `quoteDiv`, `quoteMod` and `quoteDivMod`.+* Define `instance MulHi {Int,Word}64` even on 32-bit arch.+ ## 0.2.1.0  * Add `quoteAST` and `assumeNonNegArg`.
quote-quot.cabal view
@@ -1,12 +1,15 @@-cabal-version:      >=1.10+cabal-version:      2.2 name:               quote-quot-version:            0.2.1.0-license:            BSD3+version:            0.2.2.0+license:            BSD-3-Clause license-file:       LICENSE copyright:          2020-2022 Andrew Lelechenko maintainer:         andrew.lelechenko@gmail.com author:             Andrew Lelechenko-tested-with:        ghc ==8.10.7, ghc ==9.0.2, ghc ==9.2.2+tested-with:+  ghc ==9.2.8 ghc ==9.4.8 ghc ==9.6.7 ghc ==9.8.4 ghc ==9.10.3+  ghc ==9.12.2 ghc ==9.14.1+ homepage:           https://github.com/Bodigrim/quote-quot#readme synopsis:           Divide without division description:@@ -16,7 +19,7 @@  category:           Math, Numerical build-type:         Simple-extra-source-files:+extra-doc-files:   changelog.md   README.md @@ -30,8 +33,8 @@   default-language: Haskell2010   ghc-options:      -Wall -Wcompat   build-depends:-    base < 5,-    template-haskell >=2.16+    base >=4.18 && < 5,+    template-haskell-lift >= 0.1 && < 0.2  test-suite quote-quot-tests   type:             exitcode-stdio-1.0@@ -43,19 +46,17 @@     base,     quote-quot,     tasty,-    tasty-quickcheck,-    -- wide-word >=0.1.1.2,+    tasty-quickcheck+    -- wide-word >=0.1.9,     -- word24,-    template-haskell  benchmark quote-quot-bench   type:             exitcode-stdio-1.0   main-is:          Bench.hs   hs-source-dirs:   bench   default-language: Haskell2010-  ghc-options:      -Wall -O2 -Wcompat+  ghc-options:      -Wall -O2 -Wcompat -fproc-alignment=64   build-depends:     base,     quote-quot,-    tasty-bench >= 0.3,-    template-haskell+    tasty-bench >= 0.3.3
src/Numeric/QuoteQuot.hs view
@@ -10,13 +10,17 @@ --  {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE LexicalNegation #-} {-# LANGUAGE MagicHash #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneKindSignatures #-}+{-# LANGUAGE TemplateHaskellQuotes #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UnboxedTuples #-}--{-# OPTIONS_GHC -Wno-missing-signatures #-}+{- HLINT ignore "Redundant lambda" -}  module Numeric.QuoteQuot   (@@ -24,6 +28,9 @@     quoteQuot   , quoteRem   , quoteQuotRem+  , quoteDiv+  , quoteMod+  , quoteDivMod   -- * AST   , astQuot   , AST(..)@@ -31,19 +38,17 @@   , quoteAST   , assumeNonNegArg   , MulHi(..)+  , Unsigned   ) where -#include "MachDeps.h"--import Prelude-import Data.Bits-import Data.Int-import Data.Word-import GHC.Exts-import Language.Haskell.TH.Syntax+import Data.Bits (shiftL, shiftR, isSigned, FiniteBits, countTrailingZeros, finiteBitSize)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Word ( Word8, Word16, Word32, Word64 )+import GHC.Exts (dataToTag#, Int(..), Word(..), timesInt2#, timesWord2#)+import Language.Haskell.TH.Lift (Lift, Quote, Code) --- | Quote integer division ('quot') by a compile-time known divisor,--- which generates source code, employing arithmetic and bitwise operations only.+-- | Quote integer division ('quot') by a compile-time known divisor, which+-- generates source code, employing arithmetic and bitwise operations only. -- This is usually __2.5x-3.5x faster__ than using normal 'quot'. -- -- > {-# LANGUAGE TemplateHaskell #-}@@ -79,71 +84,108 @@ -- Benchmarks show that this implementation is __3.5x faster__ -- than @(`@'quot'@` 10)@. ---quoteQuot ::-#if MIN_VERSION_template_haskell(2,17,0)-  (MulHi a, Lift a, Quote m) => a -> Code m (a -> a)-#else-  (MulHi a, Lift a) => a -> Q (TExp (a -> a))-#endif+quoteQuot :: forall a m. (MulHi a, Lift a, Quote m) => a -> Code m (a -> a) quoteQuot d = quoteAST (astQuot d)  -- | Similar to 'quoteQuot', but for 'rem'.-quoteRem ::-#if MIN_VERSION_template_haskell(2,17,0)-  (MulHi a, Lift a, Quote m) => a -> Code m (a -> a)-#else-  (MulHi a, Lift a) => a -> Q (TExp (a -> a))-#endif+quoteRem :: (MulHi a, Lift a, Quote m) => a -> Code m (a -> a) quoteRem d = [|| snd . $$(quoteQuotRem d) ||]  -- | Similar to 'quoteQuot', but for 'quotRem'.-quoteQuotRem ::-#if MIN_VERSION_template_haskell(2,17,0)-  (MulHi a, Lift a, Quote m) => a -> Code m (a -> (a, a))-#else-  (MulHi a, Lift a) => a -> Q (TExp (a -> (a, a)))-#endif+quoteQuotRem :: (MulHi a, Lift a, Quote m) => a -> Code m (a -> (a, a)) quoteQuotRem d = [|| \w -> let q = $$(quoteQuot d) w in (q, w - d * q) ||] +-- | Similar to 'quoteQuot', but for 'div'.+--+-- @since 0.2.2.0+quoteDiv :: forall a m. (MulHi a, Lift a, MulHi (Unsigned a), Lift (Unsigned a), Quote m) => a -> Code m (a -> a)+quoteDiv d+    | isSigned d+    = [|| \(i :: a') -> let w2i = fromIntegral :: Unsigned a' -> a' in+                if i < 0+                then w2i -(1 + $$go (fromIntegral -(i + 1)))+                else w2i $ $$go (fromIntegral i) ||]+    | otherwise = quoteQuot d+  where+    go :: Code m (Unsigned a -> Unsigned a)+    go = quoteAST (unsignedQuot (fromIntegral d))++-- | Similar to 'quoteQuot', but for 'mod'.+--+-- @since 0.2.2.0+quoteMod :: (MulHi a, Lift a, MulHi (Unsigned a), Lift (Unsigned a), Quote m) => a -> Code m (a -> a)+quoteMod d = [|| snd . $$(quoteDivMod d) ||]++-- | Similar to 'quoteQuot', but for 'divMod'.+--+-- @since 0.2.2.0+quoteDivMod :: (MulHi a, Lift a, MulHi (Unsigned a), Lift (Unsigned a), Quote m) => a -> Code m (a -> (a, a))+quoteDivMod d = [|| \w -> let q = $$(quoteDiv d) w in (q, w - d * q) ||]++-- | Associate the corresponding unsigned type.+--+-- @since 0.2.2.0+type family Unsigned t+type instance Unsigned Int = Word+type instance Unsigned Word = Word+type instance Unsigned Int8 = Word8+type instance Unsigned Word8 = Word8+type instance Unsigned Int16 = Word16+type instance Unsigned Word16 = Word16+type instance Unsigned Int32 = Word32+type instance Unsigned Word32 = Word32+type instance Unsigned Int64 = Word64+type instance Unsigned Word64 = Word64+ -- | Types allowing to multiply wide and return the high word of result. class (Integral a, FiniteBits a) => MulHi a where   mulHi :: a -> a -> a  instance MulHi Word8 where-  mulHi x y = fromIntegral ((fromIntegral x * fromIntegral y :: Word16) `shiftR` 8)+  mulHi x y = fromIntegral ((widen x * widen y) `shiftR` 8)+    where widen = fromIntegral :: Word8 -> Word16  instance MulHi Word16 where-  mulHi x y = fromIntegral ((fromIntegral x * fromIntegral y :: Word32) `shiftR` 16)+  mulHi x y = fromIntegral ((widen x * widen y) `shiftR` 16)+    where widen = fromIntegral :: Word16 -> Word32  instance MulHi Word32 where-  mulHi x y = fromIntegral ((fromIntegral x * fromIntegral y :: Word64) `shiftR` 32)+  mulHi x y = fromIntegral ((widen x * widen y) `shiftR` 32)+    where widen = fromIntegral :: Word32 -> Word64 -#if WORD_SIZE_IN_BITS == 64+-- | This instance is not efficient on 32-bit architecture. instance MulHi Word64 where-  mulHi x y = fromIntegral (fromIntegral x `mulHi` fromIntegral y :: Word)-#endif+  mulHi x y+    | finiteBitSize (0 :: Word) == 64+    = fromIntegral (fromIntegral x `mulHi` fromIntegral y :: Word)+    | otherwise+    = defaultMulHi x y  instance MulHi Word where   mulHi (W# x) (W# y) = let !(# hi, _ #) = timesWord2# x y in W# hi  instance MulHi Int8 where-  mulHi x y = fromIntegral ((fromIntegral x * fromIntegral y :: Int16) `shiftR` 8)+  mulHi x y = fromIntegral ((widen x * widen y) `shiftR` 8)+    where widen = fromIntegral :: Int8 -> Int16  instance MulHi Int16 where-  mulHi x y = fromIntegral ((fromIntegral x * fromIntegral y :: Int32) `shiftR` 16)+  mulHi x y = fromIntegral ((widen x * widen y) `shiftR` 16)+    where widen = fromIntegral :: Int16 -> Int32  instance MulHi Int32 where-  mulHi x y = fromIntegral ((fromIntegral x * fromIntegral y :: Int64) `shiftR` 32)+  mulHi x y = fromIntegral ((widen x * widen y) `shiftR` 32)+    where widen = fromIntegral :: Int32 -> Int64 -#if MIN_VERSION_base(4,15,0)-#if WORD_SIZE_IN_BITS == 64+-- | This instance is not efficient on 32-bit architecture. instance MulHi Int64 where-  mulHi x y = fromIntegral (fromIntegral x `mulHi` fromIntegral y :: Int)-#endif+  mulHi x y+    | finiteBitSize (0 :: Int) == 64+    = fromIntegral (fromIntegral x `mulHi` fromIntegral y :: Int)+    | otherwise+    = defaultMulHi x y  instance MulHi Int where   mulHi (I# x) (I# y) = let !(# _, hi, _ #) = timesInt2# x y in I# hi-#endif  -- | An abstract syntax tree to represent -- a function of one argument.@@ -191,7 +233,7 @@   where     go = \case       Arg       -> n-      MulHi x k -> fromInteger $ (toInteger (go x) * toInteger k) `shiftR` finiteBitSize k+      MulHi x k -> defaultMulHi (go x) k       MulLo x k -> go x * k       Add x y   -> go x + go y       Sub x y   -> go x - go y@@ -200,13 +242,17 @@       CmpGE x k -> if go x >= k then 1 else 0       CmpLT x k -> if go x <  k then 1 else 0 +defaultMulHi :: (Integral a, FiniteBits a) => a -> a -> a+defaultMulHi x y =+    fromInteger $ (toInteger x * toInteger y) `shiftR` finiteBitSize x++iprop :: Integral a => (a -> Bool) -> a -> a+iprop p = \i -> fromIntegral (I# (dataToTag# (p i)))+{-# INLINE iprop #-}++ -- | Embed 'AST' into Haskell expression.-quoteAST ::-#if MIN_VERSION_template_haskell(2,17,0)-  (MulHi a, Lift a, Quote m) => AST a -> Code m (a -> a)-#else-  (MulHi a, Lift a) => AST a -> Q (TExp (a -> a))-#endif+quoteAST :: (MulHi a, Lift a, Quote m) => AST a -> Code m (a -> a) quoteAST = \case   Arg            -> [|| id ||]   Shr x k        -> [|| (`shiftR` k) . $$(quoteAST x) ||]@@ -215,8 +261,8 @@   MulLo x k      -> [|| (* k) . $$(quoteAST x) ||]   Add x y        -> [|| \w -> $$(quoteAST x) w + $$(quoteAST y) w ||]   Sub x y        -> [|| \w -> $$(quoteAST x) w - $$(quoteAST y) w ||]-  CmpGE x k      -> [|| (\w -> fromIntegral (I# (dataToTag# (w >= k)))) . $$(quoteAST x) ||]-  CmpLT x k      -> [|| (\w -> fromIntegral (I# (dataToTag# (w <  k)))) . $$(quoteAST x) ||]+  CmpGE x k      -> [|| iprop (>= k) . $$(quoteAST x) ||]+  CmpLT x k      -> [|| iprop (< k) . $$(quoteAST x) ||]  -- | 'astQuot' @d@ constructs an 'AST' representing -- a function, equivalent to 'quot' @a@ for positive @a@,@@ -255,16 +301,17 @@   = CmpGE Arg k'   -- Hacker's Delight, 10-8, Listing 1   | k >= 1 `shiftL` shft-  = shr (MulHi Arg magic) (shft + kZeros)+  = shr (MulHi Arg magic) shft'   -- Hacker's Delight, 10-8, Listing 3   | otherwise-  = shr (Add (shr (Sub Arg (MulHi Arg magic)) 1) (MulHi Arg magic)) (shft - 1 + kZeros)+  = shr (Add (shr (Sub Arg (MulHi Arg magic)) 1) (MulHi Arg magic)) (shft' - 1)   where     fbs = finiteBitSize k'     kZeros = countTrailingZeros k'     k = k' `shiftR` kZeros     r0 = fromInteger ((1 `shiftL` fbs) `rem` toInteger k)     shft = go r0 0+    shft' = shft + kZeros     magic = fromInteger ((1 `shiftL` (fbs + shft)) `quot` toInteger k + 1)      go r s@@ -286,16 +333,17 @@   = Sub (CmpGE Arg k') (CmpLT Arg (1 - k'))   -- Hacker's Delight, 10-3, Listing 2   | magic >= 0-  = Add (shr (MulHi Arg magic) (shft + kZeros)) (CmpLT Arg 0)+  = Add (shr (MulHi Arg magic) shft') (CmpLT Arg 0)   -- Hacker's Delight, 10-3, Listing 3   | otherwise-  = Add (shr (Add Arg (MulHi Arg magic)) (shft + kZeros)) (CmpLT Arg 0)+  = Add (shr (Add Arg (MulHi Arg magic)) shft') (CmpLT Arg 0)   where     fbs = finiteBitSize k'     kZeros = countTrailingZeros k'     k = k' `shiftR` kZeros     r0 = fromInteger ((1 `shiftL` fbs) `rem` toInteger k)     shft = go r0 0+    shft' = shft + kZeros     magic = fromInteger ((1 `shiftL` (fbs + shft)) `quot` toInteger k + 1)      go r s
tests/Test.hs view
@@ -19,7 +19,6 @@ import Numeric.QuoteQuot import Test.Tasty import Test.Tasty.QuickCheck-import Text.Printf  #ifdef MIN_VERSION_word24 import Data.Int.Int24@@ -31,7 +30,7 @@ #endif  main :: IO ()-main = defaultMain $ testGroup "All" [testAst, testQuotes]+main = defaultMain $ testGroup "All" [testAst, testQuotes, testDivs, testMulHi]  testAst :: TestTree testAst = testGroup "Ast"@@ -53,6 +52,7 @@   , testGroup "Word128" (mkTests (Proxy @Word128))   , testGroup "Word256" (mkTests (Proxy @Word256))   , testGroup "Int128"  (mkTests (Proxy @Int128))+  , testGroup "Int256"  (mkTests (Proxy @Int256)) #endif   ] @@ -77,24 +77,22 @@ prop   :: (Integral a, FiniteBits a, Show a)   => a -> Positive a -> Property-prop x (Positive y) = counterexample-  (printf-    "%s `quot` %s = %s /= %s = eval (%s) %s"-    (show x) (show y) (show ref) (show q) (show ast) (show x))-  (q == ref)+prop x (Positive y) =+  (q === qref) .&&. (d === dref)   where-    ref = x `quot` y-    ast = astQuot y-    q   = interpretAST ast x+    qref = x `quot` y+    dref = x `div` y+    qast = astQuot y+    dast = assumeNonNegArg (astQuot y)+    q    = interpretAST qast x+    d | x < 0 = -(interpretAST dast (-(x + 1)) + 1)+      | otherwise = q  propNonNeg   :: (Integral a, FiniteBits a, Show a)   => NonNegative a -> Positive a -> Property-propNonNeg (NonNegative x) (Positive y) = counterexample-  (printf-    "%s `quot` %s = %s /= %s = eval (%s) %s"-    (show x) (show y) (show ref) (show q) (show ast) (show x))-  (q == ref)+propNonNeg (NonNegative x) (Positive y) =+  q === ref   where     ref = x `quot` y     ast = assumeNonNegArg $ astQuot y@@ -109,6 +107,7 @@ instance Arbitrary Word128 where arbitrary = arbitrarySizedBoundedIntegral instance Arbitrary Word256 where arbitrary = arbitrarySizedBoundedIntegral instance Arbitrary Int128  where arbitrary = arbitrarySizedBoundedIntegral+instance Arbitrary Int256  where arbitrary = arbitrarySizedBoundedIntegral #endif  #define testQuotes(ty) \@@ -124,25 +123,88 @@     , testProperty "10" $ \x -> $$(quoteQuotRem (10 :: ty)) x === x `quotRem` 10 \     , testProperty "maxBound" $ \x -> $$(quoteQuotRem (maxBound :: ty)) x === x `quotRem` maxBound \     , testProperty "maxBound - 1" $ \x -> $$(quoteQuotRem (maxBound - 1 :: ty)) x === x `quotRem` (maxBound - 1) \-    ] \+    ] +#define testDivs(ty) \+    [ testProperty  "1" $ \x -> $$(quoteDivMod  (1 :: ty)) x === x `divMod`  1 \+    , testProperty  "2" $ \x -> $$(quoteDivMod  (2 :: ty)) x === x `divMod`  2 \+    , testProperty  "3" $ \x -> $$(quoteDivMod  (3 :: ty)) x === x `divMod`  3 \+    , testProperty  "4" $ \x -> $$(quoteDivMod  (4 :: ty)) x === x `divMod`  4 \+    , testProperty  "5" $ \x -> $$(quoteDivMod  (5 :: ty)) x === x `divMod`  5 \+    , testProperty  "6" $ \x -> $$(quoteDivMod  (6 :: ty)) x === x `divMod`  6 \+    , testProperty  "7" $ \x -> $$(quoteDivMod  (7 :: ty)) x === x `divMod`  7 \+    , testProperty  "8" $ \x -> $$(quoteDivMod  (8 :: ty)) x === x `divMod`  8 \+    , testProperty  "9" $ \x -> $$(quoteDivMod  (9 :: ty)) x === x `divMod`  9 \+    , testProperty "10" $ \x -> $$(quoteDivMod (10 :: ty)) x === x `divMod` 10 \+    , testProperty "maxBound" $ \x -> $$(quoteDivMod (maxBound :: ty)) x === x `divMod` maxBound \+    , testProperty "maxBound - 1" $ \x -> $$(quoteDivMod (maxBound - 1 :: ty)) x === x `divMod` (maxBound - 1) \+    ]+ testQuotes :: TestTree testQuotes = testGroup "Quotes"   [ testGroup "Word8"  testQuotes(Word8)   , testGroup "Word16" testQuotes(Word16)   , testGroup "Word32" testQuotes(Word32)-#if WORD_SIZE_IN_BITS == 64   , testGroup "Word64" testQuotes(Word64)-#endif   , testGroup "Word"   testQuotes(Word)-   , testGroup "Int8"   testQuotes(Int8)   , testGroup "Int16"  testQuotes(Int16)   , testGroup "Int32"  testQuotes(Int32)-#if MIN_VERSION_base(4,15,0)-#if WORD_SIZE_IN_BITS == 64   , testGroup "Int64"  testQuotes(Int64)-#endif   , testGroup "Int"    testQuotes(Int)-#endif   ]++testDivs :: TestTree+testDivs = testGroup "Divs"+  [ testGroup "Word8"  testDivs(Word8)+  , testGroup "Word16" testDivs(Word16)+  , testGroup "Word32" testDivs(Word32)+  , testGroup "Word64" testDivs(Word64)+  , testGroup "Word"   testDivs(Word)+  , testGroup "Int8"   testDivs(Int8)+  , testGroup "Int16"  testDivs(Int16)+  , testGroup "Int32"  testDivs(Int32)+  , testGroup "Int64"  testDivs(Int64)+  , testGroup "Int"    testDivs(Int)+  ]++testMulHi :: TestTree+testMulHi = testGroup "MulHi"+  [ testGroup "Word"    (mkTestsMulHi (Proxy @Word))+  , testGroup "Word8"   (mkTestsMulHi (Proxy @Word8))+  , testGroup "Word16"  (mkTestsMulHi (Proxy @Word16))+  , testGroup "Word32"  (mkTestsMulHi (Proxy @Word32))+  , testGroup "Word64"  (mkTestsMulHi (Proxy @Word64))+  , testGroup "Int"     (mkTestsMulHi (Proxy @Int))+  , testGroup "Int8"    (mkTestsMulHi (Proxy @Int8))+  , testGroup "Int16"   (mkTestsMulHi (Proxy @Int16))+  , testGroup "Int32"   (mkTestsMulHi (Proxy @Int32))+  , testGroup "Int64"   (mkTestsMulHi (Proxy @Int64))+  ]++mkTestsMulHi+  :: forall a.+     (MulHi a, Show a, Bounded a, Arbitrary a)+  => Proxy a -> [TestTree]+mkTestsMulHi _ = [ combineMods mod1 mod2 | mod1 <- mods, mod2 <- mods]+  where+    mods :: [(String, a -> Bool, a -> a)]+    mods+      | isSigned (undefined :: a) =+      [ ("above zero", (>= 0), id)+      , ("below zero", (>= 0), negate)+      , ("above minBound", (>= 0), (minBound +))+      , ("below maxBound", (>= 0), (maxBound -))+      ]+      | otherwise =+      [ ("above zero", const True, id)+      , ("below maxBound", const True, (maxBound -))+      ]++    combineMods (name1, pred1, tr1) (name2, pred2, tr2) =+      testProperty (name1 ++ ", " ++ name2) $+        \x y -> pred1 x && pred2 y ==> propMulHi (tr1 x) (tr2 y)++propMulHi :: (MulHi a, Show a) => a -> a -> Property+propMulHi x y = mulHi x y ===+  fromInteger ((toInteger x * toInteger y) `shiftR` finiteBitSize x)