Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
- next
workflow_dispatch:

permissions:
contents: read

jobs:
testsuite:
runs-on: ubuntu-latest
Expand All @@ -18,6 +21,8 @@ jobs:

steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down Expand Up @@ -51,6 +56,8 @@ jobs:

steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -67,3 +74,24 @@ jobs:

- name: Run phpcs
run: composer cs-check

docs:
name: Documentation
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
cache-dependency-path: docs/package-lock.json
- run: npm ci
working-directory: docs
- run: npm audit --audit-level=low
working-directory: docs
- run: npm run docs:build
working-directory: docs
2 changes: 1 addition & 1 deletion .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@v6
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,12 @@ var_dump($total->equals('0.3')); // bool(true)
- Arithmetic, rounding, comparison and conversion.
- Immutable.
- Handles numbers beyond the integer and float range.
- No custom PHP extension, only `bcmath`.
- Calculates with PHP's `bcmath` extension.

## Installation

```
composer require php-collective/decimal-object
```
Follow [Installation](https://php-collective.github.io/decimal-object/guide/installation)
to install the package and enable bcmath for the PHP process that runs it.

Requires PHP 8.2+ and the `bcmath` extension. The 1.x API is maintained in the 1.4 releases, see [Upgrading](https://php-collective.github.io/decimal-object/reference/upgrading) for the changes. PHP 8.1 is covered by the 1.3 releases. For anything older, use the [legacy library](https://github.com/spryker/decimal-object).

Expand Down
3 changes: 2 additions & 1 deletion docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
.vp-doc table code,
.vp-doc p code,
.vp-doc li code {
white-space: nowrap;
white-space: normal;
overflow-wrap: anywhere;
}

.vp-doc .custom-block.tip {
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ To preview the site locally:

```sh
cd docs
npm install
npm ci
npm run docs:dev
```
2 changes: 1 addition & 1 deletion docs/guide/creating.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ $a = Decimal::create('1.234');
$b = new Decimal('1.234');
```

Both forms take the same arguments. `create()` reads better in a chain and also accepts an existing `Decimal`.
Both forms accept numeric values and `Stringable` objects, including an existing `Decimal`. `create()` reads better in a chain, and returns a `Decimal` of the called class as is when no scale is given.

## Accepted input

Expand Down
13 changes: 11 additions & 2 deletions docs/guide/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
# Getting started

Complete [Installation](./installation) first.

Floats cannot represent most decimal fractions exactly:

```php
var_dump(0.1 + 0.2 === 0.3); // bool(false)
var_dump(0.1 + 0.2 - 0.3); // float(5.551115123125783E-17)
```

`Decimal` stores the number as a string of digits and calculates with bcmath, so the result is the one you would get on paper:
`Decimal` stores the number as a string of digits and calculates with bcmath, so decimal fractions can be added exactly.

Save this as `example.php` in the directory containing `composer.json`:

```php
<?php
require __DIR__ . '/vendor/autoload.php';

use PhpCollective\DecimalObject\Decimal;

$sum = Decimal::create('0.1')->add('0.2');

echo $sum; // 0.3
echo $sum, PHP_EOL; // 0.3
var_dump($sum->equals('0.3')); // bool(true)
```

Run `php example.php`. Later snippets use the same Composer autoloader and `Decimal` import.

## Immutability

A `Decimal` never changes. Each operation returns a new object, so assign the result:
Expand Down
9 changes: 3 additions & 6 deletions docs/guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
composer require php-collective/decimal-object
```

## Requirements
## Compatibility

- PHP 8.2 or newer
- The `bcmath` extension

Composer checks both. Enable `ext-bcmath` for the CLI and for the web server, as they often load different `php.ini` files.
Composer checks PHP and extension compatibility. Enable `ext-bcmath` for the CLI and for the web server, as they often load different `php.ini` files.

::: tip Older PHP versions
PHP 8.1 is served by the 1.3 releases, which Composer selects on its own. For PHP 7 use the original [spryker/decimal-object](https://github.com/spryker/decimal-object).
PHP 8.1 is served by the 1.3 releases, which Composer selects on its own. For PHP versions older than 8.1, use the original [spryker/decimal-object](https://github.com/spryker/decimal-object).
:::

Continue with [Getting started](./getting-started).
2 changes: 1 addition & 1 deletion docs/guide/rounding.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ echo Decimal::create('-12.5')->round(); // -13
echo Decimal::create('11')->round(2); // 11.00
```

The first argument is the number of decimal places, `0` by default. The result always has exactly that scale, padded with zeros if needed.
The first argument is the number of decimal places, `0` by default. The result always has exactly that scale, padded with zeros if needed. Negative scales round to positions before the decimal point.

The default mode rounds half away from zero, like PHP's `round()`. It works on the decimal digits and never passes through a float.

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ layout: home
hero:
name: Decimal Object
text: Exact decimal arithmetic for PHP
tagline: An immutable value object on top of bcmath. No custom extension, no float rounding error.
tagline: An immutable value object on top of bcmath. Uses bcmath, no float rounding error.
image:
src: /logo.svg
alt: Decimal Object logo
Expand Down
15 changes: 12 additions & 3 deletions docs/reference/alternatives.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This library is one way to handle decimals in PHP. Depending on the project anot

| Library | Notes |
| --- | --- |
| [php-decimal](https://php-decimal.github.io/) | A PHP extension. Faster, and it supports the arithmetic operators, but it has to be installed on every server. Works with precision (significant digits) where this library works with scale. |
| [php-decimal](https://php-decimal.github.io/) | A PHP extension with arithmetic operators, installed separately on each server. Works with precision (significant digits) where this library works with scale. |
| [brick/math](https://github.com/brick/math) | `BigDecimal`, `BigInteger` and `BigRational` with several calculation engines and more rounding modes. |
| [brick/money](https://github.com/brick/money), [moneyphp/money](https://github.com/moneyphp/money) | Money with currencies, allocation and formatting. Choose one of these when you need currency handling and not only a number. |

Expand All @@ -16,10 +16,19 @@ This library is one way to handle decimals in PHP. Depending on the project anot
| `abs()` | `absolute()` |
| `signum()` | `sign()` |
| `sub()`, `mul()`, `div()` | `subtract()`, `multiply()`, `divide()` |
| `shift()`, `isEven()`, `isOdd()` | Same method names |
| `rem($value)` | `mod($value)` |
| `mod($value)` | Integer modulo in php-decimal; see the different [modulo behavior](../guide/arithmetic#modulo) here |
| `toFixed($places)` | `(string)$decimal->withScale($places)` |
| `Decimal::sum()` | `Decimal::sum()` |
| `Decimal::avg()` | `Decimal::average($values, $scale, $roundMode)` |
| Precision as significant digits | Scale as decimal places |

Not available here: `rem()`, `shift()`, `ln()`, `exp()`, `log10()`, `parity()`, `isEven()`, `isOdd()`, `toFixed()`, `Decimal::sum()` and `Decimal::avg()`.
There are no methods named `rem()`, `ln()`, `exp()`, `log10()` or `parity()` here.
For a quotient and remainder, use [`divideWithRemainder()`](../guide/arithmetic#quotient-and-remainder).
Precision and scale differ between the libraries, so review rounding and division
when migrating calculations.

## History

The library was written in 2019 for the [Spryker](https://spryker.com/) commerce framework and has been used there in order and price calculations since. This repository is the independently maintained continuation for PHP 8. The original for older PHP versions is [spryker/decimal-object](https://github.com/spryker/decimal-object).
The library was written in 2019 for the [Spryker](https://spryker.com/) commerce framework and has been used there in order and price calculations since. This repository is the independently maintained continuation for the PHP ecosystem. The original for older PHP versions is [spryker/decimal-object](https://github.com/spryker/decimal-object).
6 changes: 2 additions & 4 deletions docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@

[dereuromark/cakephp-decimal](https://dereuromark.github.io/cakephp-decimal/) maps `DECIMAL` database columns to `Decimal` objects in the CakePHP ORM. Entities receive value objects on read, and form input is converted during marshalling.

```sh
composer require dereuromark/cakephp-decimal
```
Follow the plugin's [Installation](https://dereuromark.github.io/cakephp-decimal/guide/installation) for setup.

```php
$order = $orders->get($id);
$order->total = $order->total->add('0.10');
$orders->saveOrFail($order);
```

The plugin documentation covers setup, forms, localized input and formatting.
The plugin documentation also covers forms, localized input and formatting.

## Your own framework

Expand Down
8 changes: 8 additions & 0 deletions docs/reference/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,11 @@ Zero has no sign. `'-0.0'` becomes `0.0`, `isNegative()` is false for it, and `n
#### Operands

`mod()` and `pow()` accept operands in scientific notation.

## 1.4.1

`mod()` and `pow()` now accept float operands. Subclasses overriding these methods must include `float` in their operand type to keep their signatures compatible.

## 1.4.2

Comparisons use every stored decimal. `Decimal::create('1.9', 0)->compareTo(1)` returned `0` and now returns `1`. In 2.0 that value cannot be created without a rounding mode.
19 changes: 9 additions & 10 deletions src/Decimal.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,8 @@ protected function normalizeValue(string $value): string
/**
* Returns a Decimal instance from the given value.
*
* If the value is already a Decimal instance, then (since immutable) return it unmodified.
* Otherwise, create a new Decimal instance from the given value and return
* it.
* If the value is already an instance of this class and no scale is supplied,
* return it as is. Otherwise, construct a new instance from the value.
*
* @param \Stringable|string|float|int $value
* @param int|null $scale Decimal places in the result. Omit to enable auto-detection.
Expand Down Expand Up @@ -356,12 +355,12 @@ protected static function extreme(iterable $values, int $direction): static
/**
* Equality
*
* This method is equivalent to the `==` operator.
* Compares numeric values, ignoring differences in scale.
*
* @param self|string|float|int $value
*
* @return bool TRUE if this decimal is considered equal to the given value.
* Equal decimal values tie-break on precision.
* Trailing zeros do not affect equality.
*/
public function equals(self|string|float|int $value): bool
{
Expand Down Expand Up @@ -539,7 +538,7 @@ public function isInteger(): bool
}

/**
* Returns if truly zero.
* Returns whether the value is zero.
*
* @return bool
*/
Expand All @@ -549,7 +548,7 @@ public function isZero(): bool
}

/**
* Returns if truly negative (not zero).
* Returns whether the value is negative.
*
* @return bool
*/
Expand All @@ -559,7 +558,7 @@ public function isNegative(): bool
}

/**
* Returns if truly positive (not zero).
* Returns whether the value is positive.
*
* @return bool
*/
Expand Down Expand Up @@ -754,7 +753,7 @@ protected function roundedQuotient(self $divisor, int $scale, RoundingMode|int $
}

/**
* This method is equivalent to the ** operator.
* Raises this decimal to a whole-number exponent.
*
* @param self|string|float|int $exponent A whole number up to MAX_EXPONENT in either direction
* @param int|null $scale Decimal places in the result. Omit for the exact result of a positive
Expand Down Expand Up @@ -831,7 +830,7 @@ public function sqrt(?int $scale = null, RoundingMode|int $roundMode = self::ROU
}

/**
* This method is equivalent to the % operator.
* Returns the remainder.
*
* @param self|string|float|int $value
* @param int|null $scale Decimal places in the result. Omit for the larger scale of both, which is exact.
Expand Down
Loading