Laravel cast for handling decimal values as base-100 integers (cents/centesimals)
aichadigital/lara100 is a Laravel package for laravel cast for handling decimal values as base-100 integers (cents/centesimals).
It currently has 0 GitHub stars and 2.444 downloads on Packagist (latest version v2.0.0).
Install it with composer require aichadigital/lara100.
Discover more Laravel packages by aichadigital
or browse all Laravel packages to compare alternatives.
Last updated
Development happens on gitlab.castris.com. The GitHub repository is a read-only distribution mirror: issues and pull requests opened there are not seen.
A Laravel package providing an immutable, scale-configurable exact decimal value object (FixedDecimal) and a matching Eloquent cast (FixedDecimalCast). Values are stored as plain integers (unscaled) in the database; the cast and value object handle all precision arithmetic using brick/math under the hood.
Floating-point arithmetic is not safe for monetary or fiscal values:
0.1 + 0.2 === 0.3; // false — result is 0.30000000000000004
FixedDecimal eliminates this class of error entirely:
use AichaDigital\Lara100\ValueObjects\FixedDecimal;
$a = FixedDecimal::ofDecimalString('0.1');
$b = FixedDecimal::ofDecimalString('0.2');
$c = $a->plus($b);
$c->toDecimalString(); // '0.3' — exact, always
Arithmetic is exact at every step. The result serialises as a decimal string ('0.3'), not a float, so API consumers and hash-based fiscal systems (AEAT Verifactu, etc.) always receive the canonical value.
brick/math ^0.14.2 (pulled in automatically)composer require aichadigital/lara100
No configuration file is required — lara100 ships with no publishable config.
Store unscaled integers. The scale is declared at the model level, not in the column:
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->integer('price')->default(0); // scale 2: 1999 = 19.99
$table->integer('exchange_rate')->default(0); // scale 4: 12345 = 1.2345
$table->timestamps();
});
Declare the cast with the scale as a colon-separated suffix. The scale is required; omitting it throws immediately rather than silently misinterpreting the column:
use AichaDigital\Lara100\Casts\FixedDecimalCast;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected function casts(): array
{
return [
'price' => FixedDecimalCast::class.':2', // 1999 → 19.99
'exchange_rate' => FixedDecimalCast::class.':4', // 12345 → 1.2345
];
}
}
The cast accepts only FixedDecimal|null on assignment. Scalars are rejected deliberately — callers must choose the conversion explicitly:
// Reading: the cast returns a FixedDecimal built from the stored integer
$product = Product::find(1);
$price = $product->price; // FixedDecimal('19.99', scale=2)
$price->toDecimalString(); // '19.99'
$price->unscaledValue(); // 1999
$price->scale(); // 2
echo json_encode($price); // "19.99" (exact decimal string, not float)
// Writing: build a FixedDecimal explicitly, then assign it
$product->price = FixedDecimal::ofDecimalString('29.99', 2);
$product->save(); // DB stores 2999 (integer)
// null is allowed for nullable columns
$product->price = null;
$product->save(); // DB stores NULL
Assigning a raw scalar throws InvalidFixedDecimal — the strict contract prevents accidental precision loss at the boundary:
$product->price = 19.99; // throws InvalidFixedDecimal — use ofDecimalString / ofFloat
Recommended constructors:
use AichaDigital\Lara100\ValueObjects\FixedDecimal;
// From the raw stored integer (no conversion, no rounding — the cleanest path)
$price = FixedDecimal::ofUnscaled(1999, 2); // 19.99
$rate = FixedDecimal::ofUnscaled(12345, 4); // 1.2345
// From a decimal string (parse and optionally coerce to a target scale)
$price = FixedDecimal::ofDecimalString('19.99'); // scale inferred from the string's decimals (here, 2)
$price = FixedDecimal::ofDecimalString('19.9', 2); // coerced to scale 2 → '19.90'
// Zero at a given scale
$zero = FixedDecimal::zero(2); // 0.00
Migration-only boundary (not for new code):
use AichaDigital\Lara100\RoundingMode;
// From a float — reintroduces IEEE-754 at the boundary; use only when crossing
// a legacy float API or when receiving user input that cannot be treated as a string
$price = FixedDecimal::ofFloat(19.99, 2, RoundingMode::HalfUp);
All arithmetic produces a new FixedDecimal; the original is unchanged (immutable):
$price = FixedDecimal::ofDecimalString('19.99');
$tax_rate = FixedDecimal::ofDecimalString('0.21');
$tax = $price->multipliedBy($tax_rate)->toScale(2, RoundingMode::HalfUp);
$total = $price->plus($tax);
$tax->toDecimalString(); // '4.20'
$total->toDecimalString(); // '24.19'
// Division always requires an explicit scale and rounding mode
$half = $total->dividedBy(2, 2, RoundingMode::HalfUp);
$half->toDecimalString(); // '12.10'
Available arithmetic: plus, minus, multipliedBy, dividedBy, toScale, negated, abs.
$a = FixedDecimal::ofDecimalString('10.00');
$b = FixedDecimal::ofDecimalString('20.00');
$a->isEqualTo($b); // false
$a->isLessThan($b); // true
$a->isGreaterThan($b); // false
$a->isZero(); // false
$a->isPositive(); // true
$a->isNegative(); // false
$a->compareTo($b); // -1
$price = FixedDecimal::ofUnscaled(1999, 2);
$price->toDecimalString(); // '19.99' — exact, preferred for persistence / hashing
$price->unscaledValue(); // 1999 — raw stored integer
$price->scale(); // 2
$price->toFloat(); // 19.99 — reintroduces float imprecision; display/legacy only
$price->toBigDecimal(); // Brick\Math\BigDecimal — escape hatch for raw brick/math use
json_encode($price); // "19.99" — jsonSerialize() returns the decimal string
lara100 ships its own RoundingMode enum — fully encapsulated from the underlying brick/math engine:
| Case | Description | Typical use |
|------|-------------|-------------|
| HalfUp | 0.5 rounds away from zero | EU/Spain fiscal standard; default |
| HalfEven | 0.5 rounds to nearest even | Banker's rounding; accounting |
| HalfDown | 0.5 rounds toward zero | Conservative rounding |
| Up | Always away from zero | Always round up |
| Down | Always toward zero (truncate) | Always truncate |
| Ceiling | Toward positive infinity | |
| Floor | Toward negative infinity | |
HalfUp is the European/Spanish fiscal standard and the default wherever lara100 accepts a rounding mode. HalfEven (banker's rounding) is the right choice for general accounting aggregations.
lara100 is a Laravel-native, scale-aware exact decimal built on top of brick/math. It occupies a specific niche — here is when to reach for each option:
| Library | Abstraction | Laravel cast | Currency | When to use |
|---------|-------------|:------------:|:--------:|-------------|
| lara100 | FixedDecimal (exact, scale-aware) | Yes (FixedDecimalCast) | No | Prices, rates, fiscal amounts in Laravel — exact arithmetic without currency overhead |
| brick/math | BigDecimal, BigInteger | No | No | Raw exact arithmetic outside Laravel; lara100 builds on this |
| brick/money | Money (amount + currency) | No | Yes | Multi-currency systems, allocation, currency conversion |
| moneyphp/money | Money (amount + currency) | No | Yes | Same as brick/money; wider ecosystem adoption |
Choose lara100 when:
FixedDecimal attributes directlyReach for brick/math directly when:
BigInteger or BigRational (lara100 only wraps BigDecimal)Reach for brick/money or moneyphp/money when:
lara100 encapsulates brick/math ^0.14.2 internally. The dependency is lightweight; brick types are not exposed except via the explicit toBigDecimal() escape hatch.
| Solution | DB column | PHP value | Precision | Laravel cast | Multi-currency |
|----------|-----------|-----------|-----------|:------------:|:--------------:|
| lara100 FixedDecimal | INTEGER (unscaled) | FixedDecimal (exact) | Exact | Yes | No |
| lara100 Base100Int | INTEGER (cents) | int | Exact | Yes | No |
| brick/math (BigDecimal) | — | BigDecimal (exact) | Exact | No | No |
| brick/money | INTEGER | Money (exact + currency) | Exact | No | Yes |
| moneyphp/money | INTEGER | Money (exact + currency) | Exact | No | Yes |
| Native DECIMAL column | DECIMAL(10,2) | float | Float — imprecise | No | No |
composer test
composer test-coverage
composer test-parallel
composer phpstan
composer pint
Please see CHANGELOG for more information on what has changed recently.
Contributions are welcome! Please feel free to submit a Pull Request.
If you discover a security vulnerability, please send an e-mail to Abdelkarim Mateos Sanchez via [email protected].
The MIT License (MIT). Please see License File for more information.
AichaDigital is an IT company focused on IT services.