atrophy-0.1.0.0: src/Atrophy/LongMultiplication.hs
{-# LANGUAGE
TypeApplications
, ScopedTypeVariables
, LambdaCase
#-}
module Atrophy.LongMultiplication where
import Data.WideWord.Word128
import Data.Word
import qualified Data.Primitive.Contiguous as Contiguous
import Data.Primitive.Contiguous (PrimArray, MutableSliced, Mutable)
import Control.Monad.ST.Strict (ST)
import Data.STRef.Strict (newSTRef, modifySTRef, readSTRef)
import Data.Bits
import Data.Foldable (for_)
{-# INLINE multiply256By128UpperBits #-}
multiply256By128UpperBits :: Word128 -> Word128 -> Word128 -> Word128
multiply256By128UpperBits aHi aLo b =
let
-- Break a and b into little-endian 64-bit chunks
aChunks :: PrimArray Word64
aChunks = Contiguous.quadrupleton
(word128Lo64 aLo)
(word128Hi64 aLo)
(word128Lo64 aHi)
(word128Hi64 aHi)
bChunks :: PrimArray Word64
bChunks = Contiguous.doubleton
(word128Lo64 b)
(word128Hi64 b)
-- Multiply b by a, one chunk of b at a time
prod :: PrimArray Word64
prod = Contiguous.create $ do
prod' <- Contiguous.replicateMut 6 0
flip Contiguous.itraverse_ bChunks $ \bIndex bDigit -> do
pSize <- Contiguous.sizeMut prod'
multiply256By64Helper
(Contiguous.sliceMut prod' bIndex (pSize - bIndex))
aChunks
bDigit
pure prod'
in Word128
{ word128Hi64 = Contiguous.index prod 5
, word128Lo64 = Contiguous.index prod 4
}
{-# INLINE multiply256By64Helper #-}
multiply256By64Helper :: forall s. MutableSliced PrimArray s Word64 -> PrimArray Word64 -> Word64 -> ST s ()
multiply256By64Helper _ _ 0 = pure ()
multiply256By64Helper prod a b = do
carry <- newSTRef 0
productSize <- Contiguous.sizeMut prod
let
aSize = Contiguous.size a
productLo :: MutableSliced PrimArray s Word64
productLo = Contiguous.sliceMut prod 0 aSize
productHi :: MutableSliced PrimArray s Word64
productHi = Contiguous.sliceMut prod aSize (productSize - aSize)
-- Multiply each of the digits in a by b, adding them into the 'prod' value.
-- We don't zero out prod, because we this will be called multiple times, so it probably contains a previous iteration's partial prod, and we're adding + carrying on top of it
for_ [0..aSize - 1] $ \i -> do
p <- Contiguous.read productLo i
let aDigit = Contiguous.index a i
modifySTRef carry $ \x -> x
+ Word128 0 p
+ (Word128 0 aDigit * Word128 0 b)
Contiguous.write prod i . word128Lo64 =<< readSTRef carry
modifySTRef carry (`unsafeShiftR` 64)
let productHiSize = productSize - aSize
for_ [0..productHiSize - 1] $ \i -> do
p <- Contiguous.read productHi i
modifySTRef carry (+ Word128 0 p)
Contiguous.write productHi i . word128Lo64 =<< readSTRef carry
modifySTRef carry (`unsafeShiftR` 64)
readSTRef carry >>= \case
0 -> pure ()
_ -> error "carry overflow during multiplication!"
-- compute prod += a * b
{-# INLINE longMultiply #-}
longMultiply :: forall s. PrimArray Word64 -> Word64 -> Mutable PrimArray s Word64 -> ST s ()
longMultiply a b prod = do
prod' <- Contiguous.toSliceMut prod
multiply256By64Helper prod' a b