laravel-numberable maintained by tresor-kasenda
Laravel Numberable
A fluent, expressive API for numeric operations in Laravel — like Stringable, but for numbers.
use TresorKasenda\Numberable\Numberable;
Numberable::make(1500)
->add(500)
->multiply(1.1)
->asCurrency('USD')
->withLocale('en_US')
->format(); // "$2,200.00"
Requirements
- PHP 8.3+
- Laravel 10, 11, or 12
ext-intlPHP extension- Optional:
brick/mathfor arbitrary-precision decimal math
Installation
composer require tresor-kasenda/laravel-numberable
The package auto-registers its service provider via Laravel's package discovery.
For arbitrary-precision decimal operations (addPrecise(), dividePrecise(), etc.), install:
composer require brick/math
Quick Start
There are several ways to create a Numberable instance:
use TresorKasenda\Numberable\Numberable;
// Using the static factory
$n = Numberable::make(42);
$n = Numberable::of(42); // alias
// Using the global helper
$n = number(42);
// Parsing strings (handles comma decimals)
$n = Numberable::parse('1.234,56', 'de_DE');
$n = Numberable::from('1.234,56', 'de_DE'); // alias
// Strict type parsing
$n = Numberable::parseInt('42'); // int
$n = Numberable::parseFloat('3.14'); // float
The
number()helper returnsnullwhen givennull, making it safe for nullable values.
Arithmetic Operations
All operations are immutable — they return a new instance, leaving the original untouched.
$price = Numberable::make(100);
$price->add(50); // 150
$price->subtract(20); // 80
$price->multiply(2); // 200
$price->divide(4); // 25
$price->mod(3); // 1
$price->pow(2); // 10000
$price->abs(); // 100 (useful for negatives)
$price->round(2); // rounds to 2 decimals
$price->floor(); // rounds down
$price->ceil(); // rounds up
$price->clamp(0, 100); // keeps the value between 0 and 100
$price->trim(); // removes trailing decimal zeros (10.0 => 10)
Chaining
Chain multiple operations fluently:
$result = number(100)
->add(50)
->multiply(2)
->subtract(10)
->divide(3)
->round(2)
->value(); // 96.67
Division by zero throws a
\DivisionByZeroError.
Arbitrary Precision (Optional)
When brick/math is installed, you can perform decimal operations without float drift:
$result = Numberable::fromDecimal('0.1')
->addPrecise('0.2')
->multiplyPrecise('3')
->dividePrecise('7', scale: 8)
->roundPrecise(4)
->preciseValue(); // "0.1286"
Available precise methods:
Numberable::supportsArbitraryPrecision()Numberable::fromDecimal(int|float|string $value)->addPrecise(),->subtractPrecise(),->multiplyPrecise()->dividePrecise(int|float|string $value, int $scale = 14, string $roundingMode = 'HALF_UP')->modPrecise(),->roundPrecise()->comparePrecise(),->equalsPrecise(),->preciseValue()
Formatting
Basic Formatting
number(1234567)->format(); // "1,234,567"
number(3.14159)->withPrecision(2)->format(); // "3.14"
number(1234.5)->withLocale('fr')->format(); // "1 234,5"
Format Styles
// Currency
number(1000)->asCurrency('EUR')->format(); // "€1,000.00"
number(1000)->withCurrency('USD')->asCurrency()->format(); // "$1,000.00"
// Percentage
number(75)->asPercentage()->format(); // "75%"
// Ordinal
number(1)->asOrdinal()->format(); // "1st"
// Spell out
number(5)->asSpell()->format(); // "five"
// File size
number(1048576)->asFileSize()->format(); // "1 MB"
// Abbreviation
number(1000000)->asAbbreviated()->format(); // "1M"
Direct Style Formatting
Use formatAs() to format with a specific style directly:
number(42)->formatAs('spell'); // "forty-two"
number(2)->formatAs('ordinal'); // "2nd"
number(1500)->formatAs('currency', ['currency' => 'GBP']); // "£1,500.00"
number(2048)->formatAs('fileSize'); // "2 KB"
Available styles: currency, percentage, spell, ordinal, spellOrdinal, abbreviated (alias: summarized), fileSize (alias: humanReadable)
Locale Support
Apply locale for any formatting via withLocale():
number(1234.56)->withLocale('de_DE')->format(); // "1.234,56"
number(1000)->withLocale('fr_FR')->asCurrency('EUR')->format(); // "1 000,00 €"
Fluent Configuration
Configure formatting options via immutable "with" methods:
number(1234.5678)
->withLocale('en_US')
->withPrecision(2)
->format(); // "1,234.57"
number(99.99)
->withCurrency('EUR')
->asCurrency()
->format(); // "€99.99"
number(3.14159)
->withMaxPrecision(3)
->format(); // "3.142"
Type Checks
number(42)->isInt(); // true
number(3.14)->isInt(); // false
number(3.14)->isFloat(); // true
number(4)->isEven(); // true
number(5)->isOdd(); // true
number(12)->isMultipleOf(3); // true
number(17)->isPrime(); // true
number(5)->isPositive(); // true
number(-5)->isNegative(); // true
number(0)->isZero(); // true
Comparisons
$n = number(10);
$n->equals(10); // true
$n->greaterThan(5); // true
$n->greaterThanOrEqualTo(10); // true
$n->lessThan(20); // true
$n->lessThanOrEqualTo(10); // true
$n->between(5, 10); // true (inclusive by default)
$n->between(5, 10, false); // false if exactly on boundary
Conditional / Tap Helpers
Numberable now includes Laravel's Conditionable and Tappable traits:
$result = number(100)
->when(app()->isProduction(), fn ($n) => $n->multiply(1.2))
->unless(auth()->check(), fn ($n) => $n->add(10))
->tap(fn ($n) => logger()->info('Computed total', ['value' => $n->value()]));
Value Accessors
number(3.9)->value(); // 3.9 (raw int|float)
number(3.9)->toInt(); // 3
number(42)->toFloat(); // 42.0
Pairs
Generate range pairs — useful for building histograms, sliders, or pagination ranges:
number(0)->pairs(10, 3);
// [[0, 10], [10, 20], [20, 30]]
number(100)->pairs(25, 4);
// [[100, 125], [125, 150], [150, 175], [175, 200]]
Stringable
Numberable implements Stringable, so it works anywhere a string is expected:
echo number(42); // "42"
echo number(3.14)->withPrecision(1); // "3.1"
echo number(75)->asPercentage(); // "75%"
$message = "Total: " . number(1000)->asCurrency('USD');
// "Total: $1,000.00"
Custom Formats
Register your own named format styles:
Numberable::registerFormat('compact', function (int|float $value, array $options = []) {
$prefix = $options['prefix'] ?? '';
return $prefix . number_format($value, 0, '.', 'k');
});
number(512)->formatAs('compact'); // "512"
number(512)->formatAs('compact', ['prefix' => '#']); // "#512"
Custom formats take priority over built-in styles, allowing you to override defaults:
Numberable::registerFormat('currency', fn ($value) => "CUSTOM: $value");
number(100)->formatAs('currency'); // "CUSTOM: 100"
Clear all custom formats with:
Numberable::flushFormats();
Macros
Extend Numberable with your own methods using Laravel's Macroable trait:
// Register a macro (e.g., in a service provider)
Numberable::macro('double', function () {
return $this->multiply(2);
});
Numberable::macro('taxed', function (float $rate = 0.2) {
return $this->multiply(1 + $rate);
});
// Use it
number(50)->double()->value(); // 100
number(100)->taxed(0.15)->value(); // 115.0
// Check if a macro exists
Numberable::hasMacro('double'); // true
Testing
composer test
Static Analysis
composer analyse
License
The MIT License (MIT). Please see License File for more information.