nanasess / bcmath-polyfill
PHP 8.4 bcmath functions polyfill with fallback compatibility for environments without bcmath extension. Based on phpseclib/bcmath_compat with additional support for new PHP 8.4 bcmath functions.
Requires
- php: >=8.1
- phpseclib/phpseclib: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.86
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5
- rector/rector: ^2.1
Suggests
- ext-gmp: Will enable faster math operations
Provides
- ext-bcmath: 8.1.0
README
PHP 8.4 bcmath functions polyfill with fallback compatibility for environments without bcmath extension.
๐ Features
- โ Complete bcmath extension polyfill for PHP 8.1+
- โ
Supports all PHP 8.4+ bcmath functions including
bcfloor(),bcceil(),bcround(), andbcdivmod() - โ PHP 8.5 compatibility tested and verified
- โ Zero dependencies in production (uses phpseclib for arbitrary precision math)
- โ Seamless fallback when bcmath extension is not available
- โ 100% compatible with native bcmath functions
๐ฆ Installation
Install via Composer:
composer require nanasess/bcmath-polyfill
๐ง Usage
Simply include the polyfill in your project and use bcmath functions as you normally would:
// The polyfill will automatically load when bcmath extension is not available require_once 'vendor/autoload.php'; // All bcmath functions work identically to the native extension echo bcadd('1.234', '5.678', 2); // 6.91 echo bcmul('2.5', '3.5', 1); // 8.7 echo bcpow('2', '8'); // 256 // PHP 8.4 functions are also supported echo bcfloor('4.7'); // 4 echo bcceil('4.3'); // 5 echo bcround('3.14159', 2); // 3.14 // bcround() supports RoundingMode enum (PHP 8.1+ with polyfill, native in PHP 8.4+) echo bcround('2.5', 0, \RoundingMode::HalfAwayFromZero); // 3 echo bcround('2.5', 0, \RoundingMode::HalfTowardsZero); // 2 echo bcround('2.5', 0, \RoundingMode::HalfEven); // 2 echo bcround('2.5', 0, \RoundingMode::HalfOdd); // 3 echo bcround('2.5', 0, \RoundingMode::TowardsZero); // 2 echo bcround('2.5', 0, \RoundingMode::AwayFromZero); // 3 echo bcround('2.5', 0, \RoundingMode::PositiveInfinity); // 3 echo bcround('2.5', 0, \RoundingMode::NegativeInfinity); // 2 // bcdivmod() returns [quotient, remainder] [$quot, $rem] = bcdivmod('17', '5'); // ['3', '2']
๐ Supported Functions
Classic bcmath Functions
bcadd()- Add two arbitrary precision numbersbcsub()- Subtract one arbitrary precision number from anotherbcmul()- Multiply two arbitrary precision numbersbcdiv()- Divide two arbitrary precision numbersbcmod()- Get modulus of an arbitrary precision numberbcpow()- Raise an arbitrary precision number to anotherbcsqrt()- Get the square root of an arbitrary precision numberbcscale()- Set/get default scale parameterbccomp()- Compare two arbitrary precision numbersbcpowmod()- Raise an arbitrary precision number to another, reduced by a specified modulus
PHP 8.4 Functions (Added in PR #6)
bcfloor()- Round down to the nearest integerbcceil()- Round up to the nearest integerbcround()- Round to a specified precision with configurable rounding modesbcdivmod()- Get the quotient and remainder of a division in a single call
RoundingMode Enum Support
The bcround() function supports PHP 8.4's RoundingMode enum through a polyfill for PHP 8.1-8.3.
All eight native modes are implemented with exact, arbitrary-precision string arithmetic (no float
fallback), so results match the native bcround() even for very large or high-precision numbers:
RoundingMode::HalfAwayFromZero(equivalent toPHP_ROUND_HALF_UP)RoundingMode::HalfTowardsZero(equivalent toPHP_ROUND_HALF_DOWN)RoundingMode::HalfEven(equivalent toPHP_ROUND_HALF_EVEN)RoundingMode::HalfOdd(equivalent toPHP_ROUND_HALF_ODD)RoundingMode::TowardsZeroRoundingMode::AwayFromZeroRoundingMode::NegativeInfinityRoundingMode::PositiveInfinity
The legacy integer PHP_ROUND_* constants are also accepted for the four half-modes.
Note: The polyfilled
RoundingModeis a pure enum, matching native PHP 8.4 (it has no backing value;->value/from()/tryFrom()are intentionally unavailable).
Interoperability with symfony/polyfill-php84
symfony/polyfill-php84 also declares bcceil(), bcfloor(), bcround() and bcdivmod(), but only
when the native bcmath extension is already loaded (its purpose is to backport the new PHP 8.4
functions to older PHP that already has bcmath). This package instead provides the full bcmath API for
environments without the extension.
When both packages are installed on an environment that does have the bcmath extension and runs
PHP 8.2/8.3, both declare bcceil()/bcfloor()/bcround(); whichever autoloads first wins (the
function_exists() guards prevent a fatal redeclaration). This package's rounding is implemented with a
native-compatible string algorithm, so the numeric results are identical regardless of which
implementation is used. On environments without the extension only this package is active.
โก Performance
This polyfill uses phpseclib's BigInteger class for arbitrary precision arithmetic, providing reliable performance for applications that cannot use the native bcmath extension.
Performance Benchmarking
This project includes a comprehensive benchmarking tool to compare the performance of the polyfill against native bcmath functions.
Local Benchmark Execution
# Run benchmark with default settings (10,000 iterations) composer benchmark # or php benchmarks/run-benchmarks.php # Export results in different formats php benchmarks/run-benchmarks.php -f json -o results.json php benchmarks/run-benchmarks.php -f csv -o results.csv php benchmarks/run-benchmarks.php -f markdown -o BENCHMARK.md # Show help php benchmarks/run-benchmarks.php --help
Benchmark Configuration
The benchmark tests include:
- Basic Operations: Addition, subtraction, multiplication, division with various number sizes
- Advanced Operations: Power, square root, modulo operations
- Large Numbers: Operations with 100, 500, and 1000 digit numbers
- High Precision: Operations with scale values of 20, 50, and 100
Interpreting Results
Typical performance comparison shows:
- Basic operations: Polyfill is ~100-250x slower than native
- Large numbers: Performance gap increases with number size (up to ~800x slower)
- Square root: Interestingly, polyfill can be faster for high-precision operations
Example output:
BCMath Performance Benchmark
========================================
Iterations per test: 10000
Native BCMath: Available
Basic Operations
----------------
Small numbers (10 digits):
bcadd | Native: 1.5ms | Polyfill: 230ms | Ratio: 150x slower
...
Summary
========================================
Average performance ratio: 26x
Polyfill is on average 26x slower than native
GitHub Actions Integration
The project includes automated benchmarking workflows:
1. PR Benchmarks (/benchmark command)
Comment /benchmark on any PR to run performance tests:
- Automatically triggered on PR updates to relevant files
- Quick mode (1,000 iterations) for fast feedback
- Results posted as PR comment
2. Merge Benchmarks
Automatically runs when PRs are merged to main:
- Full benchmark (10,000 iterations)
- Results posted to the merged PR for reference
3. Manual Benchmarks
Trigger via GitHub Actions UI:
- Customizable iteration count
- Multi-PHP version testing (8.1, 8.2, 8.3, 8.4, 8.5)
- Results saved as artifacts
Performance Considerations
While the polyfill is significantly slower than native bcmath:
- It provides full functionality when bcmath extension is unavailable
- Performance is adequate for most applications not requiring intensive calculations
- Consider using native bcmath for performance-critical applications
โ ๏ธ Known Limitations
Extension Detection
extension_loaded('bcmath')will returnfalsewhen using the polyfill- Recommended approach: Don't check for the extension, just use the functions
Configuration Options
- bcmath.scale INI setting is ignored: When the native bcmath extension is not loaded, PHP does not recognize the
bcmath.scaleINI setting. This means:ini_get('bcmath.scale')returnsfalseini_set('bcmath.scale', ...)won't work- INI settings in php.ini or PHPT tests are ignored
- The polyfill defaults to scale 0 when no explicit scale is provided
- Workaround: Use
bcscale()instead to set the scale globally - To get the current scale:
- PHP >= 7.3.0: Use
bcscale()without arguments - PHP < 7.3.0: Use
max(0, strlen(bcadd('0', '0')) - 2)
- PHP >= 7.3.0: Use
๐ Key Differences from phpseclib/bcmath_compat and symfony/polyfill-php84
The three libraries target different goals. phpseclib/bcmath_compat is the classic pre-8.4 polyfill.
symfony/polyfill-php84 backports only the new PHP 8.4 functions and activates them only when the
native bcmath extension is already loaded. bcmath-polyfill provides the complete bcmath API โ
including the PHP 8.4 additions โ for environments with or without the extension.
| Feature | phpseclib/bcmath_compat | symfony/polyfill-php84 | bcmath-polyfill |
|---|---|---|---|
Classic bc functions (bcadd, bcmul, โฆ) |
โ | โ Not provided | โ |
| Works without the bcmath extension | โ | โ Requires the extension | โ |
bcfloor() / bcceil() / bcround() |
โ | โ (extension required) | โ |
bcdivmod() |
โ | โ (delegates to native bc) | โ (works without the extension) |
| RoundingMode enum (all 8 modes) | โ | โ | โ |
| Arbitrary-precision rounding (no float fallback) | โ | โ (pure string algorithm) | โ (pure string algorithm) |
RoundingMode is a pure enum (native shape) |
โ | โ | โ |
| Implementation of rounding | โ | Pure string digits | Pure string digits (phpseclib elsewhere) |
| Extra dependency | phpseclib | none | phpseclib |
| PHP 8.2+ deprecations | โ ๏ธ Warnings | โ Fixed | โ Fixed |
| Active maintenance | โ Limited | โ Active | โ Active |
| CI/CD (PHP versions) | GitHub Actions (8.1, 8.2, 8.3) | 7.2+ | GitHub Actions (8.1, 8.2, 8.3, 8.4, 8.5) |
When
bcmath-polyfillandsymfony/polyfill-php84are installed together on an environment that has the bcmath extension and runs PHP 8.2/8.3, both declarebcceil()/bcfloor()/bcround()and whichever autoloads first wins (guarded byfunction_exists(), so no fatal redeclaration). Because this package's rounding uses a native-compatible string algorithm, the results are identical either way. See Interoperability withsymfony/polyfill-php84.
Migration from phpseclib/bcmath_compat
Switching is seamless - no code changes required:
# Remove old package composer remove phpseclib/bcmath_compat # Install bcmath-polyfill composer require nanasess/bcmath-polyfill
๐งช Testing
Running PHPUnit Tests
# Install dependencies composer install # Run all tests composer test # or vendor/bin/phpunit # Run tests without bcmath extension vendor/bin/phpunit --group without-bcmath
Docker-based PHPT Testing
This project includes comprehensive Docker-based testing using official PHP core bcmath tests to ensure 100% compatibility:
# Build Docker test environment (PHP 8.3 by default) docker build -f Dockerfile.test-without-bcmath -t bcmath-phpt-test . # Run all PHPT tests docker run --rm -v $PWD:/app bcmath-phpt-test # Build and test with specific PHP version docker build -f Dockerfile.test-without-bcmath --build-arg PHP_VERSION=8.4 -t bcmath-phpt-test:8.4 . docker run --rm -v $PWD:/app bcmath-phpt-test:8.4 # Skip specific tests (supports exact test name matching) docker run --rm -v $PWD:/app bcmath-phpt-test --skip bcceil,bcround,bcpowmod # Run specific test file docker run --rm -v $PWD:/app bcmath-phpt-test tests/php-src/bcadd.phpt # Show help docker run --rm bcmath-phpt-test --help
Available Options
--skip TESTS- Comma-separated list of test names to skip (exact matching)--help, -h- Show usage information
Supported PHP Versions
- PHP 8.1, 8.2, 8.3, 8.4, 8.5
The Docker PHPT tests automatically run on GitHub Actions CI across all supported PHP versions to ensure comprehensive compatibility with the official PHP core bcmath test suite.
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ License
This project is licensed under the MIT License - see the LICENSE.md file for details.
๐ Credits
This project is a fork of phpseclib/bcmath_compat, originally created by the phpseclib team. We've extended it with PHP 8.4 function support and continue to maintain compatibility with all PHP versions.
Made with โค๏ธ for the PHP community