packages feed

ac-library-hs-1.5.2.1: benchmarks/BenchLib/MulMod/Montgomery.hs

{-# LANGUAGE NamedFieldPuns #-}

module BenchLib.MulMod.Montgomery
  ( Montgomery (mMontgomery),
    new,
    umod,
    mulMod,
    mulModGenerated,
    generate,
    reduce,
  )
where

-- FIXME: test
-- TODO: Use MagicHash?

import Data.Bits (bit, (!>>.))
import Data.Word (Word32, Word64)

-- | Fast modular multiplication by Montgomery multiplication. The modulus value must be odd for
-- the speed.
data Montgomery = Montgomery
  { mMontgomery :: {-# UNPACK #-} !Word64,
    -- | R2 == (2^64) % MOD;
    r2Montgomery :: {-# UNPACK #-} !Word64,
    -- | MOD * NEG_INV % (2^32) == (2^32) - 1;
    negInvMontgomery :: {-# UNPACK #-} !Word32
  }

-- | Creates a `Montgomery` for modulus @m@.
new :: Word64 -> Montgomery
new m =
  let !negInv = inner 0 0 1 0
      !r2 = bit 32 `rem` m
      !r2' = r2 * r2 `rem` m
   in Montgomery m r2' $ fromIntegral negInv
  where
    inner :: Word64 -> Word64 -> Word64 -> Word64 -> Word64
    -- TODO: more efficient impplementation
    inner !negInv 32 !_ !_ = negInv
    inner !negInv !i !s !t
      -- REMARK: `!>>.` does not work here.
      -- \| even t = inner (negInv + s) (i + 1) (2 * s) ((t + m) !>>. 2)
      -- \| otherwise = inner negInv (i + 1) (2 * s) (t !>>. 2)
      | even t = inner (negInv + s) (i + 1) (2 * s) ((t + m) `div` 2)
      | otherwise = inner negInv (i + 1) (2 * s) (t `div` 2)

-- | Retrieves the modulus \(m\).
umod :: Montgomery -> Word64
umod = mMontgomery

-- | Calculates \(a \cdot b \bmod m\).
mulMod :: Montgomery -> Word64 -> Word64 -> Word64
mulMod m a b = reduce m $ generate m a * generate m b

-- | Calculates \(a \cdot b \bmod m\).
mulModGenerated :: Montgomery -> Word64 -> Word64 -> Word64
mulModGenerated m a b = reduce m $ a * b

-- | Retrieves \( a \cdot b \bbmod m from a \cdot b\).
generate :: Montgomery -> Word64 -> Word64
generate m x = reduce m $ x * r2Montgomery m

-- | Retrieves \( a \cdot b \bbmod m from a \cdot b\). Prefer `reduce32`.
reduce :: Montgomery -> Word64 -> Word64
reduce Montgomery {mMontgomery, negInvMontgomery} x =
  let !x' = (x + fromIntegral (as32 x * negInvMontgomery) * mMontgomery) !>>. 32
   in if x' < mMontgomery then x' else x' - mMontgomery
  where
    -- TODO: makes sense??
    as32 :: Word64 -> Word32
    as32 = fromIntegral