From 3c2fdf758217dcff6ca886f2e3bbdda329901bc0 Mon Sep 17 00:00:00 2001 From: arthurvergacas Date: Thu, 14 Oct 2021 15:15:49 -0300 Subject: [PATCH 1/5] Add Fermat Primality Test --- Maths/FermatPrimalityTest.test.js | 19 +++++++ Maths/test/FermatPrimalityTest.js | 90 +++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 Maths/FermatPrimalityTest.test.js create mode 100644 Maths/test/FermatPrimalityTest.js diff --git a/Maths/FermatPrimalityTest.test.js b/Maths/FermatPrimalityTest.test.js new file mode 100644 index 000000000..04b1ff402 --- /dev/null +++ b/Maths/FermatPrimalityTest.test.js @@ -0,0 +1,19 @@ +import { modularExponentiation, fermatPrimeCheck } from '../FermatPrimalityTest' + +describe('modularExponentiation', () => { + it('should give the correct output for all exponentiations', () => { + expect(modularExponentiation(38, 220, 221)).toBe(1) + expect(modularExponentiation(24, 220, 221)).toBe(81) + }) +}) + +describe('fermatPrimeCheck', () => { + it('should give the correct output for prime and composite numbers', () => { + expect(fermatPrimeCheck(2, 50)).toBe(true) + expect(fermatPrimeCheck(10, 50)).toBe(false) + expect(fermatPrimeCheck(94286167, 50)).toBe(true) + expect(fermatPrimeCheck(83165867, 50)).toBe(true) + expect(fermatPrimeCheck(13268774, 50)).toBe(false) + expect(fermatPrimeCheck(13233852, 50)).toBe(false) + }) +}) diff --git a/Maths/test/FermatPrimalityTest.js b/Maths/test/FermatPrimalityTest.js new file mode 100644 index 000000000..144816348 --- /dev/null +++ b/Maths/test/FermatPrimalityTest.js @@ -0,0 +1,90 @@ +/** + * The Fermat primality test is a probabilistic test to determine whether a number is a probable prime. It relies on + * Fermat's Little Theorem, which states that if p is prime and a is not divisible by p, then + * + * a^(p - 1) % p = 1 + * + * However, there are certain numbers (so called Fermat Liars) that screw things up; + * if a is one of these liars the equation will hold even though p is composite. + * + * But not everything is lost! It's been proven that at least half of all integers aren't Fermat Liar (these ones called + * Fermat Witnesses). Thus, if we keep testing the primality with random integers, we can achieve higher reliability. + * + * The interesting about all of this is that since half of all integers are Fermat Witnesses, the precision gets really + * high really fast! Suppose that we make the test 50 times: the chance of getting only Fermat Liars in all runs is + * + * 1 / 2^50 = 8.8 * 10^-16 (a pretty small number) + * + * For comparison, the probability of a cosmic ray causing an error to your infalible program is around 1.4 * 10^-15. An + * order of magnitude below! + * + * You can find more about the Fermat primality test and its flaws here: + * https://en.wikipedia.org/wiki/Fermat_primality_test + */ + +/** + * Faster exponentiation that capitalize on the fact that we are only interested + * in the modulus of the exponentiation. + * + * Find out more about it here: https://en.wikipedia.org/wiki/Modular_exponentiation + * + * @param {number} base + * @param {number} exponent + * @param {number} modulus + */ +const modularExponentiation = (base, exponent, modulus) => { + if (modulus === 1) return 0 // after all, any x % 1 = 0 + + let result = 1 + base %= modulus // make sure that base < modulus + + while (exponent > 0) { + // if exponent is odd, multiply the result by the base + if (exponent % 2 === 1) { + result = (result * base) % modulus + exponent-- + } else { + exponent = exponent / 2 // exponent is even for sure + base = (base * base) % modulus + } + } + + return result +} + +/** + * Test if a given number n is prime or not. + * + * @param {number} n The number to check for primality + * @param {number} numberOfIterations The number of times to apply Fermat's Little Theorem + * @returns True if prime, false otherwise + */ +const fermatPrimeCheck = (n, numberOfIterations) => { + // first check for corner cases +<<<<<<< HEAD + if (n <= 1 || n === 4) return false +======= + if (n <= 1 || n == 4) return false +>>>>>>> 951c7258323a057041c0d128880982ddab303ee5 + if (n <= 3) return true // 2 and 3 are included here + + for (let i = 0; i < numberOfIterations; i++) { + // pick a random number between 2 and n - 2 +<<<<<<< HEAD + const randomNumber = Math.floor(Math.random() * (n - 1 - 2) + 2) +======= + let randomNumber = Math.floor(Math.random() * (n - 1 - 2) + 2) +>>>>>>> 951c7258323a057041c0d128880982ddab303ee5 + + // if a^(n - 1) % n is different than 1, n is composite + if (modularExponentiation(randomNumber, n - 1, n) !== 1) { + return false + } + } + + // if we arrived here without finding a Fermat Witness, this is almost guaranteed + // to be a prime number (or a Carmichael number, if you are unlucky) + return true +} + +export { modularExponentiation, fermatPrimeCheck } From c5e44d498ac5433099483677375abe49d6baf21e Mon Sep 17 00:00:00 2001 From: Roland Hummel Date: Thu, 14 Oct 2021 22:32:56 +0200 Subject: [PATCH 2/5] A few suggestions / modifications / fixes It seems you've accidentally swapped the implementation and the test file :) The overall comment describing the algorithm (VERY nice doc, by the way) is not "proper" JSdoc => only one leading asterisk. It's generally considered good style to start a comment block (both JSdoc and regular comments) with a single, short sentence. Further down, there were some git hiccups, most likely caused by merge conflicts? --- Maths/{test => }/FermatPrimalityTest.js | 17 +++++------------ Maths/{ => test}/FermatPrimalityTest.test.js | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) rename Maths/{test => }/FermatPrimalityTest.js (86%) rename Maths/{ => test}/FermatPrimalityTest.test.js (91%) diff --git a/Maths/test/FermatPrimalityTest.js b/Maths/FermatPrimalityTest.js similarity index 86% rename from Maths/test/FermatPrimalityTest.js rename to Maths/FermatPrimalityTest.js index 144816348..3b8c5661f 100644 --- a/Maths/test/FermatPrimalityTest.js +++ b/Maths/FermatPrimalityTest.js @@ -1,6 +1,7 @@ -/** - * The Fermat primality test is a probabilistic test to determine whether a number is a probable prime. It relies on - * Fermat's Little Theorem, which states that if p is prime and a is not divisible by p, then +/* + * The Fermat primality test is a probabilistic test to determine whether a number is a probable prime. + * + * It relies on Fermat's Little Theorem, which states that if p is prime and a is not divisible by p, then * * a^(p - 1) % p = 1 * @@ -53,7 +54,7 @@ const modularExponentiation = (base, exponent, modulus) => { } /** - * Test if a given number n is prime or not. + * Test if a given number n is prime or not. * * @param {number} n The number to check for primality * @param {number} numberOfIterations The number of times to apply Fermat's Little Theorem @@ -61,20 +62,12 @@ const modularExponentiation = (base, exponent, modulus) => { */ const fermatPrimeCheck = (n, numberOfIterations) => { // first check for corner cases -<<<<<<< HEAD if (n <= 1 || n === 4) return false -======= - if (n <= 1 || n == 4) return false ->>>>>>> 951c7258323a057041c0d128880982ddab303ee5 if (n <= 3) return true // 2 and 3 are included here for (let i = 0; i < numberOfIterations; i++) { // pick a random number between 2 and n - 2 -<<<<<<< HEAD const randomNumber = Math.floor(Math.random() * (n - 1 - 2) + 2) -======= - let randomNumber = Math.floor(Math.random() * (n - 1 - 2) + 2) ->>>>>>> 951c7258323a057041c0d128880982ddab303ee5 // if a^(n - 1) % n is different than 1, n is composite if (modularExponentiation(randomNumber, n - 1, n) !== 1) { diff --git a/Maths/FermatPrimalityTest.test.js b/Maths/test/FermatPrimalityTest.test.js similarity index 91% rename from Maths/FermatPrimalityTest.test.js rename to Maths/test/FermatPrimalityTest.test.js index 04b1ff402..d6605e079 100644 --- a/Maths/FermatPrimalityTest.test.js +++ b/Maths/test/FermatPrimalityTest.test.js @@ -1,4 +1,4 @@ -import { modularExponentiation, fermatPrimeCheck } from '../FermatPrimalityTest' +import { fermatPrimeCheck, modularExponentiation } from '../FermatPrimalityTest' describe('modularExponentiation', () => { it('should give the correct output for all exponentiations', () => { From 09ac20dec2a2ddb11040765b02dfd69216508374 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:34:05 +0000 Subject: [PATCH 3/5] Auto-update DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 8290405c9..4d412b9c3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -153,6 +153,7 @@ * [Factorial](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Factorial.js) * [Factors](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Factors.js) * [FareyApproximation](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/FareyApproximation.js) + * [FermatPrimalityTest](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/FermatPrimalityTest.js) * [Fibonacci](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Fibonacci.js) * [FindHcf](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/FindHcf.js) * [FindLcm](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/FindLcm.js) From ffb138adb18df3b947ee49fd232f6fc155debb05 Mon Sep 17 00:00:00 2001 From: arthurvergacas Date: Fri, 15 Oct 2021 12:21:48 -0300 Subject: [PATCH 4/5] Minor fixes - Add default number of iterations - Correct "corner cases" to "edge cases" - Make clear which bounds are inclusive and which are exclusive --- Maths/FermatPrimalityTest.js | 10 +++++----- Maths/test/FermatPrimalityTest.test.js | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Maths/FermatPrimalityTest.js b/Maths/FermatPrimalityTest.js index 3b8c5661f..aa79e4013 100644 --- a/Maths/FermatPrimalityTest.js +++ b/Maths/FermatPrimalityTest.js @@ -8,7 +8,7 @@ * However, there are certain numbers (so called Fermat Liars) that screw things up; * if a is one of these liars the equation will hold even though p is composite. * - * But not everything is lost! It's been proven that at least half of all integers aren't Fermat Liar (these ones called + * But not everything is lost! It's been proven that at least half of all integers aren't Fermat Liars (these ones called * Fermat Witnesses). Thus, if we keep testing the primality with random integers, we can achieve higher reliability. * * The interesting about all of this is that since half of all integers are Fermat Witnesses, the precision gets really @@ -60,14 +60,14 @@ const modularExponentiation = (base, exponent, modulus) => { * @param {number} numberOfIterations The number of times to apply Fermat's Little Theorem * @returns True if prime, false otherwise */ -const fermatPrimeCheck = (n, numberOfIterations) => { - // first check for corner cases +const fermatPrimeCheck = (n, numberOfIterations = 50) => { + // first check for edge cases if (n <= 1 || n === 4) return false if (n <= 3) return true // 2 and 3 are included here for (let i = 0; i < numberOfIterations; i++) { - // pick a random number between 2 and n - 2 - const randomNumber = Math.floor(Math.random() * (n - 1 - 2) + 2) + // pick a random number a, with 2 <= a < n - 2 (remember Math.random() range is [0, 1[ -> 1 exclusive) + const randomNumber = Math.floor(Math.random() * (n - 2) + 2) // if a^(n - 1) % n is different than 1, n is composite if (modularExponentiation(randomNumber, n - 1, n) !== 1) { diff --git a/Maths/test/FermatPrimalityTest.test.js b/Maths/test/FermatPrimalityTest.test.js index d6605e079..92fad4bcd 100644 --- a/Maths/test/FermatPrimalityTest.test.js +++ b/Maths/test/FermatPrimalityTest.test.js @@ -9,11 +9,11 @@ describe('modularExponentiation', () => { describe('fermatPrimeCheck', () => { it('should give the correct output for prime and composite numbers', () => { - expect(fermatPrimeCheck(2, 50)).toBe(true) - expect(fermatPrimeCheck(10, 50)).toBe(false) - expect(fermatPrimeCheck(94286167, 50)).toBe(true) - expect(fermatPrimeCheck(83165867, 50)).toBe(true) - expect(fermatPrimeCheck(13268774, 50)).toBe(false) - expect(fermatPrimeCheck(13233852, 50)).toBe(false) + expect(fermatPrimeCheck(2, 35)).toBe(true) + expect(fermatPrimeCheck(10, 30)).toBe(false) + expect(fermatPrimeCheck(94286167)).toBe(true) + expect(fermatPrimeCheck(83165867)).toBe(true) + expect(fermatPrimeCheck(13268774)).toBe(false) + expect(fermatPrimeCheck(13233852)).toBe(false) }) }) From fa1abf4e1c6d967abfb86b6c85905949c4a81d26 Mon Sep 17 00:00:00 2001 From: arthurvergacas Date: Sat, 16 Oct 2021 11:37:15 -0300 Subject: [PATCH 5/5] Minor fixes - Add some explanation and links about Carmichael Numbers - Remove explanation about in-built function Math.random() --- Maths/FermatPrimalityTest.js | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Maths/FermatPrimalityTest.js b/Maths/FermatPrimalityTest.js index aa79e4013..1df78e339 100644 --- a/Maths/FermatPrimalityTest.js +++ b/Maths/FermatPrimalityTest.js @@ -1,26 +1,40 @@ /* - * The Fermat primality test is a probabilistic test to determine whether a number is a probable prime. + * The Fermat primality test is a probabilistic test to determine whether a number + * is a probable prime. * - * It relies on Fermat's Little Theorem, which states that if p is prime and a is not divisible by p, then + * It relies on Fermat's Little Theorem, which states that if p is prime and a + * is not divisible by p, then * * a^(p - 1) % p = 1 * * However, there are certain numbers (so called Fermat Liars) that screw things up; * if a is one of these liars the equation will hold even though p is composite. * - * But not everything is lost! It's been proven that at least half of all integers aren't Fermat Liars (these ones called - * Fermat Witnesses). Thus, if we keep testing the primality with random integers, we can achieve higher reliability. + * But not everything is lost! It's been proven that at least half of all integers + * aren't Fermat Liars (these ones called Fermat Witnesses). Thus, if we keep + * testing the primality with random integers, we can achieve higher reliability. * - * The interesting about all of this is that since half of all integers are Fermat Witnesses, the precision gets really - * high really fast! Suppose that we make the test 50 times: the chance of getting only Fermat Liars in all runs is + * The interesting about all of this is that since half of all integers are + * Fermat Witnesses, the precision gets really high really fast! Suppose that we + * make the test 50 times: the chance of getting only Fermat Liars in all runs is * * 1 / 2^50 = 8.8 * 10^-16 (a pretty small number) * - * For comparison, the probability of a cosmic ray causing an error to your infalible program is around 1.4 * 10^-15. An - * order of magnitude below! + * For comparison, the probability of a cosmic ray causing an error to your + * infalible program is around 1.4 * 10^-15. An order of magnitude below! + * + * But because nothing is perfect, there's a major flaw to this algorithm, and + * the cause are the so called Carmichael Numbers. These are composite numbers n + * that hold the equality from Fermat's Little Theorem for every a < n (excluding + * is factors). In other words, if we are trying to determine if a Carmichael Number + * is prime or not, the chances of getting a wrong answer are pretty high! Because + * of that, the Fermat Primality Test is not used is serious applications. :( * * You can find more about the Fermat primality test and its flaws here: * https://en.wikipedia.org/wiki/Fermat_primality_test + * + * And about Carmichael Numbers here: + * https://primes.utm.edu/glossary/xpage/CarmichaelNumber.html */ /** @@ -66,7 +80,7 @@ const fermatPrimeCheck = (n, numberOfIterations = 50) => { if (n <= 3) return true // 2 and 3 are included here for (let i = 0; i < numberOfIterations; i++) { - // pick a random number a, with 2 <= a < n - 2 (remember Math.random() range is [0, 1[ -> 1 exclusive) + // pick a random number a, with 2 <= a < n - 2 const randomNumber = Math.floor(Math.random() * (n - 2) + 2) // if a^(n - 1) % n is different than 1, n is composite