Skip to content
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
2 changes: 1 addition & 1 deletion .phpunit.result.cache
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":2,"defects":{"PrimeNumberTest::testIsPrime":8,"PrimeNumberTest::testModPow":8,"PrimeNumberTest::testLargePrimeNumbers":8,"PrimeNumberTest::testEdgeCases":8,"IntegerTest::testIntegerValidations":7,"IntegerTest::testDivisionByBiggerDivisorException":7,"PrimeGeneratorTest::testGeneratePrime":7,"PrimeGeneratorTest::testPrimeGenerationPerformance":8,"PrimeGeneratorTest::testLargePrimeGeneration":8},"times":{"IntegerTest::testIntegerValidations":0.002,"IntegerTest::testInvalidNumberException":0,"IntegerTest::testCreateDefault":0,"IntegerTest::testCreateByInt":0,"IntegerTest::testCreateByString":0,"IntegerTest::testPrint":0,"IntegerTest::testAdd":0.083,"IntegerTest::testLargeAddition":0,"IntegerTest::testSubtract":0.082,"IntegerTest::testLargeSubtraction":0,"IntegerTest::testNegativeSubtraction":0,"IntegerTest::testSubtractWithNegativeResults":0.172,"IntegerTest::testLargeNegativeSubtraction":0,"IntegerTest::testMultiplyByInt":0,"IntegerTest::testMultiplyByIntException":0,"IntegerTest::testMultiply":0.428,"IntegerTest::testGreaterThan":0.049,"IntegerTest::testGreaterOrEqualTo":0,"IntegerTest::testStringLength":14.224,"IntegerTest::testMaximumMultiplier":0.026,"IntegerTest::testMaxMultiplierException":0,"IntegerTest::testNumberLength":0,"IntegerTest::testDivision":0.003,"IntegerTest::testDivisionByZeroException":0,"IntegerTest::testDivisionByBiggerDivisorException":0,"IntegerTest::testSeveralDivisions":0.064,"IntegerTest::testModule":0.001,"IntegerTest::testCreatingLargeNumber":0.001,"PrimeNumberTest::testIsPrime":0.012,"PrimeNumberTest::testIsProbablePrime":0,"PrimeNumberTest::testModPow":0,"PrimeNumberTest::testGcd":0,"PrimeNumberTest::testLcm":0,"PrimeNumberTest::testLargePrimeNumbers":0.17,"PrimeNumberTest::testEdgeCases":0,"PrimeGeneratorTest::testGenerateRandomOdd":0,"PrimeGeneratorTest::testGeneratePrime":0.415,"PrimeGeneratorTest::testGeneratePrimeSmallBits":0.013,"PrimeGeneratorTest::testGenerateTwinPrimes":0.355,"PrimeGeneratorTest::testGenerateNextPrime":0.007,"PrimeGeneratorTest::testGeneratePrimesInRange":0.029,"PrimeGeneratorTest::testGenerateRandomPrimeInRange":0.01,"PrimeGeneratorTest::testGenerateSophieGermainPrime":0.745,"PrimeGeneratorTest::testInvalidBitLength":0,"PrimeGeneratorTest::testInvalidBitLengthRandomOdd":0,"PrimeGeneratorTest::testPrimeGenerationPerformance":0.525,"PrimeGeneratorTest::testLargePrimeGeneration":1.584,"PrimeGeneratorTest::testPrimeProperties":0.499}}
{"version":2,"defects":{"PrimeNumberTest::testIsPrime":8,"PrimeNumberTest::testModPow":8,"PrimeNumberTest::testLargePrimeNumbers":8,"PrimeNumberTest::testEdgeCases":8,"IntegerTest::testIntegerValidations":7,"IntegerTest::testDivisionByBiggerDivisorException":7,"PrimeGeneratorTest::testGeneratePrime":7,"PrimeGeneratorTest::testPrimeGenerationPerformance":8,"PrimeGeneratorTest::testLargePrimeGeneration":8,"PrimalityComparisonTest::testSmallNumberComparison":7},"times":{"IntegerTest::testIntegerValidations":0.002,"IntegerTest::testInvalidNumberException":0,"IntegerTest::testCreateDefault":0,"IntegerTest::testCreateByInt":0,"IntegerTest::testCreateByString":0,"IntegerTest::testPrint":0,"IntegerTest::testAdd":0.082,"IntegerTest::testLargeAddition":0,"IntegerTest::testSubtract":0.082,"IntegerTest::testLargeSubtraction":0,"IntegerTest::testNegativeSubtraction":0,"IntegerTest::testSubtractWithNegativeResults":0.176,"IntegerTest::testLargeNegativeSubtraction":0,"IntegerTest::testMultiplyByInt":0,"IntegerTest::testMultiplyByIntException":0,"IntegerTest::testMultiply":0.397,"IntegerTest::testGreaterThan":0.048,"IntegerTest::testGreaterOrEqualTo":0,"IntegerTest::testStringLength":14.207,"IntegerTest::testMaximumMultiplier":0.026,"IntegerTest::testMaxMultiplierException":0,"IntegerTest::testNumberLength":0,"IntegerTest::testDivision":0.002,"IntegerTest::testDivisionByZeroException":0,"IntegerTest::testDivisionByBiggerDivisorException":0,"IntegerTest::testSeveralDivisions":0.064,"IntegerTest::testModule":0,"IntegerTest::testCreatingLargeNumber":0.001,"PrimeNumberTest::testIsPrime":0.015,"PrimeNumberTest::testIsProbablePrime":0,"PrimeNumberTest::testModPow":0,"PrimeNumberTest::testGcd":0,"PrimeNumberTest::testLcm":0.001,"PrimeNumberTest::testLargePrimeNumbers":0.184,"PrimeNumberTest::testEdgeCases":0,"PrimeGeneratorTest::testGenerateRandomOdd":0,"PrimeGeneratorTest::testGeneratePrime":0.476,"PrimeGeneratorTest::testGeneratePrimeSmallBits":0.012,"PrimeGeneratorTest::testGenerateTwinPrimes":0.273,"PrimeGeneratorTest::testGenerateNextPrime":0.007,"PrimeGeneratorTest::testGeneratePrimesInRange":0.033,"PrimeGeneratorTest::testGenerateRandomPrimeInRange":0.012,"PrimeGeneratorTest::testGenerateSophieGermainPrime":0.197,"PrimeGeneratorTest::testInvalidBitLength":0,"PrimeGeneratorTest::testInvalidBitLengthRandomOdd":0,"PrimeGeneratorTest::testPrimeGenerationPerformance":0.424,"PrimeGeneratorTest::testLargePrimeGeneration":1.592,"PrimeGeneratorTest::testPrimeProperties":0.388,"PrimalityComparisonTest::testSmallNumberComparison":0.004,"PrimalityComparisonTest::testMediumNumberComparison":1.813,"PrimalityComparisonTest::testLargeNumberProbabilisticOnly":1.038,"PrimalityComparisonTest::testAccuracyComparison":0.008}}
35 changes: 35 additions & 0 deletions src/thehappycat/numerictools/Integer.php
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,41 @@ public function isPrime(): bool
return $this->isProbablePrime(20);
}

/**
* Brute force primality test - tests every possible divisor
* WARNING: Extremely slow for large numbers!
*
* @return bool
*/
public function isPrimeBruteForce(): bool
{
if ($this->isNegative() || $this->isZero() || $this->getStringValue() === '1') {
return false;
}

if ($this->getStringValue() === '2' || $this->getStringValue() === '3') {
return true;
}

// Check if even
if ($this->mod(Integer::createByInt(2))->isZero()) {
return false;
}

$sqrt = $this->sqrt();
$divisor = Integer::createByInt(3);

// Test odd divisors up to √n
while ($divisor->lessThanOrEqualTo($sqrt)) {
if ($this->mod($divisor)->isZero()) {
return false; // Found a divisor
}
$divisor = $divisor->add(Integer::createByInt(2));
}

return true; // No divisors found
}

/**
* Check if this number is less than or equal to another number
*
Expand Down
163 changes: 163 additions & 0 deletions tests/PrimalityComparisonTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

require_once __DIR__ . '/../vendor/autoload.php';

use PHPUnit\Framework\TestCase;
use TheHappyCat\NumericTools\Integer;

/**
* Class PrimalityComparisonTest
* Compares performance of probabilistic vs brute force primality testing
*/
class PrimalityComparisonTest extends TestCase
{
public function testSmallNumberComparison()
{
$number = Integer::createByString('97'); // Known prime

echo "\n=== Testing Small Number (97) ===\n";

// Test probabilistic method
$start = microtime(true);
$probResult = $number->isProbablePrime(5);
$probTime = (microtime(true) - $start) * 1000;

// Test brute force method
$start = microtime(true);
$bruteResult = $number->isPrimeBruteForce();
$bruteTime = (microtime(true) - $start) * 1000;

echo "Probabilistic: " . ($probResult ? "Prime" : "Composite") . " in " . round($probTime, 2) . "ms\n";
echo "Brute Force: " . ($bruteResult ? "Prime" : "Composite") . " in " . round($bruteTime, 2) . "ms\n";
echo "Speedup: " . round($bruteTime / $probTime, 1) . "x faster\n";

$this->assertTrue($probResult);
$this->assertTrue($bruteResult);

// For small numbers, both should be fast (under 100ms in CI environment)
$this->assertTrue($probTime < 100, "Probabilistic test took too long: {$probTime}ms");
$this->assertTrue($bruteTime < 100, "Brute force test took too long: {$bruteTime}ms");

// Both methods should give correct results
$this->assertTrue($probResult === $bruteResult, "Results should match");
}

public function testMediumNumberComparison()
{
$number = Integer::createByString('1000000007'); // Known prime

echo "\n=== Testing Medium Number (1000000007) ===\n";

// Test probabilistic method
$start = microtime(true);
$probResult = $number->isProbablePrime(5);
$probTime = (microtime(true) - $start) * 1000;

// Test brute force method (this will take a while!)
echo "Running brute force test (this may take several minutes)...\n";
$start = microtime(true);
$bruteResult = $number->isPrimeBruteForce();
$bruteTime = (microtime(true) - $start) * 1000;

echo "Probabilistic: " . ($probResult ? "Prime" : "Composite") . " in " . round($probTime, 2) . "ms\n";
echo "Brute Force: " . ($bruteResult ? "Prime" : "Composite") . " in " . round($bruteTime, 2) . "ms\n";
echo "Speedup: " . round($bruteTime / $probTime, 1) . "x faster\n";

$this->assertTrue($probResult);
$this->assertTrue($bruteResult);

// For medium numbers, probabilistic should be significantly faster
// Allow for some variance in CI environments
$this->assertTrue($probTime < $bruteTime * 0.8, "Probabilistic should be faster for medium numbers");

// Both methods should give correct results
$this->assertTrue($probResult === $bruteResult, "Results should match");
}

public function testLargeNumberProbabilisticOnly()
{
$number = Integer::createByString('123456789012345678901234567890123456789');

echo "\n=== Testing Large Number (Probabilistic Only) ===\n";

// Only test probabilistic method for large numbers
$start = microtime(true);
$probResult = $number->isProbablePrime(10);
$probTime = (microtime(true) - $start) * 1000;

echo "Number: " . $number->getStringValue() . "\n";
echo "Digits: " . $number->getLength() . "\n";
echo "Probabilistic: " . ($probResult ? "Prime" : "Composite") . " in " . round($probTime, 2) . "ms\n";
echo "Brute Force: Would take approximately " . calculateBruteForceTime($number) . "\n";

// Don't test brute force - it would take too long!
$this->assertIsBool($probResult);
}

public function testAccuracyComparison()
{
echo "\n=== Testing Accuracy ===\n";

// Test known primes
$knownPrimes = ['2', '3', '5', '7', '11', '13', '17', '19', '23', '29', '31', '37'];
$knownComposites = ['4', '6', '8', '9', '10', '12', '14', '15', '16', '18', '20', '21'];

$probCorrect = 0;
$bruteCorrect = 0;
$total = count($knownPrimes) + count($knownComposites);

// Test primes
foreach ($knownPrimes as $primeStr) {
$number = Integer::createByString($primeStr);
$probResult = $number->isProbablePrime(5);
$bruteResult = $number->isPrimeBruteForce();

if ($probResult) $probCorrect++;
if ($bruteResult) $bruteCorrect++;
}

// Test composites
foreach ($knownComposites as $compositeStr) {
$number = Integer::createByString($compositeStr);
$probResult = $number->isProbablePrime(5);
$bruteResult = $number->isPrimeBruteForce();

if (!$probResult) $probCorrect++;
if (!$bruteResult) $bruteCorrect++;
}

$probAccuracy = ($probCorrect / $total) * 100;
$bruteAccuracy = ($bruteCorrect / $total) * 100;

echo "Probabilistic Accuracy: " . round($probAccuracy, 1) . "%\n";
echo "Brute Force Accuracy: " . round($bruteAccuracy, 1) . "%\n";

$this->assertEquals(100, $bruteAccuracy); // Brute force should be 100% accurate
$this->assertGreaterThan(95, $probAccuracy); // Probabilistic should be >95% accurate
}
}

/**
* Calculate estimated time for brute force primality test
* This is a rough approximation based on number size
*/
function calculateBruteForceTime(Integer $number): string
{
$digits = $number->getLength();

// Rough approximation: O(√n) operations
// For n digits, √n ≈ 10^(n/2)
// Assuming each operation takes ~1 microsecond

if ($digits <= 10) {
return "seconds";
} elseif ($digits <= 20) {
return "minutes";
} elseif ($digits <= 30) {
return "hours";
} elseif ($digits <= 40) {
return "days";
} else {
return "years or more";
}
}