ac-library-hs-1.3.0.0: src/AtCoder/Extra/Math/Montgomery64.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE NamedFieldPuns #-}
-- | Fast modular multiplication for `Word64` using Montgomery multiplication. If the modulus value
-- is known to fit in 32 bits, use the @AtCoder.Internal.Barrett@ module instead.
--
-- @since 1.2.6.0
module AtCoder.Extra.Math.Montgomery64
( -- * Montgomery64
Montgomery64,
-- * Constructor
new,
fromVal,
-- * Accessor
umod,
-- * Montgomery form encoding
encode,
decode,
reduce,
-- * Calculations
addMod,
subMod,
mulMod,
powMod,
eq,
)
where
import AtCoder.Internal.Assert qualified as ACIA
import Data.Bits (bit, (!>>.))
import Data.WideWord.Word128 (Word128 (..))
import Data.Word (Word64)
import GHC.Exts (Proxy#)
import GHC.Stack (HasCallStack)
import GHC.TypeNats (KnownNat, natVal')
-- TODO: provide with newtype for Montgomery form?
-- | Fast modular multiplication for `Word64` using Montgomery64 multiplication.
--
-- @since 1.2.6.0
data Montgomery64 = Montgomery64
{ mM64 :: {-# UNPACK #-} !Word64,
rM64 :: {-# UNPACK #-} !Word64,
n2M64 :: {-# UNPACK #-} !Word64
}
deriving
( -- | @since 1.2.6.0
Eq,
-- | @since 1.2.6.0
Show
)
-- TODO: add unasfePerformIO?
-- TODO: remove NOINLINE?
-- | \(O(1)\) Static, shared storage of `Montgomery64`.
--
-- ==== Constraints
-- - \(m \le 2^{62})
-- - \(m\) is odd
--
-- @since 1.2.6.0
{-# NOINLINE new #-}
new :: forall a. (HasCallStack, KnownNat a) => Proxy# a -> Montgomery64
-- FIXME: test allocated once
new p = fromVal . fromIntegral $! natVal' p
-- | \(O(1)\) Creates a `Montgomery64` for a modulus value \(m\) of type `Word64` value.
--
-- ==== Constraints
-- - \(m \le 2^{62})
-- - \(m\) is odd
--
-- @since 1.2.6.0
{-# INLINE fromVal #-}
fromVal :: (HasCallStack) => Word64 -> Montgomery64
fromVal m =
let !m128 :: Word128 = fromIntegral m
!n2 = word128Lo64 $ (-m128) `mod` m128
!r = getR m 0
!_ = ACIA.runtimeAssert (r * m == -1) "AtCoder.Extra.Montgomery64.fromVal: internal implementation error"
in Montgomery64 m r n2
where
!_ = ACIA.runtimeAssert (odd m && m <= bit 62) $ "AtCoder.Extra.Montgomery64.fromVal: not given odd modulus value that is less than or equal to 2^62: " ++ show m
getR :: Word64 -> Int -> Word64
getR !acc i
| i >= 5 = -acc
| otherwise = getR (acc * (2 - m * acc)) (i + 1)
-- | \(O(1)\) Retrieves the modulus \(m\).
--
-- @since 1.2.6.0
{-# INLINE umod #-}
umod :: Montgomery64 -> Word64
umod Montgomery64 {mM64} = mM64
-- | \(O(1)\) Converts the given `Word64` to Montgomery form.
--
-- @since 1.2.6.0
{-# INLINE encode #-}
encode :: Montgomery64 -> Word64 -> Word64
encode mont@Montgomery64 {n2M64} x = reduce mont $! fromIntegral x * fromIntegral n2M64
-- | \(O(1)\) Retrieves the value from a Montgomery form of value.
--
-- @since 1.2.6.0
{-# INLINE decode #-}
decode :: Montgomery64 -> Word64 -> Word64
decode mont@Montgomery64 {mM64} x =
let !res = reduce mont $! fromIntegral x
in if res >= mM64 then res - mM64 else res
-- | \(O(1)\) Takes the mod in Montgomery form.
--
-- @since 1.2.6.0
{-# INLINE reduce #-}
reduce :: Montgomery64 -> Word128 -> Word64
reduce Montgomery64 {mM64, rM64} x =
word128Hi64 $!
(x + fromIntegral (word128Lo64 x * rM64) * fromIntegral mM64)
-- | \(O(1)\) Calculates \(a + b \bmod m\) in the Montgomery form.
{-# INLINE addMod #-}
addMod :: Word64 -> Word64 -> Word64 -> Word64
addMod m a b
| x' >= m = x' - m
| otherwise = x'
where
!x' = a + b
-- | \(O(1)\) Calculates \(a - b \bmod m\) in the Montgomery form.
{-# INLINE subMod #-}
subMod :: Word64 -> Word64 -> Word64 -> Word64
subMod m a b
| a >= b = a - b
| otherwise = a - b + m
-- | \(O(1)\) Calculates \(a^n \bmod m\) in the Montgomery form.
--
-- @since 1.2.6.0
{-# INLINE mulMod #-}
mulMod :: Montgomery64 -> Word64 -> Word64 -> Word64
mulMod mont a b = reduce mont $! fromIntegral a * fromIntegral b
-- | \(O(w)\) Calculates \(a^n \bmod m\) in the Montgomery form.
--
-- @since 1.2.6.0
{-# INLINE powMod #-}
powMod :: (HasCallStack) => Montgomery64 -> Word64 -> Int -> Word64
powMod mont x0 n0 = inner n0 (encode mont 1) x0
where
!_ = ACIA.runtimeAssert (0 <= n0) $ "AtCoder.Extra.Math.Montgomery64.powMod: given negative exponential `n`: " ++ show n0 ++ show "`"
inner :: Int -> Word64 -> Word64 -> Word64
inner !n !r !y
| n == 0 = r
| otherwise =
let !r' = if odd n then mulMod mont r y else r
!y' = mulMod mont y y
in inner (n !>>. 1) r' y'
-- | \(O(1)\) Compares two values of Montgomery form and returns whether they represent the same
-- value.
--
-- @since 1.2.6.0
{-# INLINE eq #-}
eq :: Word64 -> Word64 -> Word64 -> Bool
eq mM64 a b = a' == b'
where
!a' = if a < mM64 then a else a - mM64
!b' = if b < mM64 then b else b - mM64