oriolsegura / laravel-decimal
Value Object to handle decimals in Laravel without losing precision and directly from Eloquent models.
Requires
- php: ^8.2
- ext-bcmath: *
- illuminate/contracts: ^11.0 || ^12.0
- illuminate/database: ^11.0 || ^12.0
Requires (Dev)
- orchestra/testbench: ^9.0 || ^10.0
- phpunit/phpunit: ^10.0 || ^11.0 || ^12.0
README
A lightweight, immutable Value Object to handle decimals in Laravel without losing precision.
It uses bcmath internally to ensure mathematical correctness where floats fail.
🚀 Why use this package?
Designed for simplicity and immediate productivity. If you need to handle money or precise numbers in Laravel but don't want the overhead of heavy financial libraries or complex configurations, this is for you.
- Plug & Play: Zero configuration. Works out of the box.
- Laravel Native: Built with Eloquent casting in mind.
- Lightweight: No heavy dependencies. Just a wrapper around
bcmath.
Ideal for e-commerce, invoices, scientific data, and any scenario where 0.1 + 0.2 must equal 0.3.
⚠️ The Problem with Floats
Floating-point arithmetic is not precise because IEEE 754 standard cannot represent all decimal fractions exactly.
An example of this issue:
echo sprintf("%.17f", 0.1 + 0.2); // 0.30000000000000004 ❌ echo var_dump(0.3 === (0.1 + 0.2)); // bool(false) ❌
🛠️ Requirements
- PHP 8.2+
- Laravel 11.0+ / 12.0+
ext-bcmath
Installation
composer require oriolsegura/laravel-decimal
Eloquent Casting
This package shines when used with Eloquent models. You can store values as precise decimals (or strings) in your database and work with Decimal objects automatically in your code.
- Define the cast in your Model:
use Illuminate\Database\Eloquent\Model; use OriolSegura\Decimal\Decimal; class Product extends Model { protected $casts = [ 'price' => Decimal::class, // <--- Auto-casting ]; }
- Enjoy seamless precision:
$product = Product::create([ 'price' => '19.99', ]); // You can operate directly on the attribute $product->price = $product->price->add('5.50'); $product->save();
Usage API
Creation
You can create a Decimal from a string, integer or another Decimal.
$a = Decimal::from('10.50'); $b = Decimal::from(10);
Arithmetic
Since the object is immutable, operations always return a new instance.
$val = Decimal::from('10'); // Chaining $result = $val->plus(5)->minus(2)->times(2); // (10 + 5 - 2) * 2 = 26
Supported methods
These are the implemented methods for arithmetic operations:
plus(self|int|string $other)(alias:add,sum)minus(self|int|string $other)(alias:take,subtract)times(self|int|string $other)(alias:mul,multiply)dividedBy(self|int|string $other, int|null $scale = null)(alias:div)mod(self|int|string $other)(alias:modulo,remainder)abs()negate()(aliases:neg)inverse()(aliases:inv,reciprocal)
And these are the implemented methods for comparisons:
eq(self|int|string $other)(alias:equals)ne(self|int|string $other)(alias:notEquals,diff)gt(self|int|string $other)(alias:greaterThan)gte(self|int|string $other)(alias:greaterThanOrEqual)lt(self|int|string $other)(alias:lessThan)lte(self|int|string $other)(alias:lessThanOrEqual)isZero()isPositive()isNegative()isStrictlyPositive()isStrictlyNegative()min(self|int|string $other, self|int|string ...$values)max(self|int|string $other, self|int|string ...$values)
Division & Rounding
By default, division uses an automatic scale equal to the maximum of the two operands scales,
ensuring this is also at least 12 decimal places to ensure precision. But you can also
provide a $scale parameter to specify the number of decimal places in the result.
Currently, this library only supports Half-Up Rounding for division.
// Automatic scale (truncating logic for infinite fractions) echo Decimal::from(1)->div(3); // "0.333333333333" // Explicit scale (Rounds Half-Up) echo Decimal::from(2)->div(3, scale: 2); // "0.67"
License
This package is open-sourced software licensed under the MIT license.