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
40 changes: 27 additions & 13 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
name: PHP Composer
name: PHP Tests

on:
push:
branches: [ "master" ]
branches: [ "master", "main" ]
pull_request:
branches: [ "master" ]
branches: [ "master", "main" ]

permissions:
contents: read

jobs:
build:

test:
name: PHP 8.4 Tests

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4

- name: Setup PHP 8.4
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, xml, ctype, json, tokenizer
coverage: xdebug

- name: Validate composer.json and composer.lock
run: composer validate --strict
Expand All @@ -25,15 +34,20 @@ jobs:
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
key: ${{ runner.os }}-php-8.4-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php-
${{ runner.os }}-php-8.4-

- name: Install dependencies
run: composer install --prefer-dist --no-progress

# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
- name: Run test suite
run: vendor/bin/phpunit --coverage-clover=coverage.xml

# - name: Run test suite
# run: composer run-script test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
2 changes: 1 addition & 1 deletion .phpunit.result.cache
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":2,"defects":[],"times":{"IntegerTest::testIntegerValidations":0.002,"IntegerTest::testInvalidNumberException":0,"IntegerTest::testCreateDefault":0,"IntegerTest::testCreateByInt":0,"IntegerTest::testCreateByString":0,"IntegerTest::testPrint":0,"IntegerTest::testAdd":0.085,"IntegerTest::testLargeAddition":0,"IntegerTest::testSubtract":0.083,"IntegerTest::testLargeSubtraction":0,"IntegerTest::testNegativeSubtraction":0,"IntegerTest::testSubtractWithNegativeResults":0.172,"IntegerTest::testLargeNegativeSubtraction":0,"IntegerTest::testMultiplyByInt":0,"IntegerTest::testMultiplyByIntException":0,"IntegerTest::testMultiply":0.398,"IntegerTest::testGreaterThan":0.047,"IntegerTest::testGreaterOrEqualTo":0,"IntegerTest::testStringLength":14.228,"IntegerTest::testMaximumMultiplier":0.026,"IntegerTest::testMaxMultiplierException":0,"IntegerTest::testNumberLength":0,"IntegerTest::testDivision":0.004,"IntegerTest::testDivisionByZeroException":0,"IntegerTest::testDivisionByBiggerDivisorException":0,"IntegerTest::testSeveralDivisions":0.188,"IntegerTest::testModule":0.001,"IntegerTest::testCreatingLargeNumber":0}}
{"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}}
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}
],
"require": {
"php": "^8.0"
"php": "^8.4"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
Expand All @@ -20,6 +20,10 @@
"psr-4": {
"TheHappyCat\\NumericTools\\": "src/thehappycat/numerictools/"
}
},
"scripts": {
"test": "php vendor/bin/phpunit",
"test:coverage": "php vendor/bin/phpunit --coverage-html coverage/"
}

}
4 changes: 2 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

201 changes: 201 additions & 0 deletions console/prime_generator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#!/usr/bin/env php
<?php

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

use TheHappyCat\NumericTools\Integer;
use TheHappyCat\NumericTools\PrimeGenerator;

/**
* Prime Number Generator CLI
*
* Usage examples:
* php prime_generator.php generate 256
* php prime_generator.php test 1000000007
* php prime_generator.php twin 128
* php prime_generator.php range 100 200
* php prime_generator.php sophie 64
*/

if ($argc < 2) {
showUsage();
exit(1);
}

$generator = new PrimeGenerator();
$command = $argv[1];

try {
switch ($command) {
case 'generate':
if ($argc < 3) {
echo "Error: Please specify bit length\n";
showUsage();
exit(1);
}
$bits = (int)$argv[2];
$start = microtime(true);
$prime = $generator->generatePrime($bits);
$end = microtime(true);

echo "Generated prime number:\n";
echo "Value: " . $prime->getStringValue() . "\n";
echo "Bits: ~" . $prime->getLength() * 3.32 . "\n";
echo "Time: " . round(($end - $start) * 1000, 2) . "ms\n";
break;

case 'test':
if ($argc < 3) {
echo "Error: Please specify number to test\n";
showUsage();
exit(1);
}
$number = Integer::createByString($argv[2]);
$start = microtime(true);
$isPrime = $number->isPrime();
$end = microtime(true);

echo "Primality test result:\n";
echo "Number: " . $number->getStringValue() . "\n";
echo "Is Prime: " . ($isPrime ? "Yes" : "No") . "\n";
echo "Time: " . round(($end - $start) * 1000, 2) . "ms\n";
break;

case 'twin':
if ($argc < 3) {
echo "Error: Please specify bit length\n";
showUsage();
exit(1);
}
$bits = (int)$argv[2];
$start = microtime(true);
list($p1, $p2) = $generator->generateTwinPrimes($bits);
$end = microtime(true);

echo "Generated twin primes:\n";
echo "p: " . $p1->getStringValue() . "\n";
echo "p+2: " . $p2->getStringValue() . "\n";
echo "Time: " . round(($end - $start) * 1000, 2) . "ms\n";
break;

case 'range':
if ($argc < 4) {
echo "Error: Please specify start and end values\n";
showUsage();
exit(1);
}
$start = Integer::createByString($argv[2]);
$end = Integer::createByString($argv[3]);
$startTime = microtime(true);
$primes = $generator->generatePrimesInRange($start, $end);
$endTime = microtime(true);

echo "Primes in range [" . $start->getStringValue() . ", " . $end->getStringValue() . "]:\n";
echo "Count: " . count($primes) . "\n";
echo "Primes: " . implode(', ', array_map('strval', $primes)) . "\n";
echo "Time: " . round(($endTime - $startTime) * 1000, 2) . "ms\n";
break;

case 'sophie':
if ($argc < 3) {
echo "Error: Please specify bit length\n";
showUsage();
exit(1);
}
$bits = (int)$argv[2];
$start = microtime(true);
$prime = $generator->generateSophieGermainPrime($bits);
$end = microtime(true);

$q = $prime->multiplyBy(Integer::createByInt(2))->add(Integer::createByInt(1));

echo "Generated Sophie Germain prime:\n";
echo "p: " . $prime->getStringValue() . "\n";
echo "2p+1: " . $q->getStringValue() . "\n";
echo "Time: " . round(($end - $start) * 1000, 2) . "ms\n";
break;

case 'benchmark':
if ($argc < 3) {
echo "Error: Please specify bit length for benchmark\n";
showUsage();
exit(1);
}
$bits = (int)$argv[2];
runBenchmark($generator, $bits);
break;

default:
echo "Error: Unknown command '$command'\n";
showUsage();
exit(1);
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}

function showUsage(): void
{
echo "Prime Number Generator CLI\n";
echo "==========================\n\n";
echo "Commands:\n";
echo " generate <bits> - Generate prime with specified bit length\n";
echo " test <number> - Test if number is prime\n";
echo " twin <bits> - Generate twin primes\n";
echo " range <start> <end> - Find primes in range\n";
echo " sophie <bits> - Generate Sophie Germain prime\n";
echo " benchmark <bits> - Run performance benchmark\n\n";
echo "Examples:\n";
echo " php prime_generator.php generate 256\n";
echo " php prime_generator.php test 1000000007\n";
echo " php prime_generator.php twin 128\n";
echo " php prime_generator.php range 100 200\n";
echo " php prime_generator.php sophie 64\n";
echo " php prime_generator.php benchmark 128\n";
}

function runBenchmark(PrimeGenerator $generator, int $bits): void
{
echo "Running benchmark for $bits-bit prime generation...\n";
echo "================================================\n\n";

$times = [];
$iterations = 5;

for ($i = 1; $i <= $iterations; $i++) {
echo "Iteration $i/$iterations... ";
$start = microtime(true);
$prime = $generator->generatePrime($bits);
$end = microtime(true);
$time = ($end - $start) * 1000;
$times[] = $time;
echo "Done in " . round($time, 2) . "ms\n";
}

$avgTime = array_sum($times) / count($times);
$minTime = min($times);
$maxTime = max($times);

echo "\nBenchmark Results:\n";
echo "==================\n";
echo "Bit length: $bits\n";
echo "Iterations: $iterations\n";
echo "Average time: " . round($avgTime, 2) . "ms\n";
echo "Min time: " . round($minTime, 2) . "ms\n";
echo "Max time: " . round($maxTime, 2) . "ms\n";
echo "Standard deviation: " . round(calculateStdDev($times), 2) . "ms\n";
}

function calculateStdDev(array $values): float
{
$mean = array_sum($values) / count($values);
$variance = 0;

foreach ($values as $value) {
$variance += pow($value - $mean, 2);
}

$variance /= count($values);
return sqrt($variance);
}
Loading