|
6 | 6 | {-# LANGUAGE NoStarIsType #-}
|
7 | 7 | module ModularArithmetic_TypeNats where
|
8 | 8 | import Data.Int
|
9 | | -import GHC.TypeNats (Nat, KnownNat, natVal, type (^), type (+)) |
| 9 | +import GHC.TypeNats |
| 10 | +import qualified Test.QuickCheck as QC |
| 11 | +import Data.Proxy |
| 12 | +import Control.Exception (assert) |
10 | 13 |
|
11 | 14 | --
|
12 | 15 | -- Modular Arithmetic
|
@@ -68,3 +71,100 @@ instance KnownNat m => Fractional (IntMod m) where
|
68 | 71 | _ -> error "not invertible"
|
69 | 72 | where modulus = fromIntegral (natVal t)
|
70 | 73 | fromRational = undefined
|
| 74 | + |
| 75 | +{- |
| 76 | +import qualified Data.Vector.Unboxing as U |
| 77 | +instance U.Unboxable (IntMod m) where |
| 78 | + type Rep (IntMod m) = Int64 |
| 79 | +-} |
| 80 | + |
| 81 | +recipM :: (Eq a, Integral a, Show a) => a -> a -> a |
| 82 | +recipM !x modulo = case exEuclid x modulo of |
| 83 | + (1,a,_) -> a `mod` modulo |
| 84 | + (-1,a,_) -> (-a) `mod` modulo |
| 85 | + (g,a,b) -> error $ show x ++ "^(-1) mod " ++ show modulo ++ " failed: gcd=" ++ show g |
| 86 | + |
| 87 | +-- | |
| 88 | +-- Assumption: @gcd m1 m2 == 1@ |
| 89 | +-- |
| 90 | +-- >>> crt 3 6 2 7 |
| 91 | +-- 9 |
| 92 | +-- >>> crt 2 5 3 9 |
| 93 | +-- 12 |
| 94 | +crt :: (Eq a, Integral a, Show a) => a -> a -> a -> a -> a |
| 95 | +crt !a1 !m1 !a2 !m2 = let !(s1,s2) = case exEuclid m2 m1 of |
| 96 | + (1,b,c) -> (b `mod` m1, c `mod` m2) |
| 97 | + (-1,b,c) -> ((-b) `mod` m1, (-c) `mod` m2) |
| 98 | + (g,a,b) -> error $ "CRT: " ++ show m1 ++ " and " ++ show m2 ++ " not coprime; gcd=" ++ show (abs g) |
| 99 | + !_ = assert (s1 == recipM m2 m1) () |
| 100 | + !_ = assert (s2 == recipM m1 m2) () |
| 101 | + c1 = ((a1 `mod` m1) * s1) `rem` m1 |
| 102 | + c2 = ((a2 `mod` m2) * s2) `rem` m2 |
| 103 | + m = m1 * m2 |
| 104 | + result = c1 * m2 + c2 * m1 |
| 105 | + in if result < m then |
| 106 | + result |
| 107 | + else |
| 108 | + result - m |
| 109 | +{-# SPECIALIZE crt :: Int64 -> Int64 -> Int64 -> Int64 -> Int64 #-} |
| 110 | + |
| 111 | +-- | |
| 112 | +-- Assumption: @gcd m1 m2 == 1@ |
| 113 | +crt' :: (KnownNat m1, KnownNat m2) => IntMod m1 -> IntMod m2 -> IntMod (m1 * m2) |
| 114 | +crt' x1@(IntMod !a1) x2@(IntMod !a2) = let !(s1,s2) = case exEuclid m2 m1 of |
| 115 | + (1,b,c) -> (b `mod` m1, c `mod` m2) |
| 116 | + (-1,b,c) -> ((-b) `mod` m1, (-c) `mod` m2) |
| 117 | + (g,a,b) -> error $ "CRT: " ++ show m1 ++ " and " ++ show m2 ++ " not coprime; gcd=" ++ show (abs g) |
| 118 | + !_ = assert (s1 == recipM m2 m1) () |
| 119 | + !_ = assert (s2 == recipM m1 m2) () |
| 120 | + c1 = (a1 * s1) `rem` m1 |
| 121 | + c2 = (a2 * s2) `rem` m2 |
| 122 | + result = c1 * m2 + c2 * m1 |
| 123 | + in IntMod $ if result < m then |
| 124 | + result |
| 125 | + else |
| 126 | + result - m |
| 127 | + where |
| 128 | + m1 = fromIntegral (natVal x1) |
| 129 | + m2 = fromIntegral (natVal x2) |
| 130 | + m = m1 * m2 |
| 131 | + |
| 132 | +-- |
| 133 | +-- Tests |
| 134 | +-- |
| 135 | + |
| 136 | +instance KnownNat m => QC.Arbitrary (IntMod m) where |
| 137 | + arbitrary = let result = IntMod <$> QC.choose (0, m - 1) |
| 138 | + m = fromIntegral (natVal (proxy result)) |
| 139 | + in result |
| 140 | + where |
| 141 | + proxy :: f (IntMod m) -> Proxy m |
| 142 | + proxy _ = Proxy |
| 143 | + |
| 144 | +runTests :: IO () |
| 145 | +runTests = do |
| 146 | + QC.quickCheck $ QC.forAll (QC.choose (2, 10^9+9)) $ \m x -> prop_recipM x (QC.Positive m) |
| 147 | + QC.quickCheck $ QC.withMaxSuccess 10000 $ QC.forAll (QC.choose (2, 10^9+9)) $ \m1 -> QC.forAll (QC.choose (2, 10^9+9)) $ \m2 x y -> prop_crt x (QC.Positive m1) y (QC.Positive m2) |
| 148 | + QC.quickCheck $ QC.withMaxSuccess 10000 $ QC.forAll (QC.choose (2, 10^9+9)) $ \m1 -> QC.forAll (QC.choose (2, 10^9+9)) $ \m2 x y -> prop_crt' x (QC.Positive m1) y (QC.Positive m2) |
| 149 | + |
| 150 | +prop_recipM :: Int64 -> QC.Positive Int64 -> QC.Property |
| 151 | +prop_recipM x (QC.Positive m) = gcd x m == 1 && m > 1 && m <= 10^9 + 9 QC.==> |
| 152 | + let y = recipM x m |
| 153 | + in 0 <= y QC..&&. y < m QC..&&. ((x `mod` m) * recipM x m) `mod` m QC.=== 1 |
| 154 | + |
| 155 | +prop_crt :: Int64 -> QC.Positive Int64 -> Int64 -> QC.Positive Int64 -> QC.Property |
| 156 | +prop_crt a1 (QC.Positive m1) a2 (QC.Positive m2) |
| 157 | + = gcd m1 m2 == 1 QC.==> let r = crt a1 m1 a2 m2 |
| 158 | + in r `mod` m1 QC.=== a1 `mod` m1 QC..&&. r `mod` m2 QC.=== a2 `mod` m2 |
| 159 | + |
| 160 | +prop_crt' :: Int64 -> QC.Positive Int64 -> Int64 -> QC.Positive Int64 -> QC.Property |
| 161 | +prop_crt' a1 (QC.Positive m1) a2 (QC.Positive m2) |
| 162 | + = gcd m1 m2 == 1 QC.==> case (someNatVal (fromIntegral m1), someNatVal (fromIntegral m2)) of |
| 163 | + (SomeNat p1, SomeNat p2) -> |
| 164 | + let x = fromIntegral a1 `asIntModProxy` p1 |
| 165 | + y = fromIntegral a2 `asIntModProxy` p2 |
| 166 | + IntMod r = crt' x y |
| 167 | + in 0 <= r QC..&&. r < m1 * m2 QC..&&. r `mod` m1 QC.=== a1 `mod` m1 QC..&&. r `mod` m2 QC.=== a2 `mod` m2 |
| 168 | + where |
| 169 | + asIntModProxy :: IntMod m -> Proxy m -> IntMod m |
| 170 | + asIntModProxy x _ = x |
0 commit comments