From 8f0925977dbe068cb72c9c6f43448720c5e87085 Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 01:34:41 +0200 Subject: [PATCH 1/7] fix: add more php versions and update test cases --- .../workflows/test-analyze-tag-release.yml | 5 +- composer.json | 6 +- src/HyperLogLog.php | 59 ++++-- tests/HyperLogLogTest.php | 193 +++++++++++++----- 4 files changed, 195 insertions(+), 68 deletions(-) diff --git a/.github/workflows/test-analyze-tag-release.yml b/.github/workflows/test-analyze-tag-release.yml index 01203cd..868631e 100644 --- a/.github/workflows/test-analyze-tag-release.yml +++ b/.github/workflows/test-analyze-tag-release.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] + php-version: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] steps: - name: Checkout code @@ -26,6 +26,9 @@ jobs: coverage: pcov tools: composer:v2 + - name: Remove packages from composer.json + run: composer remove php-cs-fixer/shim phpstan/phpstan --dev + - name: Validate composer.json run: composer validate --strict diff --git a/composer.json b/composer.json index c75506d..e1f8c05 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,12 @@ } ], "require": { - "php": "^8.0" + "php": "^8.0|^7.0" }, "require-dev": { "php-cs-fixer/shim": "^3.95", "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.6|^6.5" }, "autoload": { "psr-4": { @@ -50,4 +50,4 @@ "sort-packages": true, "optimize-autoloader": true } -} \ No newline at end of file +} diff --git a/src/HyperLogLog.php b/src/HyperLogLog.php index 386a34e..f76ee42 100644 --- a/src/HyperLogLog.php +++ b/src/HyperLogLog.php @@ -16,18 +16,22 @@ class HyperLogLog { /** @var int */ - private const TWO_POW_32 = 4_294_967_296; - private int $counterBits; + const TWO_POW_32 = 4294967296; - private string $hashAlgorithm; + /** @var int */ + private $counterBits; + + /** @var string */ + private $hashAlgorithm; - private int $m; + /** @var int */ + private $m; /** @var array Counters storing maximum rho values */ - private array $counters; + private $counters; /** @var bool Indicates if the selected hash algorithm produces less than 64 bits */ - private bool $needsPadding; + private $needsPadding; /** * HyperLogLog constructor. @@ -179,7 +183,9 @@ public function merge(self $other): self } $this->counters = array_map( - static fn (int $counter, int $otherCounter): int => max($counter, $otherCounter), + static function (int $counter, int $otherCounter): int { + return max($counter, $otherCounter); + }, $this->counters, $other->counters ); @@ -221,17 +227,34 @@ public function alpha(int $m): float throw new \InvalidArgumentException('Invalid number of counters, $m must be greater than 0'); } - return match ($m) { - 2 => 0.46852874309841, - 4 => 0.56806457964166, - 8 => 0.63557660535301, - 16 => 0.673, - 32 => 0.697, - 64 => 0.709, - 128 => 0.71527049326382, - 256 => 0.71827259324955, - default => 0.7213 / (1 + 1.079 / $m), - }; + switch ($m) { + case 2: + return 0.46852874309841; + + case 4: + return 0.56806457964166; + + case 8: + return 0.63557660535301; + + case 16: + return 0.673; + + case 32: + return 0.697; + + case 64: + return 0.709; + + case 128: + return 0.71527049326382; + + case 256: + return 0.71827259324955; + + default: + return 0.7213 / (1 + 1.079 / $m); + } } /** diff --git a/tests/HyperLogLogTest.php b/tests/HyperLogLogTest.php index fb04b1a..c95b07c 100644 --- a/tests/HyperLogLogTest.php +++ b/tests/HyperLogLogTest.php @@ -14,15 +14,29 @@ */ class HyperLogLogTest extends TestCase { - public function testConstructorSetsDefaultValues(): void + /** @var string */ + private $defaultHashAlgorithm = 'xxh3'; + + /** @var int */ + private $defaultCounterBits = 5; + + public function __construct(string $name = null, array $data = [], $dataName = '') { - $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; + parent::__construct($name, $data, $dataName); + + $this->defaultHashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; + } - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + /** + * @return void + */ + public function testConstructorSetsDefaultValues() + { + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); // Check if the default counter bits and hash algorithms are set $this->assertSame(5, $hll->getCounterBits()); - $this->assertSame($hashAlgorithm, $hll->getHashAlgorithm()); + $this->assertSame($this->defaultHashAlgorithm, $hll->getHashAlgorithm()); // m should be 2^5 = 32 $this->assertSame(32, $hll->getM()); @@ -31,7 +45,10 @@ public function testConstructorSetsDefaultValues(): void $this->assertCount(32, $hll->getCounters()); } - public function testConstructorSetsCustomValues(): void + /** + * @return void + */ + public function testConstructorSetsCustomValues() { $hll = new HyperLogLog(10, 'sha256'); @@ -43,18 +60,24 @@ public function testConstructorSetsCustomValues(): void $this->assertCount(1024, $hll->getCounters()); } - public function testConstructorFailsWithInvalidHashAlgorithm(): void + /** + * @return void + */ + public function testConstructorFailsWithInvalidHashAlgorithm() { // In PHP 8+, the hash() function throws a ValueError if the provided algorithm does not exist. $this->expectException(\InvalidArgumentException::class); new HyperLogLog(10, 'invalid_algo_123'); } - public function testGettersAndSetters(): void + /** + * @return void + */ + public function testGettersAndSetters() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); // Create a mock state to inject into the instance $mockCounters = array_fill(0, 32, 1); @@ -64,7 +87,10 @@ public function testGettersAndSetters(): void $this->assertSame($mockCounters, $hll->getCounters()); } - public function testAddAndCountUniqueElements(): void + /** + * @return void + */ + public function testAddAndCountUniqueElements() { // Use sha256 to ensure universal test execution across different PHP environments $hll = new HyperLogLog(12, 'sha256'); @@ -81,7 +107,10 @@ public function testAddAndCountUniqueElements(): void $this->assertLessThan(5500, $estimate); } - public function testAddDuplicateElementsMaintainsCardinality(): void + /** + * @return void + */ + public function testAddDuplicateElementsMaintainsCardinality() { $hll = new HyperLogLog(10, 'sha256'); @@ -92,19 +121,25 @@ public function testAddDuplicateElementsMaintainsCardinality(): void $estimate = $hll->count(); // Adding the exact same string 1000 times should result in a total count of ~1 - $this->assertEqualsWithDelta(1.0, $estimate, 1.0); + $this->assertFloatEquals(1.0, $estimate, 1.0); } - public function testAddEmptyString(): void + /** + * @return void + */ + public function testAddEmptyString() { $hll = new HyperLogLog(10, 'sha256'); $hll->add(''); // An empty string is a valid element that should be processed and count as 1. - $this->assertEqualsWithDelta(1.0, $hll->count(), 1.0); + $this->assertFloatEquals(1.0, $hll->count(), 1.0); } - public function testAddWithShortHashAlgorithmTriggersPaddingCorrectly(): void + /** + * @return void + */ + public function testAddWithShortHashAlgorithmTriggersPaddingCorrectly() { // crc32 produces a 32-bit (4 bytes) hash. // This must trigger the internal right-padding with null bytes so unpack('J') doesn't fail. @@ -117,7 +152,10 @@ public function testAddWithShortHashAlgorithmTriggersPaddingCorrectly(): void $this->assertGreaterThan(1.0, $estimate); } - public function testCountTriggersSmallRangeCorrectionInternalBranch(): void + /** + * @return void + */ + public function testCountTriggersSmallRangeCorrectionInternalBranch() { $hll = new HyperLogLog(12, 'sha256'); // m = 4096 @@ -134,7 +172,10 @@ public function testCountTriggersSmallRangeCorrectionInternalBranch(): void $this->assertLessThan(15.0, $estimate); } - public function testCountTriggersLargeRangeCorrectionInternalBranch(): void + /** + * @return void + */ + public function testCountTriggersLargeRangeCorrectionInternalBranch() { $hll = new HyperLogLog(10, 'sha256'); // m = 1024 @@ -146,7 +187,7 @@ public function testCountTriggersLargeRangeCorrectionInternalBranch(): void $hll->setCounters($mockCounters); $estimate = $hll->count(); - $twoPow32 = 4_294_967_296; + $twoPow32 = 4294967296; // Verify that the value is a valid float and not a mathematically undefined result (NAN). $this->assertFalse(is_nan($estimate), 'The estimate must not be NAN.'); @@ -155,22 +196,28 @@ public function testCountTriggersLargeRangeCorrectionInternalBranch(): void $this->assertGreaterThan($twoPow32 / 30, $estimate); } - public function testTheoreticalErrorRateCalculation(): void + /** + * @return void + */ + public function testTheoreticalErrorRateCalculation() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); // Theoretical error rate formula: 1.04 / sqrt(m) // 1.04 / sqrt(16) = 1.04 / 4 = 0.26 - $this->assertEqualsWithDelta(0.26, $hll->theoreticalErrorRate(16), 0.001); + $this->assertFloatEquals(0.26, $hll->theoreticalErrorRate(16), 0.001); } - public function testTheoreticalErrorRateThrowsExceptionForInvalidM(): void + /** + * @return void + */ + public function testTheoreticalErrorRateThrowsExceptionForInvalidM() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid number of counters, $m must be greater than 0'); @@ -178,47 +225,59 @@ public function testTheoreticalErrorRateThrowsExceptionForInvalidM(): void $hll->theoreticalErrorRate(0); } - public function testMeasureError(): void + /** + * @return void + */ + public function testMeasureError() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); // measureError simply returns the difference: estimate - real $this->assertSame(5, $hll->measureError(15, 10)); $this->assertSame(-2, $hll->measureError(8, 10)); } - public function testAlphaCalculationValues(): void + /** + * @return void + */ + public function testAlphaCalculationValues() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); // Test exact predefined constants for m=2 and m=16 - $this->assertEqualsWithDelta(0.46852874309841, $hll->alpha(2), 0.0000001); - $this->assertEqualsWithDelta(0.673, $hll->alpha(16), 0.001); + $this->assertFloatEquals(0.46852874309841, $hll->alpha(2), 0.0000001); + $this->assertFloatEquals(0.673, $hll->alpha(16), 0.001); // Test the default fallback calculation for m >= 128 (e.g., 512) $expectedLargeAlpha = 0.7213 / (1 + 1.079 / 512); - $this->assertEqualsWithDelta($expectedLargeAlpha, $hll->alpha(512), 0.001); + $this->assertFloatEquals($expectedLargeAlpha, $hll->alpha(512), 0.001); } - public function testAlphaThrowsExceptionForInvalidM(): void + /** + * @return void + */ + public function testAlphaThrowsExceptionForInvalidM() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); $this->expectException(\InvalidArgumentException::class); $hll->alpha(0); } - public function testEstimateThrowsExceptionForInvalidZ(): void + /** + * @return void + */ + public function testEstimateThrowsExceptionForInvalidZ() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid harmonic mean, $Z must be positive'); @@ -227,22 +286,28 @@ public function testEstimateThrowsExceptionForInvalidZ(): void $hll->estimate(16, 0.0); } - public function testEstimateUsingLinearCounting(): void + /** + * @return void + */ + public function testEstimateUsingLinearCounting() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); $m = 64; $v = 16; // 16 empty counters out of 64 // Linear Counting formula for small cardinalities: m * log(m / V) $expected = 64 * log(64 / 16); // 64 * log(4) ≈ 88.72 - $this->assertEqualsWithDelta($expected, $hll->estimateUsingLinearCounting($v, $m), 0.001); - $this->assertEqualsWithDelta($expected, $hll->estimateUsingSmallCardinalitiesApproach($v, $m), 0.001); + $this->assertFloatEquals($expected, $hll->estimateUsingLinearCounting($v, $m), 0.001); + $this->assertFloatEquals($expected, $hll->estimateUsingSmallCardinalitiesApproach($v, $m), 0.001); } - public function testMergeThrowsExceptionForMismatchedCounterBits(): void + /** + * @return void + */ + public function testMergeThrowsExceptionForMismatchedCounterBits() { $hll1 = new HyperLogLog(10, 'sha256'); $hll2 = new HyperLogLog(12, 'sha256'); @@ -253,7 +318,10 @@ public function testMergeThrowsExceptionForMismatchedCounterBits(): void $hll1->merge($hll2); } - public function testMergeThrowsExceptionForMismatchedHashAlgorithms(): void + /** + * @return void + */ + public function testMergeThrowsExceptionForMismatchedHashAlgorithms() { $hll1 = new HyperLogLog(10, 'sha256'); $hll2 = new HyperLogLog(10, 'sha1'); @@ -264,7 +332,10 @@ public function testMergeThrowsExceptionForMismatchedHashAlgorithms(): void $hll1->merge($hll2); } - public function testMergeUpdatesCountersWithMaxValues(): void + /** + * @return void + */ + public function testMergeUpdatesCountersWithMaxValues() { $hll1 = new HyperLogLog(5, 'sha256'); // m = 32 $hll2 = new HyperLogLog(5, 'sha256'); // m = 32 @@ -298,7 +369,10 @@ public function testMergeUpdatesCountersWithMaxValues(): void $this->assertSame($hll1, $result); } - public function testMergeEstimatesUnionOfTwoSetsCorrectly(): void + /** + * @return void + */ + public function testMergeEstimatesUnionOfTwoSetsCorrectly() { $hll1 = new HyperLogLog(12, 'sha256'); $hll2 = new HyperLogLog(12, 'sha256'); @@ -323,23 +397,50 @@ public function testMergeEstimatesUnionOfTwoSetsCorrectly(): void $this->assertLessThan(5500, $estimate); } - public function testEstimateUsingLargeCardinalitiesApproach(): void + /** + * @return void + */ + public function testEstimateUsingLargeCardinalitiesApproach() { $hashAlgorithm = PHP_VERSION >= 8100 ? 'xxh3' : 'sha256'; - $hll = new HyperLogLog(hashAlgorithm: $hashAlgorithm); + $hll = new HyperLogLog($this->defaultCounterBits, $this->defaultHashAlgorithm); - $rawEstimate = 3_000_000_000; - $twoPow32 = 4_294_967_296; + $rawEstimate = 3000000000; + $twoPow32 = 4294967296; // Large Cardinality formula to correct hash collision bias near 2^32: // -2^32 * log(1 - E / 2^32) $expected = -$twoPow32 * log(1 - $rawEstimate / $twoPow32); - $this->assertEqualsWithDelta( + $this->assertFloatEquals( $expected, $hll->estimateUsingLargeCardinalitiesApproach($rawEstimate), 0.001 ); } + + /** + * Custom assertion helper to support delta comparisons across all PHPUnit versions + * without conflicting with native static methods. + * + * @param float $expected The expected value + * @param float $actual The actual value to test + * @param float $delta The allowed margin of error + */ + private function assertFloatEquals(float $expected, float $actual, float $delta): void + { + $difference = abs($expected - $actual); + + $this->assertLessThanOrEqual( + $delta, + $difference, + sprintf( + 'Failed asserting that actual %s matches expected %s within a delta of %s.', + $actual, + $expected, + $delta + ) + ); + } } From 3a2e1237fa3d203a12dc16d613df9229f3812866 Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 01:36:33 +0200 Subject: [PATCH 2/7] add new phpunit version for php 7.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e1f8c05..905b97b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "require-dev": { "php-cs-fixer/shim": "^3.95", "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^9.6|^6.5" + "phpunit/phpunit": "^9.6|^8.5|^6.5" }, "autoload": { "psr-4": { From 1d1c4cce317580968c36e7f245d76aea66b9b016 Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 01:37:27 +0200 Subject: [PATCH 3/7] forget void --- src/HyperLogLog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HyperLogLog.php b/src/HyperLogLog.php index f76ee42..bf355fb 100644 --- a/src/HyperLogLog.php +++ b/src/HyperLogLog.php @@ -100,7 +100,7 @@ public function setCounters(array $counters): self * * @param string $value element to insert */ - public function add(string $value): void + public function add(string $value) { $hashString = $this->hash($value, $this->hashAlgorithm); From e44d33230581cd9b3e607616a3a9309031096e84 Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 01:54:16 +0200 Subject: [PATCH 4/7] php compatiblity --- .php-cs-fixer.dist.php | 3 +++ src/HyperLogLog.php | 4 +++- tests/HyperLogLogTest.php | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 157e42b..25ec078 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -8,8 +8,11 @@ return (new Config()) ->setRiskyAllowed(false) ->setRules([ + '@PHP7x0Migration' => true, '@Symfony' => true, '@PhpCsFixer' => true, + 'nullable_type_declaration_for_default_null_value' => false, + 'phpdoc_no_empty_return' => false, ]) // 💡 by default, Fixer looks for `*.php` files excluding `./vendor/` - here, you can groom this config ->setFinder( diff --git a/src/HyperLogLog.php b/src/HyperLogLog.php index bf355fb..e7cdfb7 100644 --- a/src/HyperLogLog.php +++ b/src/HyperLogLog.php @@ -16,7 +16,7 @@ class HyperLogLog { /** @var int */ - const TWO_POW_32 = 4294967296; + public const TWO_POW_32 = 4294967296; /** @var int */ private $counterBits; @@ -99,6 +99,8 @@ public function setCounters(array $counters): self * and leading zero count, and the register is updated with the maximum observed rho value. * * @param string $value element to insert + * + * @return void */ public function add(string $value) { diff --git a/tests/HyperLogLogTest.php b/tests/HyperLogLogTest.php index c95b07c..d5e5ff8 100644 --- a/tests/HyperLogLogTest.php +++ b/tests/HyperLogLogTest.php @@ -20,6 +20,10 @@ class HyperLogLogTest extends TestCase /** @var int */ private $defaultCounterBits = 5; + /** + * @param array $data + * @param int|string $dataName + */ public function __construct(string $name = null, array $data = [], $dataName = '') { parent::__construct($name, $data, $dataName); From 2c450a6f03d56e58a3da17e758425b750a196b9c Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 01:57:44 +0200 Subject: [PATCH 5/7] fix: php cs fixer miss config --- .php-cs-fixer.dist.php | 3 +++ src/HyperLogLog.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 25ec078..38c3ae9 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -13,6 +13,9 @@ '@PhpCsFixer' => true, 'nullable_type_declaration_for_default_null_value' => false, 'phpdoc_no_empty_return' => false, + 'modifier_keywords' => [ + 'elements' => ['property', 'method'], + ], ]) // 💡 by default, Fixer looks for `*.php` files excluding `./vendor/` - here, you can groom this config ->setFinder( diff --git a/src/HyperLogLog.php b/src/HyperLogLog.php index e7cdfb7..4f9fffc 100644 --- a/src/HyperLogLog.php +++ b/src/HyperLogLog.php @@ -16,7 +16,7 @@ class HyperLogLog { /** @var int */ - public const TWO_POW_32 = 4294967296; + const TWO_POW_32 = 4294967296; /** @var int */ private $counterBits; From 86f8118d3e29d71d0ce7c60c3f0bd3f617350133 Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 01:59:25 +0200 Subject: [PATCH 6/7] fix --- tests/HyperLogLogTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/HyperLogLogTest.php b/tests/HyperLogLogTest.php index d5e5ff8..a51577a 100644 --- a/tests/HyperLogLogTest.php +++ b/tests/HyperLogLogTest.php @@ -431,8 +431,10 @@ public function testEstimateUsingLargeCardinalitiesApproach() * @param float $expected The expected value * @param float $actual The actual value to test * @param float $delta The allowed margin of error + * + * @return void */ - private function assertFloatEquals(float $expected, float $actual, float $delta): void + private function assertFloatEquals(float $expected, float $actual, float $delta) { $difference = abs($expected - $actual); From 64e6eaa4dc4f8b9483961d808a2c5f38bb7a52c8 Mon Sep 17 00:00:00 2001 From: Hichxm Date: Sat, 4 Jul 2026 02:01:37 +0200 Subject: [PATCH 7/7] update readme to reflect PHP --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 799c583..5e91933 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ It is **not** appropriate when an exact distinct count is required. ## Requirements -- **PHP 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5** +- **PHP 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5** - No external dependencies > **Note:** The `xxh3` and `xxh128` hash algorithms are available only when supported by your PHP version and build. If