Skip to content

Fix zero handling in BCMath division and power operations#31

Merged
nanasess merged 2 commits intomainfrom
fix/zero-base
Sep 8, 2025
Merged

Fix zero handling in BCMath division and power operations#31
nanasess merged 2 commits intomainfrom
fix/zero-base

Conversation

@nanasess
Copy link
Owner

@nanasess nanasess commented Sep 8, 2025

Summary

This PR addresses critical zero handling issues in BCMath::div() and BCMath::pow() that were causing infinite loops and phpseclib internal errors when processing various zero formats.

Issues Fixed

🐛 Division by Zero Detection

  • Problem: checkDivisionByZero() only detected '0', missing decimal formats like '0.00', '-0.00'
  • Impact: Tests like bcdiv_error1.php failed with infinite loops and phpseclib errors
  • Solution: Enhanced normalization logic to detect all zero formats

🔄 Power Operation Infinite Loops

  • Problem: BCMath::pow() with zero base and negative exponents caused infinite loops
  • Impact: bcpow('0', '-1') hung indefinitely with phpseclib internal errors
  • Solution: Added early zero base detection with proper scaling

Changes Made

Core Fixes

  • Enhanced checkDivisionByZero(): Now properly detects '0', '0.00', '-0.00', '+0.000', etc.
  • Added zero base handling in BCMath::pow(): Prevents infinite loops with negative exponents
  • Robust normalization: Uses consistent logic for zero detection across operations

Test Coverage

  • Division by zero tests: 3 new test methods covering all zero formats from bcdiv_error1.php
  • Power zero base tests: DataProvider-driven tests covering 11 scenarios + special cases
  • PHPStan compliance: Fixed type annotations for static analysis

CI Integration

  • Enabled php-src tests: Removed bcpow from CI skip list
  • Docker compatibility: Enhanced Dockerfile for better php-src test execution

Technical Details

Zero Detection Algorithm

private static function checkDivisionByZero(string $divisor): void
{
    // Normalize and check for zero - handle '0', '0.00', '-0.00', etc.
    $normalized = ltrim($divisor, '+-');
    $normalized = ltrim($normalized, '0');
    $normalized = ltrim($normalized, '.');
    $normalized = rtrim($normalized, '0');
    if ($normalized === '' || $normalized === '.') {
        throw new \DivisionByZeroError(self::DIVISION_BY_ZERO_MESSAGE);
    }
}

Zero Base Power Handling

// Handle special case: 0 to any power is 0 (except 0^0 which is handled above)
if ($normalized === '' || $normalized === '.') {
    $result = '0';
    if ($scale !== 0) {
        $result .= '.'.str_repeat('0', $scale);
    }
    return $result;
}

Test Results

Before

  • docker run bcmath-test-without php tests/php-src/bcdiv_error1.php → Infinite loop with phpseclib errors
  • BCMath::pow('0', '-1', 2) → Hung indefinitely
  • PHPUnit tests: Some division by zero scenarios not covered

After

  • bcdiv_error1.php outputs: Division by zero × 3 (as expected)
  • BCMath::pow('0', '-1', 2) returns: 0.00
  • PHPUnit: +15 new tests, all passing
  • PHPStan: No type errors

Compatibility

  • php-src compatibility: All bcdiv_error1.php and bcpow.php tests pass
  • Backward compatibility: Existing functionality unchanged
  • Performance: Zero overhead for non-zero operations
  • Type safety: PHPStan level compliance maintained

Test plan

  • Run docker run --rm -v $PWD:/app bcmath-test-without php tests/php-src/bcdiv_error1.php - outputs 3 "Division by zero" messages
  • Run docker run --rm -v $PWD:/app bcmath-test-without php tests/php-src/bcpow.php - completes without errors
  • Run vendor/bin/phpunit --filter="testBcdiv.*Zero" - 3/3 tests pass
  • Run vendor/bin/phpunit --filter="testBcpow.*Zero" - 12/12 tests pass
  • Run vendor/bin/phpstan analyse - No errors
  • Verify no regressions in existing test suite

🤖 Generated with Claude Code

nanasess and others added 2 commits September 8, 2025 13:39
- Fix checkDivisionByZero to handle decimal zero formats ('0.00', '-0.00')
- Add zero base handling for BCMath::pow to prevent infinite loops with negative exponents
- Add comprehensive test coverage for bcdiv division by zero cases
- Add parameterized tests for BCMath::pow zero base scenarios using DataProvider
- Remove bcpow from CI skip list to enable php-src compatibility tests

Fixes phpseclib internal errors and infinite loops when processing
zero values in various decimal formats.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Updated the `bcpowZeroBaseProvider` method to `provideBcpowZeroBaseCases` and changed its return type from `array` to `iterable` for better flexibility. Adjusted the corresponding test annotation to reflect the new method name.
@claude
Copy link

claude bot commented Sep 8, 2025

Code Review: Fix zero handling in BCMath division and power operations

Overall Assessment ✅

This is a well-implemented fix that addresses critical zero-handling bugs in the BCMath polyfill. The changes are focused, thoroughly tested, and maintain backward compatibility.

Strengths 🎯

1. Robust Zero Detection Logic

The enhanced zero detection in both checkDivisionByZero() and pow() is comprehensive:

  • Handles various zero formats: '0', '0.00', '-0.00', '+0.000'
  • Uses consistent normalization logic across both methods
  • Prevents infinite loops that were occurring with phpseclib

2. Excellent Test Coverage

  • 15 new test cases covering edge cases
  • DataProvider pattern for systematic testing of various zero formats
  • php-src compatibility tests ensure behavior matches native bcmath
  • Tests are well-named and properly documented

3. Performance Considerations

  • Zero overhead for non-zero operations
  • Early return for zero cases prevents expensive BigInteger operations
  • Minimal additional string processing only when needed

Code Quality Observations 📋

1. Code Duplication Opportunity

The zero normalization logic is duplicated between checkDivisionByZero() and pow():

// Duplicated in both methods:
$normalized = ltrim($divisor, '+-');
$normalized = ltrim($normalized, '0');
$normalized = ltrim($normalized, '.');
$normalized = rtrim($normalized, '0');

Recommendation: Consider extracting this to a private helper method:

private static function isZero(string $number): bool
{
    $normalized = ltrim($number, '+-');
    $normalized = ltrim($normalized, '0');
    $normalized = ltrim($normalized, '.');
    $normalized = rtrim($normalized, '0');
    return $normalized === '' || $normalized === '.';
}

2. Edge Case Consideration

The normalization handles most cases well, but consider these edge cases:

  • Empty string inputs (though these should be caught by earlier validation)
  • Very long strings with many zeros
  • Malformed numbers (again, should be caught earlier)

3. Documentation

The inline comments are clear and explain the logic well. The PR description is exemplary in its thoroughness.

Security & Safety ✅

  • No security concerns identified
  • Type safety maintained with proper exception throwing
  • Input validation remains robust
  • Error messages are consistent with PHP's native bcmath

Performance Analysis 🚀

  • Early detection prevents expensive BigInteger operations for zero cases
  • String operations are minimal and efficient
  • No regression in performance for normal operations
  • Memory usage remains unchanged

Compatibility Assessment 🔧

  • Backward compatible - no breaking changes to existing API
  • php-src compatible - matches native bcmath behavior
  • Version compatibility maintained across supported PHP versions
  • Dependencies unchanged (phpseclib usage remains the same)

Minor Suggestions 💡

  1. Extract zero detection logic to reduce duplication
  2. Consider adding benchmarks for zero-heavy workloads (though not critical)
  3. Document the normalization algorithm in class-level docs for future maintainers

Test Plan Verification ✅

The test plan is comprehensive:

  • Native php-src test compatibility ✅
  • Unit test coverage for all zero formats ✅
  • PHPStan type checking ✅
  • Regression testing ✅

Recommendation: APPROVE

This PR successfully fixes critical bugs while maintaining high code quality standards. The implementation is robust, well-tested, and follows the project's coding conventions as outlined in CLAUDE.md.

Impact: Resolves infinite loops and crashes → Improves library reliability significantly.

@nanasess nanasess merged commit 2dbd1ba into main Sep 8, 2025
46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments