A robust PHP library for handling decimal precision by storing floating-point numbers as integers, designed to eliminate floating-point precision issues in database storage and financial calculations.
composer require gryfoss/int-precision-helperThis library solves common floating-point precision issues by storing decimal numbers as integers. For example:
12.34is stored as1234(multiplied by 100)- All calculations are performed on integers to maintain precision
- Results are converted back to decimal representation when needed
This approach is particularly useful for:
- Financial calculations - Ensuring precise monetary computations
- Percentage calculations - Accurate rate and ratio calculations
- Database storage - Consistent decimal representation across systems
- Scientific calculations - Where precision is critical
This library requires PHP 8.4 or higher because it utilizes PHP's native bcround() function, which was introduced in PHP 8.4. The bcround() function provides:
- High-precision rounding for decimal calculations
- Consistent behavior across different platforms
- Performance optimization over custom rounding implementations
- BCMath integration for seamless mathematical operations
Converts a string representation of a decimal number to a normalized integer.
Parameters:
$value- String representation of a decimal number (e.g., "12.34", "0.05")$lessPrecise- Optional performance mode for large numbers
Example:
$normalized = IntPrecisionHelper::fromString("12.34"); // Returns: 1234
$normalized = IntPrecisionHelper::fromString("0.05"); // Returns: 5Validation: Throws InvalidArgumentException for invalid input formats.
Converts a float to a normalized integer representation.
Parameters:
$value- Float value to convert$lessPrecise- Optional performance mode
Example:
$normalized = IntPrecisionHelper::fromFloat(12.34); // Returns: 1234
$normalized = IntPrecisionHelper::fromFloat(0.99); // Returns: 99Universal conversion method that automatically detects input type and converts to normalized integer.
Parameters:
$value- Value to normalize (float, string, or integer)$lessPrecise- Optional performance mode for large numbers
Examples:
// Float input
$normalized = IntPrecisionHelper::normalize(12.34); // Returns: 1234
// String input
$normalized = IntPrecisionHelper::normalize("12.34"); // Returns: 1234
// Integer input (multiplied by precision factor)
$normalized = IntPrecisionHelper::normalize(12); // Returns: 1200Error Handling: Throws InvalidArgumentException for unsupported types (arrays, objects, etc.) or invalid string formats.
Converts a normalized integer back to its decimal representation as a float.
Parameters:
$normalizedValue- Normalized integer value
Example:
$float = IntPrecisionHelper::denormalize(1234); // Returns: 12.34
$float = IntPrecisionHelper::denormalize(99); // Returns: 0.99Converts a normalized integer back to a human-readable string representation.
Parameters:
$value- Normalized integer valuedecimalPlaces- Number of decimal places to display (default: 2)
Example:
$display = IntPrecisionHelper::toView(1234); // Returns: "12.34"
$display = IntPrecisionHelper::toView(1234, 3); // Returns: "1.234"Converts a normalized integer back to a float representation.
Example:
$float = IntPrecisionHelper::toFloat(1234); // Returns: 12.34Performs multiplication on normalized integers while maintaining precision.
Example:
// 12.34 * 2.00 = 24.68
$result = IntPrecisionHelper::normMul(1234, 200); // Returns: 2468
// Multiple values: 12.34 * 2.00 * 1.50
$result = IntPrecisionHelper::normMul(1234, 200, 150); // Returns: 3702Protection: Throws OverflowException if result would exceed PHP_INT_MAX.
Performs division on normalized integers with precision preservation.
Example:
// 12.34 / 2.00 = 6.17
$result = IntPrecisionHelper::normDiv(1234, 200); // Returns: 617Protection: Throws DivisionByZeroError if divisor is zero.
Adds multiple normalized integers.
Example:
// 12.34 + 5.67 + 8.90 = 26.91
$result = IntPrecisionHelper::normAdd(1234, 567, 890); // Returns: 2691Subtracts two normalized integers.
Example:
// 12.34 - 5.67 = 6.67
$result = IntPrecisionHelper::normSub(1234, 567); // Returns: 667Calculates percentage as a normalized integer.
Example:
// 50 out of 200 = 25.00%
$percentage = IntPrecisionHelper::calculatePercentage(50, 200); // Returns: 2500
$display = IntPrecisionHelper::toView($percentage); // Returns: "25.00"Compares two normalized integers.
Returns:
-1if$a < $b0if$a == $b1if$a > $b
Example:
$comparison = IntPrecisionHelper::normCompare(1234, 567); // Returns: 1Validates if a value is a valid normalized integer.
Example:
$isValid = IntPrecisionHelper::isValid(1234); // Returns: true
$isValid = IntPrecisionHelper::isValid("1234"); // Returns: false
$isValid = IntPrecisionHelper::isValid(12.34); // Returns: falseuse GryfOSS\Formatter\IntPrecisionHelper;
// String to normalized integer
$normalized = IntPrecisionHelper::fromString("12.34"); // 1234
// Float to normalized integer
$normalized = IntPrecisionHelper::fromFloat(12.34); // 1234
// Universal normalize method (auto-detects type)
$normalized = IntPrecisionHelper::normalize(12.34); // 1234 (float)
$normalized = IntPrecisionHelper::normalize("12.34"); // 1234 (string)
$normalized = IntPrecisionHelper::normalize(12); // 1200 (integer)
// Back to string representation
$display = IntPrecisionHelper::toView(1234); // "12.34"
// Back to float
$float = IntPrecisionHelper::toFloat(1234); // 12.34
// Using denormalize (alias for toFloat)
$float = IntPrecisionHelper::denormalize(1234); // 12.34// Demonstrate precision preservation
$originalFloat = 12.34;
$normalized = IntPrecisionHelper::normalize($originalFloat);
$restored = IntPrecisionHelper::denormalize($normalized);
// $restored === 12.34 (exact match)
$originalString = "99.99";
$normalized = IntPrecisionHelper::normalize($originalString);
$restored = IntPrecisionHelper::denormalize($normalized);
// $restored === 99.99 (exact match)
$originalInt = 100;
$normalized = IntPrecisionHelper::normalize($originalInt);
$restored = IntPrecisionHelper::denormalize($normalized);
// $restored === 100.0 (converted to float)// Multiplication: 12.34 * 2.00 = 24.68
$result = IntPrecisionHelper::normMul(1234, 200); // 2468
// Division: 12.34 / 2.00 = 6.17
$result = IntPrecisionHelper::normDiv(1234, 200); // 617
// Addition
$result = IntPrecisionHelper::normAdd(1234, 567, 890); // 2691
// Subtraction
$result = IntPrecisionHelper::normSub(1234, 567); // 667One method to convert any supported type to normalized integer:
- ✅ Floats:
normalize(12.34)→1234 - ✅ Strings:
normalize("12.34")→1234 - ✅ Integers:
normalize(12)→1200 - ❌ Arrays/Objects: Throws
InvalidArgumentException
Convert normalized integer back to decimal float:
denormalize(1234)→12.34denormalize(99)→0.99
| Input Type | Method | Example |
|---|---|---|
| String | fromString("12.34") |
1234 |
| Float | fromFloat(12.34) |
1234 |
| Integer | $value * 100 |
Manual |
| Output Type | Method | Example |
|---|---|---|
| String | toView(1234) |
"12.34" |
| Float | toFloat(1234) or denormalize(1234) |
12.34 |
- 🎯 Use
normalize()when input type varies or unknown - ⚡ Use
fromString()/fromFloat()for known types (slight performance benefit) - 📊 Use
denormalize()for float output - 📝 Use
toView()for formatted string display
The library provides comprehensive input validation with descriptive error messages:
try {
$result = IntPrecisionHelper::fromString("invalid");
} catch (InvalidArgumentException $e) {
echo "Invalid input format: " . $e->getMessage();
}
try {
$result = IntPrecisionHelper::fromString(""); // Empty string
} catch (InvalidArgumentException $e) {
echo "Input cannot be empty";
}
// Universal normalize method with type validation
try {
$result = IntPrecisionHelper::normalize([1, 2, 3]); // Array input
} catch (InvalidArgumentException $e) {
echo "Invalid input type: " . $e->getMessage();
// Output: "Input value must be a float, string, or int. Got array"
}
try {
$result = IntPrecisionHelper::normalize("1.23e2"); // Scientific notation
} catch (InvalidArgumentException $e) {
echo "Scientific notation not supported: " . $e->getMessage();
}All division operations are protected against division by zero:
try {
$result = IntPrecisionHelper::normDiv(1234, 0);
} catch (DivisionByZeroError $e) {
echo "Cannot divide by zero";
}
try {
$percentage = IntPrecisionHelper::calculatePercentage(50, 0);
} catch (DivisionByZeroError $e) {
echo "Total cannot be zero for percentage calculation";
}The library detects and prevents integer overflow conditions:
try {
$result = IntPrecisionHelper::normMul(PHP_INT_MAX, PHP_INT_MAX);
} catch (OverflowException $e) {
echo "Result would exceed maximum integer value";
}For performance-critical applications with very large numbers:
// Standard precision (recommended for most use cases)
$result = IntPrecisionHelper::fromString("12.34");
$result = IntPrecisionHelper::normalize("12.34");
// Less precise but faster for very large numbers
$result = IntPrecisionHelper::fromString("12.34", true);
$result = IntPrecisionHelper::fromFloat(12.34, true);
$result = IntPrecisionHelper::normalize("12.34", true);
$result = IntPrecisionHelper::normalize(12.34, true);NormMul and NormDiv methods have been removed due to PHP's case-insensitive function names conflicting with the new normMul and normDiv methods.
Migration Guide:
NormMul()→normMul()NormDiv()→normDiv()calculatePercentage()now returnsint|nullinstead offloat|null
You can customize the precision factor by extending the class:
class CustomPrecisionHelper extends IntPrecisionHelper
{
protected const PRECISION_FACTOR = 1000; // 3 decimal places instead of 2
protected const DECIMAL_PLACES = 3;
}
// Usage
$normalized = CustomPrecisionHelper::fromString("12.345"); // Returns: 12345
$display = CustomPrecisionHelper::toView(12345); // Returns: "12.345"PRECISION_FACTOR- Multiplier for decimal conversion (default: 100)DECIMAL_PLACES- Default decimal places for output (default: 2)
- PHP 8.4+ - Required for native
bcround()function support - BCMath extension - Essential for high-precision mathematical operations
- 64-bit system - Recommended for handling larger integer values
This library maintains 100% code coverage and follows rigorous testing standards to ensure reliability and precision.
- 📊 100% Code Coverage - Complete line coverage with comprehensive testing
- 🧪 74 Test Cases - Comprehensive unit test suite
- ✅ 151 Assertions - Detailed validation of all functionality
- 🎯 556 Feature Scenarios - Behavior-driven development tests
- 🚀 Edge Case Coverage - All error conditions and boundary cases tested
- � Continuous Integration - Automated testing on every commit
# Run complete test suite with coverage report
./vendor/bin/phpunit
# Generate detailed HTML coverage report
XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html coverage-html
# Use the provided coverage script
./test-coverage.sh# Run Behat feature tests
./vendor/bin/behat
# Run with detailed output
./vendor/bin/behat --format=pretty# Fast coverage validation
XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text- ✅ Conversion Methods - String/float to integer and back
- ✅ Universal Normalize/Denormalize - Type-agnostic conversion methods
- ✅ Mathematical Operations - Multiplication, division, addition, subtraction
- ✅ Error Handling - Invalid inputs, overflow, division by zero
- ✅ Edge Cases - Boundary values, special numbers, large integers
- ✅ Utility Functions - Comparison, validation, percentage calculations
- ✅ Division Operations - Comprehensive division scenarios
- ✅ Edge Cases - Boundary conditions and error states
- ✅ Float Conversion - Float input/output validation
- ✅ Multiplication - Complex multiplication scenarios
- ✅ Normalize/Denormalize - Universal conversion and round-trip testing
- ✅ String Conversion - String parsing and formatting
- ✅ View Conversion - Display formatting and precision
The project uses GitHub Actions for automated testing:
- Automated Test Execution - Runs on every push and pull request
- Coverage Validation - Ensures 100% coverage is maintained
- Multi-environment Testing - Tests across different PHP configurations
- Quality Gates - Prevents merging code that breaks tests or reduces coverage
We welcome contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or reporting issues, your help makes this library better for everyone.
Found a bug or have a feature request? Please open an issue with:
- Clear description of the problem or feature request
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- PHP version and system information
- Code examples demonstrating the issue
Ready to contribute code? We'd love your help! Please:
- Fork the repository and create a feature branch
- Write tests for any new functionality
- Ensure 100% coverage is maintained
- Follow PSR-12 coding standards
- Update documentation as needed
- Submit a pull request with a clear description
Before submitting a pull request, ensure:
- All tests pass:
./vendor/bin/phpunit - Feature tests pass:
./vendor/bin/behat - 100% code coverage maintained
- Code follows PSR-12 standards
- Documentation is updated
- CHANGELOG.md is updated (for notable changes)
We especially welcome contributions in these areas:
- Performance optimizations for large number operations
- Additional mathematical operations (modulo, power, etc.)
- Documentation improvements and examples
- Bug fixes and edge case handling
- Platform compatibility testing
Have an idea for a new feature? We'd love to hear it! Consider:
- Mathematical operations that would benefit from precision handling
- Integration helpers for popular frameworks
- Performance improvements for specific use cases
- Developer experience enhancements
# Clone your fork
git clone https://github.com/YOUR_USERNAME/int-precision-helper.git
cd int-precision-helper
# Install dependencies
composer install
# Run tests to ensure everything works
./vendor/bin/phpunit
./vendor/bin/behatPlease note that this project follows a Code of Conduct. By participating, you agree to:
- Be respectful and inclusive
- Focus on constructive feedback
- Help create a welcoming environment for all contributors
Need help getting started or have questions?
- 📖 Check the documentation and TESTING.md
- 🐛 Search existing issues
- 💬 Open a new issue for questions
- 📧 Contact the maintainers for complex queries
Thank you for contributing to IntPrecisionHelper! 🚀
MIT License