-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #103 from Bodigrim/jacobi
Jacobi facelifting
- Loading branch information
Showing
8 changed files
with
167 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,35 @@ | ||
-- | | ||
-- Module: Math.NumberTheory.Moduli.Jacobi | ||
-- Copyright: (c) 2011 Daniel Fischer, 2017 Andrew Lelechenko | ||
-- Copyright: (c) 2011 Daniel Fischer, 2017-2018 Andrew Lelechenko | ||
-- Licence: MIT | ||
-- Maintainer: Andrew Lelechenko <[email protected]> | ||
-- Stability: Provisional | ||
-- Portability: Non-portable (GHC extensions) | ||
-- | ||
-- Jacobi symbol. | ||
-- <https://en.wikipedia.org/wiki/Jacobi_symbol Jacobi symbol> | ||
-- is a generalization of the Legendre symbol, useful for primality | ||
-- testing and integer factorization. | ||
-- | ||
|
||
{-# LANGUAGE CPP #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE BangPatterns #-} | ||
{-# LANGUAGE CPP #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
|
||
module Math.NumberTheory.Moduli.Jacobi | ||
( JacobiSymbol(..) | ||
, jacobi | ||
, jacobi' | ||
) where | ||
|
||
import Data.Array.Unboxed | ||
import Data.Bits | ||
#if __GLASGOW_HASKELL__ < 803 | ||
import Data.Semigroup | ||
#endif | ||
import Numeric.Natural | ||
|
||
import Math.NumberTheory.Unsafe | ||
import Math.NumberTheory.Utils | ||
|
||
-- | Type for result of 'jacobi'. | ||
-- | Represents three possible values of | ||
-- <https://en.wikipedia.org/wiki/Jacobi_symbol Jacobi symbol>. | ||
data JacobiSymbol = MinusOne | Zero | One | ||
deriving (Eq, Ord, Show) | ||
|
||
|
@@ -47,103 +49,72 @@ negJS = \case | |
Zero -> Zero | ||
One -> MinusOne | ||
|
||
-- | Jacobi symbol of two numbers. | ||
-- The \"denominator\" must be odd and positive, this condition is checked. | ||
-- | <https://en.wikipedia.org/wiki/Jacobi_symbol Jacobi symbol> of two arguments. | ||
-- The lower argument (\"denominator\") must be odd and positive, | ||
-- this condition is checked. | ||
-- | ||
-- If both numbers have a common prime factor, the result | ||
-- is @0@, otherwise it is ±1. | ||
-- If arguments have a common factor, the result | ||
-- is 'Zero', otherwise it is 'MinusOne' or 'One'. | ||
-- | ||
-- > > jacobi 1001 9911 | ||
-- > Zero -- arguments have a common factor 11 | ||
-- > > jacobi 1001 9907 | ||
-- > MinusOne | ||
{-# SPECIALISE jacobi :: Integer -> Integer -> JacobiSymbol, | ||
Natural -> Natural -> JacobiSymbol, | ||
Int -> Int -> JacobiSymbol, | ||
Word -> Word -> JacobiSymbol | ||
#-} | ||
jacobi :: (Integral a, Bits a) => a -> a -> JacobiSymbol | ||
jacobi _ 1 = One | ||
jacobi a b | ||
| b < 0 = error "Math.NumberTheory.Moduli.jacobi: negative denominator" | ||
| evenI b = error "Math.NumberTheory.Moduli.jacobi: even denominator" | ||
| b == 1 = One | ||
| otherwise = jacobi' a b -- b odd, > 1 | ||
|
||
-- Invariant: b > 1 and odd | ||
-- | Jacobi symbol of two numbers without validity check of | ||
-- the \"denominator\". | ||
{-# SPECIALISE jacobi' :: Integer -> Integer -> JacobiSymbol, | ||
Int -> Int -> JacobiSymbol, | ||
Word -> Word -> JacobiSymbol | ||
#-} | ||
| b < 0 = error "Math.NumberTheory.Moduli.jacobi: negative denominator" | ||
| evenI b = error "Math.NumberTheory.Moduli.jacobi: even denominator" | ||
| otherwise = jacobi' a b -- b odd, > 1 | ||
|
||
jacobi' :: (Integral a, Bits a) => a -> a -> JacobiSymbol | ||
jacobi' 0 _ = Zero | ||
jacobi' 1 _ = One | ||
jacobi' a b | ||
| a == 0 = Zero | ||
| a == 1 = One | ||
| a < 0 = let n | rem4 b == 1 = One | ||
| otherwise = MinusOne | ||
-- Blech, minBound may pose problems | ||
(z,o) = shiftToOddCount (abs $ toInteger a) | ||
s | evenI z || unsafeAt jac2 (rem8 b) == 1 = n | ||
| otherwise = negJS n | ||
in s <> jacobi' (fromInteger o) b | ||
| a >= b = case a `rem` b of | ||
0 -> Zero | ||
r -> jacPS One r b | ||
| evenI a = case shiftToOddCount a of | ||
(z,o) -> let r | rem4 o .&. rem4 b == 1 = One | ||
| otherwise = MinusOne | ||
s | evenI z || unsafeAt jac2 (rem8 b) == 1 = r | ||
| otherwise = negJS r | ||
in jacOL s b o | ||
| otherwise = case rem4 a .&. rem4 b of | ||
3 -> jacOL MinusOne b a | ||
_ -> jacOL One b a | ||
| a < 0 = let n = if rem4is3 b then MinusOne else One | ||
(z, o) = shiftToOddCount (negate a) | ||
s = if evenI z || rem8is1or7 b then n else negJS n | ||
in s <> jacobi' o b | ||
| a >= b = case a `rem` b of | ||
0 -> Zero | ||
r -> jacPS One r b | ||
| evenI a = case shiftToOddCount a of | ||
(z, o) -> let r = if rem4is3 o && rem4is3 b then MinusOne else One | ||
s = if evenI z || rem8is1or7 b then r else negJS r | ||
in jacOL s b o | ||
| otherwise = jacOL (if rem4is3 a && rem4is3 b then MinusOne else One) b a | ||
|
||
-- numerator positive and smaller than denominator | ||
{-# SPECIALISE jacPS :: JacobiSymbol -> Integer -> Integer -> JacobiSymbol, | ||
JacobiSymbol -> Int -> Int -> JacobiSymbol, | ||
JacobiSymbol -> Word -> Word -> JacobiSymbol | ||
#-} | ||
jacPS :: (Integral a, Bits a) => JacobiSymbol -> a -> a -> JacobiSymbol | ||
jacPS j a b | ||
| evenI a = case shiftToOddCount a of | ||
(z,o) | evenI z || unsafeAt jac2 (rem8 b) == 1 -> | ||
jacOL (if rem4 o .&. rem4 b == 3 then (negJS j) else j) b o | ||
| otherwise -> | ||
jacOL (if rem4 o .&. rem4 b == 3 then j else (negJS j)) b o | ||
| otherwise = jacOL (if rem4 a .&. rem4 b == 3 then (negJS j) else j) b a | ||
jacPS !acc a b | ||
| evenI a = case shiftToOddCount a of | ||
(z, o) | ||
| evenI z || rem8is1or7 b -> jacOL (if rem4is3 o && rem4is3 b then negJS acc else acc) b o | ||
| otherwise -> jacOL (if rem4is3 o && rem4is3 b then acc else negJS acc) b o | ||
| otherwise = jacOL (if rem4is3 a && rem4is3 b then negJS acc else acc) b a | ||
|
||
-- numerator odd, positive and larger than denominator | ||
{-# SPECIALISE jacOL :: JacobiSymbol -> Integer -> Integer -> JacobiSymbol, | ||
JacobiSymbol -> Int -> Int -> JacobiSymbol, | ||
JacobiSymbol -> Word -> Word -> JacobiSymbol | ||
#-} | ||
jacOL :: (Integral a, Bits a) => JacobiSymbol -> a -> a -> JacobiSymbol | ||
jacOL j a b | ||
| b == 1 = j | ||
| otherwise = case a `rem` b of | ||
0 -> Zero | ||
r -> jacPS j r b | ||
jacOL !acc _ 1 = acc | ||
jacOL !acc a b = case a `rem` b of | ||
0 -> Zero | ||
r -> jacPS acc r b | ||
|
||
-- Utilities | ||
|
||
-- For large Integers, going via Int is much faster than bit-fiddling | ||
-- on the Integer, so we do that. | ||
{-# SPECIALISE evenI :: Integer -> Bool, | ||
Int -> Bool, | ||
Word -> Bool | ||
#-} | ||
evenI :: Integral a => a -> Bool | ||
evenI n = fromIntegral n .&. 1 == (0 :: Int) | ||
|
||
{-# SPECIALISE rem4 :: Integer -> Int, | ||
Int -> Int, | ||
Word -> Int | ||
#-} | ||
rem4 :: Integral a => a -> Int | ||
rem4 n = fromIntegral n .&. 3 | ||
-- Sadly, GHC do not optimise `Prelude.even` to a bit test automatically. | ||
evenI :: Bits a => a -> Bool | ||
evenI n = not (n `testBit` 0) | ||
|
||
{-# SPECIALISE rem8 :: Integer -> Int, | ||
Int -> Int, | ||
Word -> Int | ||
#-} | ||
rem8 :: Integral a => a -> Int | ||
rem8 n = fromIntegral n .&. 7 | ||
-- For an odd input @n@ test whether n `rem` 4 == 1 | ||
rem4is3 :: Bits a => a -> Bool | ||
rem4is3 n = n `testBit` 1 | ||
|
||
jac2 :: UArray Int Int | ||
jac2 = array (0,7) [(0,0),(1,1),(2,0),(3,-1),(4,0),(5,-1),(6,0),(7,1)] | ||
-- For an odd input @n@ test whether (n `rem` 8) `elem` [1, 7] | ||
rem8is1or7 :: Bits a => a -> Bool | ||
rem8is1or7 n = n `testBit` 1 == n `testBit` 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{-# OPTIONS_GHC -fno-warn-type-defaults #-} | ||
|
||
module Math.NumberTheory.JacobiBench | ||
( benchSuite | ||
) where | ||
|
||
import Data.Bits | ||
import Gauge.Main | ||
import Numeric.Natural | ||
|
||
import Math.NumberTheory.Moduli.Jacobi | ||
|
||
doBench :: (Integral a, Bits a) => (a -> a -> JacobiSymbol) -> a -> a | ||
doBench func lim = sum [ x + y | y <- [3, 5 .. lim], x <- [0..y], func x y == One ] | ||
|
||
benchSuite :: Benchmark | ||
benchSuite = bgroup "Jacobi" | ||
[ bench "jacobi/Int" $ nf (doBench jacobi :: Int -> Int) 2000 | ||
, bench "jacobi/Word" $ nf (doBench jacobi :: Word -> Word) 2000 | ||
, bench "jacobi/Integer" $ nf (doBench jacobi :: Integer -> Integer) 2000 | ||
, bench "jacobi/Natural" $ nf (doBench jacobi :: Natural -> Natural) 2000 | ||
] |
Oops, something went wrong.