Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Math Laravel Package

brick/math

brick/math is a PHP library for arbitrary precision arithmetic. It provides BigInteger, BigDecimal, BigRational, and BigNumber classes, automatically using GMP or BCMath when available for fast, exact calculations.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and verify the platform:

composer require brick/math:^0.19   # 0.x minor bumps can break; pin the cycle
php -m | grep -E '^(gmp|bcmath)$'   # optional, but a big speed win

The library needs PHP 8.2+. Without GMP or BCMath it still works via a pure-PHP calculator, just slower — so on production containers either install an extension or benchmark before you put it in a hot path.

Where to look first:

  • src/BigNumber.php — the shared API (plus, minus, multipliedBy, compareTo, sign, toScale, conversions). Everything below BigInteger / BigDecimal / BigRational inherits from it.
  • src/RoundingMode.php — the enum you will pass around constantly; read it before writing any money code.
  • README sections Division & rounding, Exceptions, and the of() vs parse() warning. Those three are the whole learning curve.
  • CHANGELOG/ — each 0.x cycle is a breaking release; check before bumping.

First use case — an invoice line with tax, rounded once at the end:

use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;

$unitPrice = BigDecimal::of('19.99');
$subtotal  = $unitPrice->multipliedBy(3);              // 59.97
$tax       = $subtotal->multipliedBy('0.20');          // 11.994 (exact, no float drift)

return [
    'subtotal' => (string) $subtotal->toScale(2, RoundingMode::HalfUp), // 59.97
    'tax'      => (string) $tax->toScale(2, RoundingMode::HalfUp),      // 11.99
    'total'    => (string) $subtotal->plus($tax)->toScale(2, RoundingMode::HalfUp),
];

Notice the shape of the workflow: parse strings in, do exact arithmetic, round only at the boundary where you persist or render, and emit strings.

Implementation Patterns

1. Eloquent cast that always hands you a BigDecimal.

namespace App\Casts;

use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class BigDecimalCast implements CastsAttributes
{
    public function __construct(private int $scale = 2) {}

    public function get($model, $key, $value, $attributes): ?BigDecimal
    {
        return $value === null ? null : BigDecimal::of($value)->toScale($this->scale);
    }

    public function set($model, $key, $value, $attributes): ?string
    {
        return $value === null ? null
            : (string) BigDecimal::of($value)->toScale($this->scale, RoundingMode::HalfUp);
    }
}
protected $casts = [
    'total'    => BigDecimalCast::class.':2',
    'unit_price' => BigDecimalCast::class.':4',
];

Pair this with DECIMAL(19,4) (or integer minor units in a BIGINT) columns. Never FLOAT/DOUBLE, and avoid Laravel's float cast. The stock decimal:2 cast is acceptable too — it returns a string, which is a safe input to BigDecimal::of() since DB values are trusted.

2. A single money service owns the rounding policy. Decide once whether you round per line then sum, or sum then round — brick/math will not decide for you. Centralise scale and RoundingMode there so controllers, jobs, and observers can't drift apart:

final class Money
{
    public const SCALE = 2;

    public static function round(BigDecimal $v): BigDecimal
    {
        return $v->toScale(self::SCALE, RoundingMode::HalfUp);
    }
}

3. Validate untrusted input with parse(), not of().

Validator::extend('big_decimal', function ($attribute, $value) {
    if (! is_string($value)) {
        return false;
    }

    try {
        BigDecimal::parse($value, maxDigits: 20)->toScale(2); // default mode: Unnecessary
        return true;
    } catch (\Brick\Math\Exception\MathException) {
        return false;
    }
});

toScale() with the default RoundingMode::Unnecessary doubles as a "at most 2 decimals" check. For a FormRequest, catch NumberFormatException and rethrow as a validation message so the user sees a field error, not a 500.

4. Keep BigNumber instances inside the domain, strings at the edges. In controllers, resources, JSON, cache payloads, and queued job properties, cast to (string). Sending "19.99" to JavaScript is also the only way to avoid the browser's own float truncation.

5. Comparison and sorting. Use the API, not operators:

$invoices->sort(fn ($a, $b) => $a->total->compareTo($b->total));
if ($balance->isLessThan(0)) { /* ... */ }

compareTo() works across types (BigDecimal vs BigInteger), so mixed sources are fine as long as conversion is lossless.

6. BigRational for exact rates. Tax rates, unit conversions, and proration factors are naturally fractions:

$rate  = BigRational::of('7/100');
$share = BigRational::of('1/3');
$amount = BigDecimal::of('100.00')->toBigRational()->multipliedBy($rate)->multipliedBy($share);

Convert back to BigDecimal with an explicit scale when you persist — the rational result is exact, the decimal is not.

7. Testing. Assert against strings, never floats: $this->assertSame('71.96', (string) $total);. This catches scale regressions ("71.960") that a float comparison would hide.

8. Bitwise helpers. For flag sets or IDs beyond 64 bits, BigInteger has and(), or(), xor(), not(), shiftedLeft(), shiftedRight(). Handy, but if a native int fits, use it — it is much faster.

Gotchas and Tips

  • of() is a DoS vector. A string like 1e1000000000 can allocate gigabytes. Any value from a request, API, CSV, or queue payload must go through BigDecimal::parse($v, allowedSyntax: NumberSyntax::SCIENTIFIC, maxDigits: 100) and be caught as NumberFormatException.
  • Floats are rejected on purpose. json_decode() turns 19.99 into a float, so a JSON API posting amounts as numbers is already lossy before you see it. Prefer string amounts or minor-unit integers; if you must accept a float, use BigDecimal::fromFloatShortest($f) (what the user meant) or fromFloatExact($f) (what the float actually holds — usually not what you want).
  • BigDecimal::dividedBy() requires a scale. dividedBy('8', 2) throws RoundingNecessaryException; supply a RoundingMode. Use dividedByExact() when the result is known to terminate (1/256 yes, 1/11 no).
  • BigInteger::dividedBy() throws on a remainder. That is intentional and a good bug-catcher. Use quotient(), remainder(), or quotientAndRemainder() when you actually want truncation.
  • Scale creep is real. multipliedBy() adds scales, plus() takes the max. A few chained operations can produce 0.300000000000 and blow past DECIMAL(19,4). Always toScale() before set(), and stripTrailingZeros() when normalising values for comparison or storage.
  • Different scales ≠ different values. BigDecimal::of('1.0')->isEqualTo('1.00') is true, but they are distinct objects. Never use == or === on these; use compareTo() === 0.
  • toInteger() overflows loudly. Anything beyond PHP_INT_MAX throws IntegerOverflowException — good, but it means you cannot round-trip a 30-digit bigint through toInteger(). Keep it as a string.
  • toFloat() is a one-way door. Only for legacy APIs and display. Never feed the result back into arithmetic.
  • Immutability bites twice. $total->plus($fee); does nothing, and $a->plus($b) returns a new instance — reassign or chain.
  • Catch MathException at boundaries and translate. RoundingNecessaryException → 422 validation error, DivisionByZeroException → 422, NumberFormatException → 400, everything else → 500. Use the simPod/phpstan-brick-math extension so PHPStan knows which methods throw which subclasses.
  • Rounding mode defaults are strict. The default Unnecessary throwing is a feature: it surfaces silent rounding. For accounting, HalfEven reduces cumulative bias; HalfUp matches most invoice expectations. Be explicit at every call site.
  • JSON encoding. BigDecimal is not a JS-safe number and not a JsonSerializable scalar you should rely on blindly — cast to string in JsonResource/toArray(). If you return the object directly from a cast on a model, json_encode will not do what you want.
  • Serialization is portable. These objects survive queue round-trips and cross-machine unserialization even when GMP is present on one host and absent on another — useful for queued invoice jobs, though string payloads are cheaper.
  • Pin the version. 0.x minors are breaking by design (dividedBy() semantics, getScale() → scale(), of() → nd() for rationals). Lock to ^0.19, read the changelog before upgrading, and keep the requirement in composer.json explicit so CI catches a PHP 8.1 host.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console