You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> This new major version is shifting the package towards more flexibility and configuration possibilities in general.
7
+
> This new major version aims to avoid rounding errors when working with division and multiplication.
8
8
>
9
-
> One of the main differences is that we replaced [`moneyphp/money`](https://github.com/moneyphp/money) with [`brick/money`](https://github.com/brick/money) under the hood. This introduces a ton of **breaking changes**, mainly on the instantiation methods that now reflect brick/money's API in order to keep things developer friendly. The `1.x` branch will still be available and maintained for a while, but we strongly recommend updating to `2.x`.
9
+
> We have replaced almost all `Brick\Money\Money` typehints to `Brick\Money\AbstractMoney` in order to allow usage of `Brick\Money\RationalMoney` instances as the base for the price object, in order to allow rounding-free division and multiplication. See [the new chapter on rounding errors](#handling-rounding-properly-when-using-division-and-multiplication) in this documentation for more information.
10
+
> We have also added type definitions everywhere we could. This introduces some **breaking changes**.
10
11
11
12
Using the underlying [`brick/money`](https://github.com/brick/money) library, this simple Price object allows to work with complex composite monetary values which include exclusive, inclusive, VAT (and other potential taxes) and discount amounts. It makes it safer and easier to compute final displayable prices without having to worry about their construction.
Each `Price` object has a `Brick\Money\Money` instance which is considered to be the item's unchanged, per-unit & exclusive amount. All the composition operations, such as adding VAT or applying discounts, are added on top of this base value.
22
+
Each `Price` object has a base `Brick\Money\Money` or `Brick\Money\RationalMoney` instance which is considered to be the item's unchanged, per-unit & exclusive amount. All the composition operations, such as adding VAT or applying discounts, are added on top of this base value.
22
23
23
24
```php
24
25
use Whitecube\Price\Price;
@@ -36,7 +37,7 @@ There are several convenient ways to obtain a `Price` instance:
36
37
37
38
| Method | Using major values | Using minor values | Defining units |
@@ -104,7 +105,9 @@ Parsing formatted strings is a tricky subject. More information on [parsing stri
104
105
105
106
## Accessing the Money objects (getters)
106
107
107
-
Once set, the **base amount** can be accessed using the `base()` method.
108
+
Once set, the **base amount** can be accessed using the `base()` method.
109
+
110
+
> **Note** If you give an instance of `Brick\Money\Money` as a parameter when instanciating the price object, you will get a `Brick\Money\Money` instance back. Similarly, instanciating with `Brick\Money\RationalMoney` will give you a `RationalMoney` object back.
The price object will forward all the `Brick\Money\Money` API method calls to its base value.
183
186
184
-
> ⚠️ **Warning**: In opposition to [Money](https://github.com/brick/money) objects, Price objects are not immutable. Therefore, operations like plus, minus, etc. will directly modify the price's base value instead of returning a new instance.
187
+
> **Warning**: In opposition to [Money](https://github.com/brick/money) objects, Price objects are not immutable. Therefore, operations like plus, minus, etc. will directly modify the price's base value instead of returning a new instance.
185
188
186
189
```php
187
190
use Whitecube\Price\Price;
188
-
use Brick\Money\Money;
189
191
190
192
$price = Price::ofMinor(500, 'USD')->setUnits(2); // 2 x $5.00
191
193
@@ -200,6 +202,53 @@ Please refer to [`brick/money`'s documentation](https://github.com/brick/money)
200
202
201
203
> 💡 **Nice to know**: Whenever possible, you should prefer using modifiers to alter a price since its base value is meant to be constant. For more information on modifiers, please take at the ["Adding modifiers" section](#adding-modifiers) below.
202
204
205
+
### Handling rounding properly when using division and multiplication
206
+
207
+
When creating a price object from a `Brick\Money\Money` instance, rounding errors can occur when doing division and multiplication.
208
+
209
+
An example of the problem: we have a base price of 1000 minor units, that we need to divide by 12 and then multiply by 11.
210
+
211
+
`1000 / 12 * 11 = 916,6666666666...`
212
+
213
+
Using the regular `Brick\Money\Money` class forces us to specify a rounding mode when doing the division, which means we have a rounded result before doing the multiplication, which introduces an error in the result:
214
+
215
+
216
+
```php
217
+
use \Brick\Money\Money;
218
+
use \Whitecube\Price\Price;
219
+
use \Brick\Math\RoundingMode;
220
+
221
+
$base = Money::ofMinor(1000, 'EUR');
222
+
$price = new Price($base);
223
+
224
+
// A rounding mode is mandatory in order to do the division,
The solution is to build the Price instance with a base `Brick\Money\RationalMoney` instance instead, which represents the amount as a fraction and thus does not require rounding.
// With RationalMoney, rounding is not necessary at this stage
244
+
$price->dividedBy(12)->multipliedBy(11);
245
+
246
+
// But rounding can occur at the very end
247
+
$price->to(new CustomContext(2), RoundingMode::HALF_UP)->getMinorAmount(); // 917 minor units ✅
248
+
```
249
+
250
+
For more information, see [brick/money's documentation on the matter](https://github.com/brick/money#advanced-calculations).
251
+
203
252
## Setting units (quantities)
204
253
205
254
This package's default behavior is to consider its base price as the "per unit" price. When no units have been specified, it defaults to `1`. Since "units" can be anything from a number of undividable products to a measurement, they are always converted to floats.
0 commit comments