Main | Back to Basics: Newton-Raphson Method in Powershell »
Wednesday
Jun202012

Testing for Prime Numbers Using Powershell

 So what is a prime number anyway. Lets see what Wikipedia has to say:

A prime number (or a prime) is a natural number greater than 1 that has no positive divisors other than 1 and itself. A natural number greater than 1 that is not a prime number is called a composite number. For example, 5 is prime, as only 1 and 5 divide it, whereas 6 is composite, since it has the divisors 2 and 3 in addition to 1 and 6. The fundamental theorem of arithmetic establishes the central role of primes in number theory: any integer greater than 1 can be expressed as a product of primes that is unique up to ordering. This theorem requires excluding 1 as a prime.
Since there are infintely number of primes, it's always fun to see how fast and how many we can find. The largest know prime number has nearly 13 million decimal digits!

 

Now thats out of the way lets see how we can test to see whether a number is prime or not using Powershell. Today we will look at a few different methods, including my favorite, Sieve of Eratosthenes. The first method we will look at is using a brute force method which is by far the slowest. Now lets look at some code:
function Test-isPrime            
{            
    <#
    .Synopsis
       Tests if a number is a prime using the brute force method.
    .EXAMPLE
       Test-isPrime (1..1000)
    .EXAMPLE
       1..1000 | Test-isPrime
    #>            
    [CmdletBinding()]          
            
    Param            
    (            
        [Parameter(Mandatory=$true,            
                   ValueFromPipeLine=$true,            
                   ValueFromPipelineByPropertyName=$true,            
                   Position=0)]            
        [int[]]$number            
    )            
            
    Begin            
    {            
        $properties = @{'Number'=$null;'Prime'=$null}            
    }            
            
    Process            
    {            
        foreach ($n in $number)            
        {            
            $i = 2            
            $properties.Number = $n            
            $properties.Prime = $true            
               
            if ($n -lt 2)            
            {            
                 $properties.Prime = $false            
            }            
               
            if ($n -eq 2)            
            {            
                $properties.Prime = $true            
            }            
            
            while ($i -lt $n)            
            {            
                if ($n % $i -eq 0)            
                {            
                    $properties.Prime = $false            
                }            
            
                $i++            
            
            }
            $object = New-Object PSObject –Prop $properties            
            Write-Output $object            
        }            
    }            
    End            
    {            
    }            
}
Now lets measure how long it takes to test 1 through 10,000.
PS C:\Windows\system32> Measure-Command {1..10000 | Test-isPrime}            
            
Days              	: 0            
Hours             	: 0            
Minutes           	: 3            
Seconds           	: 20            
Milliseconds      	: 93            
Ticks             	: 2000930921            
TotalDays         	: 0.00231589226967593            
TotalHours        	: 0.0555814144722222            
TotalMinutes      	: 3.33488486833333            
TotalSeconds      	: 200.0930921            
TotalMilliseconds 	: 200093.0921
This certainly isn't the most efficient way to check whether a number is a prime. So lets spice it up a bit and test if a number is a prime by checking only the numbers between 1 and n/2-1. If we find such a divisor that will be enough to say that n isn't prime:
function Test-isPrime2            
{            
    <#
    .Synopsis
       Tests if a number is a prime by checking only the numbers between 1 and n/2-1. If 
       we find such a divisor that will be enough to say that n isn’t prime.
    .EXAMPLE
       Test-isPrime2 (1..1000)
    .EXAMPLE
       1..1000 | Test-isPrime2
    #>            
            
    [CmdletBinding()]           
            
    Param            
    (            
        [Parameter(Mandatory=$true,            
                   ValueFromPipeLine=$true,            
                   ValueFromPipelineByPropertyName=$true,            
                   Position=0)]            
        [int[]]$number            
    )            
            
    Begin            
    {            
        $properties = @{'Number'=$null;'Prime'=$null}            
    }            
            
    Process            
    {            
        foreach ($n in $number)            
        {            
            $i = 2            
            $properties.Number = $n            
            $properties.Prime = $true            
            
            if ($n -lt 2)            
            {            
                $properties.Prime = $false            
            }            
                
            if ($n -eq 2)            
            {            
                $properties.Prime = $true            
            }            
            
            while ($i -le $n/2)            
            {            
                if ($n % $i -eq 0)            
                {            
                    $properties.Prime = $false            
                }            
            
                $i++            
            
            }
            $object = New-Object PSObject –Prop $properties            
            Write-Output $object            
        }            
    }            
    End            
    {            
    }            
}
Now lets measure how long it takes to test 1 through 10,000.
PS C:\Windows\system32> Measure-Command {1..10000 | Test-isPrime2}            
            
Days              	: 0            
Hours             	: 0            
Minutes           	: 2            
Seconds           	: 30            
Milliseconds     	: 357            
Ticks             	: 1503579593            
TotalDays         	: 0.00174025415856481            
TotalHours        	: 0.0417660998055556            
TotalMinutes      	: 2.50596598833333            
TotalSeconds      	: 150.3579593            
TotalMilliseconds 	: 150357.9593
Slightly better, but still not very effective for larger numbers. Lets try to improve our code by checking our number against [2, sqrt(n)]. This is correct because if n isn't prime it can be represented as p*q = n. Of course if p > sqrt(n), which we assume can't be true, that will mean that q < sqrt(n).
function Test-isPrime3            
{            
    <#
    .Synopsis
       Tests if a number is a prime by checking against [2, sqrt(n)]. This is correct, 
       because if n isn’t prime it can be represented as p*q = n. Of course if 
       p > sqrt(n), which we assume can’t be true, that will mean that q < sqrt(n).
    .EXAMPLE
       Test-isPrime3 (1..1000)
    .EXAMPLE
       1..1000 | Test-isPrime3
    #>            
            
    [CmdletBinding()]           
            
    Param            
    (            
        [Parameter(Mandatory=$true,            
                   ValueFromPipeLine=$true,            
                   ValueFromPipelineByPropertyName=$true,            
                   Position=0)]            
        [int[]]$number            
    )            
            
    Begin            
    {            
        $properties = @{'Number'=$null;'Prime'=$null}            
    }            
            
    Process            
    {            
        foreach ($n in $number)            
        {            
            $i = 2            
            $sqrtN = [math]::sqrt($n)            
            $properties.Number = $n            
            $properties.Prime = $true            
            
            if ($n -lt 2)            
            {            
                $properties.Prime = $false            
            }            
               
            if ($n -eq 2)            
            {            
                $properties.Prime = $true            
            }            
            
            while ($i -le $sqrtN)            
            {            
                if ($n % $i -eq 0)            
                {            
                    $properties.Prime = $false            
                }            
            
                $i++            
            
            }
            $object = New-Object PSObject –Prop $properties            
            Write-Output $object            
        }            
    }            
    End            
    {            
    }            
}
Now lets measure how long it takes to test 1 through 10,000.
PS C:\Windows\system32> Measure-Command {1..10000 | Test-isPrime3}            
            
Days              	: 0            
Hours             	: 0            
Minutes           	: 0            
Seconds           	: 6            
Milliseconds      	: 32            
Ticks             	: 60323509            
TotalDays         	: 6.98188761574074E-05            
TotalHours        	: 0.00167565302777778            
TotalMinutes      	: 0.100539181666667            
TotalSeconds      	: 6.0323509            
TotalMilliseconds 	: 6032.3509
As you can see this is a dramatic increase in performance, but we can still do better. Time to implement the Sieve of Eratosthenes.
function Test-isPrime4            
{            
    <#
    .Synopsis
       Tests if a number is a prime by using the Eratosthenes sieve.
    .EXAMPLE
       Test-isPrime4 -max 10
    #>            
            
    Param            
    (            
        [Parameter(Mandatory=$true,            
                   ValueFromPipeLine=$true,            
                   ValueFromPipelineByPropertyName=$true,            
                   Position=0)]            
        [int]$maxnumber            
    )            
            
    Begin            
    {            
        $sieve = New-Object 'System.Collections.Generic.SortedDictionary[int, bool]'            
        for ($i=2;$i -lt $maxnumber+1; $i++)            
        {            
            <#Intially set all numbers in the list to true. We will use
	    Eratosthenes sieve to find which integers are not prime.#>            
            $sieve[$i] = $true            
        }            
    }            
             
 Process            
 {             
    foreach ($i in @($sieve.GetEnumerator()))            
    {            
         <#Starting from p, count up in increments of p and mark each of these 
	 numbers greater than p itself in the list. These numbers will be 2p, 3p, 
	 4p, etc.; note that some of them may have already been marked. 
	 Initially, let p equal 2, the first prime number #>            
        $p = 2            
               
        while (($i.Key * $p) -le $maxnumber)            
        {            
            if ($sieve.ContainsKey($i.Key * $p))            
            {            
                $sieve[($i.Key * $p)] = $false            
            }            
                
            $p++            
         }            
    }            
              
    Write-Output $sieve            
 }            
             
 End            
 {            
 }            
}
Now lets measure how long it takes to test 1 through 10,000.
PS C:\Windows\system32> Measure-Command {Test-isPrime4 -maxnumber 10000}            
            
Days              	: 0            
Hours             	: 0            
Minutes           	: 0            
Seconds           	: 2            
Milliseconds      	: 979            
Ticks             	: 29797250            
TotalDays         	: 3.44875578703704E-05            
TotalHours        	: 0.000827701388888889            
TotalMinutes      	: 0.0496620833333333            
TotalSeconds      	: 2.979725            
TotalMilliseconds 	: 2979.725
Implementing the right algortihm, with relatively minor changes to our function, can have substantial benefits. Even if it is an ancient algorithm!

 

PrintView Printer Friendly Version

EmailEmail Article to Friend

References (3)

References allow you to track sources for this article, as well as articles that were written in response to this article.

Reader Comments (1)

I changed the line:

while (($i.Key * $p) -le $maxnumber)

to:

while (($i.Key * $p) -le $maxnumber -and $sieve[$i.key])

to stop marking numbers more than once. Any number that has already been marked will have had all its multiples marked. I halved the time taken on my machine.
May 20, 2013 | Unregistered CommenterDavid

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
All HTML will be escaped. Hyperlinks will be created for URLs automatically.