Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jacobi facelifting #103

Merged
merged 8 commits into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 59 additions & 88 deletions Math/NumberTheory/Moduli/Jacobi.hs
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)

Expand All @@ -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 &#177;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
4 changes: 2 additions & 2 deletions Math/NumberTheory/Moduli/Sqrt.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import Math.NumberTheory.Utils (shiftToOddCount, splitOff)
-- the result is @Nothing@.
sqrtModP :: Integer -> Integer -> Maybe Integer
sqrtModP n 2 = Just (n `mod` 2)
sqrtModP n prime = case jacobi' n prime of
sqrtModP n prime = case jacobi n prime of
MinusOne -> Nothing
Zero -> Just 0
One -> Just (sqrtModP' (n `mod` prime) prime)
Expand Down Expand Up @@ -215,7 +215,7 @@ findNonSquare n
where
primelist = [3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67]
++ sieveFrom (68 + n `rem` 4) -- prevent sharing
search (p:ps) = case jacobi' p n of
search (p:ps) = case jacobi p n of
MinusOne -> p
_ -> search ps
search _ = error "Should never have happened, prime list exhausted."
Expand Down
2 changes: 1 addition & 1 deletion Math/NumberTheory/Primes/Testing/Probabilistic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ lucasTest n
square = isPossibleSquare2 n && r*r == n
r = integerSquareRoot n
d = find True 5
find !pos cd = case jacobi' (n `rem` cd) cd of
find !pos cd = case jacobi (n `rem` cd) cd of
MinusOne -> if pos then cd else (-cd)
Zero -> if cd == n then 1 else 0
One -> find (not pos) (cd+2)
Expand Down
14 changes: 14 additions & 0 deletions Math/NumberTheory/Utils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import GHC.Base

import GHC.Integer
import GHC.Integer.GMP.Internals
import GHC.Natural

import Data.Bits

Expand All @@ -42,6 +43,7 @@ uncheckedShiftR (W# w#) (I# i#) = W# (uncheckedShiftRL# w# i#)
"shiftToOddCount/Int" shiftToOddCount = shiftOCInt
"shiftToOddCount/Word" shiftToOddCount = shiftOCWord
"shiftToOddCount/Integer" shiftToOddCount = shiftOCInteger
"shiftToOddCount/Natural" shiftToOddCount = shiftOCNatural
#-}
{-# INLINE [1] shiftToOddCount #-}
shiftToOddCount :: Integral a => a -> (Int, a)
Expand Down Expand Up @@ -75,6 +77,18 @@ shiftOCInteger n@(Jn# bn#) = case bigNatZeroCount bn# of
0# -> (0, n)
z# -> (I# z#, n `shiftRInteger` z#)

-- | Specialised version for @'Natural'@.
-- Precondition: argument nonzero (not checked).
shiftOCNatural :: Natural -> (Int, Natural)
shiftOCNatural n@(NatS# i#) =
case shiftToOddCount# i# of
(# z#, w# #)
| isTrue# (z# ==# 0#) -> (0, n)
| otherwise -> (I# z#, NatS# w#)
shiftOCNatural n@(NatJ# bn#) = case bigNatZeroCount bn# of
0# -> (0, n)
z# -> (I# z#, NatJ# (bn# `shiftRBigNat` z#))

-- | Count trailing zeros in a @'BigNat'@.
-- Precondition: argument nonzero (not checked, Integer invariant).
bigNatZeroCount :: BigNat -> Int#
Expand Down
1 change: 1 addition & 0 deletions arithmoi.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ benchmark criterion
other-modules:
Math.NumberTheory.ArithmeticFunctionsBench
Math.NumberTheory.GCDBench
Math.NumberTheory.JacobiBench
Math.NumberTheory.MertensBench
Math.NumberTheory.PowersBench
Math.NumberTheory.PrimesBench
Expand Down
2 changes: 2 additions & 0 deletions benchmark/Bench.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Gauge.Main

import Math.NumberTheory.ArithmeticFunctionsBench as ArithmeticFunctions
import Math.NumberTheory.GCDBench as GCD
import Math.NumberTheory.JacobiBench as Jacobi
import Math.NumberTheory.MertensBench as Mertens
import Math.NumberTheory.PowersBench as Powers
import Math.NumberTheory.PrimesBench as Primes
Expand All @@ -13,6 +14,7 @@ import Math.NumberTheory.SieveBlockBench as SieveBlock
main = defaultMain
[ ArithmeticFunctions.benchSuite
, GCD.benchSuite
, Jacobi.benchSuite
, Mertens.benchSuite
, Powers.benchSuite
, Primes.benchSuite
Expand Down
22 changes: 22 additions & 0 deletions benchmark/Math/NumberTheory/JacobiBench.hs
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
]
Loading