Skip to content

Commit b814722

Browse files
authored
Use brick/math (#5)
* WIP * Tweak * Preserve precision * --amend * Run skipped test * Readonly and private * Bump version * Fix matrix * Tweak rounding for divide * Tweak self * Fix phpstan * Tweak types * TWeak types
1 parent b21baa6 commit b814722

File tree

8 files changed

+76
-128
lines changed

8 files changed

+76
-128
lines changed

.github/workflows/run-coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Setup PHP
2121
uses: shivammathur/setup-php@v2
2222
with:
23-
php-version: 8.1
23+
php-version: 8.4
2424
extensions: mbstring, intl
2525
coverage: pcov
2626

.github/workflows/run-matcher.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Setup PHP
2020
uses: shivammathur/setup-php@v2
2121
with:
22-
php-version: 8.1
22+
php-version: 8.4
2323
extensions: mbstring, intl
2424

2525
- name: Configure matchers

.github/workflows/run-tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
php: [8.1, 8.2, 8.3, 8.4, 8.5]
15+
php: [8.2, 8.3, 8.4, 8.5]
1616
dependency-version: [prefer-stable]
17+
brick: [^0.9.3, ^0.10 , ^0.11, ^0.12, ^0.13, ^0.14]
1718

1819
name: PHP${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
1920

@@ -32,6 +33,7 @@ jobs:
3233

3334
- name: Install dependencies
3435
run: |
36+
composer require "brick/math:${{ matrix.brick }}" --no-update
3537
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest --with-all-dependencies
3638
3739
- name: Execute Unit Tests

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
}
1313
],
1414
"require": {
15-
"php": "^8.1",
16-
"ext-intl": "*"
15+
"php": "^8.2",
16+
"ext-intl": "*",
17+
"brick/math": "^0.9.3|^0.10|^0.11|^0.12|^0.13|^0.14"
1718
},
1819
"require-dev": {
1920
"phpunit/phpunit": "^10",
@@ -44,7 +45,7 @@
4445
},
4546
"extra": {
4647
"branch-alias": {
47-
"dev-main": "1.0-dev"
48+
"dev-main": "2.0-dev"
4849
}
4950
}
5051
}

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
parameters:
2-
level: 8
2+
level: 10
33

44
paths:
55
- src

src/Decimal.php

Lines changed: 59 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,37 @@
44

55
namespace Fruitcake\Decimal;
66

7-
final class Decimal
8-
{
9-
protected string $value;
7+
use Brick\Math\BigDecimal;
8+
use Brick\Math\RoundingMode;
109

11-
protected int $precision = 2;
10+
final readonly class Decimal
11+
{
12+
private BigDecimal $bigDecimal;
13+
private int $precision;
1214

13-
public function __construct(mixed $value, int $precision = 2)
15+
public function __construct(self|BigDecimal|int|float|string $value, int $precision = 2)
1416
{
1517
$this->precision = $precision;
16-
$this->setUnitValue($value * $this->getMultiplier());
18+
19+
if ($value instanceof self) {
20+
$this->bigDecimal = clone $value->getInternalDecimal();
21+
} elseif ($value instanceof BigDecimal) {
22+
$this->bigDecimal = clone $value;
23+
} else {
24+
$this->bigDecimal = BigDecimal::of((string) $value)->toScale($precision, RoundingMode::HALF_UP);
25+
}
1726
}
1827

19-
public static function fromUnitValue(mixed $value, int $precision): self
28+
public static function fromUnitValue(int|float|string $value, int $precision): self
2029
{
21-
$decimal = new Decimal(0.0, $precision);
22-
$decimal->setUnitValue($value);
23-
24-
return $decimal;
30+
return new self(BigDecimal::ofUnscaledValue($value, $precision));
2531
}
2632

2733
/**
2834
* Parse the input from user input, with different comma/dot
2935
*
3036
*/
31-
public static function parseLocale(mixed $value, int $precision = 2): self
37+
public static function parseLocale(int|float|string $value, int $precision = 2): self
3238
{
3339
if (!is_string($value)) {
3440
$value = (string) $value;
@@ -65,166 +71,113 @@ public static function parseLocale(mixed $value, int $precision = 2): self
6571

6672
public function isZero(): bool
6773
{
68-
return $this->getUnitValue() === '0';
74+
return $this->bigDecimal->isZero();
6975
}
7076

7177
public function isPositive(): bool
7278
{
73-
return $this->getUnitValue() > 0;
79+
return $this->bigDecimal->isPositive();
7480
}
7581

7682
public function isNegative(): bool
7783
{
78-
return $this->getUnitValue() < 0;
84+
return $this->bigDecimal->isNegative();
7985
}
8086

8187
public function isZeroOrPositive(): bool
8288
{
83-
return $this->isZero() || $this->isPositive();
89+
return $this->bigDecimal->isPositiveOrZero();
8490
}
8591

8692
public function isZeroOrNegative(): bool
8793
{
88-
return $this->isZero() || $this->isNegative();
94+
return $this->bigDecimal->isNegativeOrZero();
8995
}
9096

9197
public function getUnitValue(): mixed
9298
{
93-
return $this->value;
99+
return $this->bigDecimal->getUnscaledValue();
94100
}
95101

96-
public function equals(mixed $value): bool
102+
public function equals(self|int|float|string $value): bool
97103
{
98-
if (!($value instanceof Decimal)) {
99-
$value = new Decimal($value, $this->getPrecision());
100-
}
101-
102-
$this->comparePrecision($value);
103-
104-
return $this->getUnitValue() === $value->getUnitValue();
104+
return $this->bigDecimal->isEqualTo($this->prepareValue($value));
105105
}
106106

107-
public function isBiggerThan(mixed $value): bool
107+
public function isBiggerThan(self|int|float|string $value): bool
108108
{
109-
if (!($value instanceof Decimal)) {
110-
$value = new Decimal($value, $this->getPrecision());
111-
}
112-
113-
$this->comparePrecision($value);
114-
115-
return $this->getUnitValue() > $value->getUnitValue();
109+
return $this->bigDecimal->isGreaterThan($this->prepareValue($value));
116110
}
117111

118-
public function isBiggerOrEqualThan(mixed $value): bool
112+
public function isBiggerOrEqualThan(self|int|float|string $value): bool
119113
{
120-
if (!($value instanceof Decimal)) {
121-
$value = new Decimal($value, $this->getPrecision());
122-
}
123-
124-
$this->comparePrecision($value);
125-
126-
return $this->getUnitValue() >= $value->getUnitValue();
114+
return $this->bigDecimal->isGreaterThanOrEqualTo($this->prepareValue($value));
127115
}
128116

129-
public function isSmallerThan(mixed $value): bool
117+
public function isSmallerThan(self|int|float|string $value): bool
130118
{
131-
if (!($value instanceof Decimal)) {
132-
$value = new Decimal($value, $this->getPrecision());
133-
}
134-
135-
$this->comparePrecision($value);
136-
137-
return $this->getUnitValue() < $value->getUnitValue();
119+
return $this->bigDecimal->isLessThan($this->prepareValue($value));
138120
}
139121

140-
public function isSmallerOrEqualThan(mixed $value): bool
122+
public function isSmallerOrEqualThan(self|int|float|string $value): bool
141123
{
142-
if (!($value instanceof Decimal)) {
143-
$value = new Decimal($value, $this->getPrecision());
144-
}
145-
146-
$this->comparePrecision($value);
147-
148-
return $this->getUnitValue() <= $value->getUnitValue();
124+
return $this->bigDecimal->isLessThanOrEqualTo($this->prepareValue($value));
149125
}
150126

151-
public function notEquals(mixed $value): bool
127+
public function notEquals(self|int|float|string $value): bool
152128
{
153-
if (!($value instanceof Decimal)) {
154-
$value = new Decimal($value, $this->getPrecision());
155-
}
156-
157-
$this->comparePrecision($value);
158-
159-
return $this->getUnitValue() !== $value->getUnitValue();
129+
return !$this->bigDecimal->isEqualTo($this->prepareValue($value));
160130
}
161131

162-
public function add(mixed $value): self
132+
public function add(self|int|float|string $value): self
163133
{
164-
if (!($value instanceof Decimal)) {
165-
$value = new Decimal($value, $this->getPrecision());
166-
}
167-
168-
$this->comparePrecision($value);
169-
170-
return Decimal::fromUnitValue($this->getUnitValue() + $value->getUnitValue(), $this->getPrecision());
134+
return new self($this->bigDecimal->plus($this->prepareValue($value)), $this->precision);
171135
}
172136

173-
public function sub(mixed $value): self
137+
public function sub(self|int|float|string $value): self
174138
{
175-
if (!($value instanceof Decimal)) {
176-
$value = new Decimal($value, $this->getPrecision());
177-
}
178-
179-
$this->comparePrecision($value);
180-
181-
return Decimal::fromUnitValue($this->getUnitValue() - $value->getUnitValue(), $this->getPrecision());
139+
return new self($this->bigDecimal->minus($this->prepareValue($value)), $this->precision);
182140
}
183141

184-
public function multiply(mixed $multiplier): self
142+
public function multiply(self|int|float|string $multiplier): self
185143
{
186-
return Decimal::fromUnitValue($this->getUnitValue() * $multiplier, $this->getPrecision());
144+
return new self($this->bigDecimal->multipliedBy($this->prepareValue($multiplier)), $this->precision);
187145
}
188146

189-
public function divide(mixed $division): self
147+
public function divide(self|int|float|string $division): self
190148
{
191-
return Decimal::fromUnitValue($this->getUnitValue() / $division, $this->getPrecision());
149+
$result = $this->bigDecimal->dividedBy($this->prepareValue($division), null, RoundingMode::HALF_UP);
150+
151+
return new self($result, $this->precision);
192152
}
193153

194154
public function toString(?int $precision = null): string
195155
{
196-
return $this->parseAsString($this->getUnitValue() / $this->getMultiplier(), $precision);
156+
$precision = $precision ?? $this->precision;
157+
158+
return (string) $this->bigDecimal->toScale($precision, RoundingMode::HALF_UP);
197159
}
198160

199161
public function __toString()
200162
{
201163
return $this->toString();
202164
}
203165

204-
private function parseAsString(mixed $value, ?int $precision = null): string
166+
private function prepareValue(self|BigDecimal|int|float|string $value): BigDecimal
205167
{
206-
return number_format($value, !is_null($precision) ? $precision : $this->getPrecision(), '.', '');
207-
}
208-
209-
private function getPrecision(): int
210-
{
211-
return $this->precision;
212-
}
168+
if ($value instanceof BigDecimal) {
169+
return $value;
170+
}
213171

214-
private function getMultiplier(): int
215-
{
216-
return 10 ** $this->getPrecision();
217-
}
172+
if ($value instanceof self) {
173+
return $value->getInternalDecimal();
174+
}
218175

219-
private function setUnitValue(mixed $value): void
220-
{
221-
$this->value = number_format($value, 0, '', '');
176+
return (new self($value))->getInternalDecimal();
222177
}
223178

224-
private function comparePrecision(self $other): void
179+
private function getInternalDecimal(): BigDecimal
225180
{
226-
if ($other->getPrecision() !== $this->getPrecision()) {
227-
throw new \RuntimeException('Precision must match');
228-
}
181+
return $this->bigDecimal;
229182
}
230183
}

src/helpers.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
declare(strict_types=1);
44

5+
use Brick\Math\BigDecimal;
56
use Fruitcake\Decimal\Decimal;
67

7-
function decimal(mixed $value, int $precision = 2): Decimal
8+
function decimal(Decimal|int|float|string $value, int $precision = 2): Decimal
89
{
910
return new Decimal($value, $precision);
1011
}
1112

12-
function decimal_parse_locale(mixed $value, int $precision = 2): Decimal
13+
function decimal_parse_locale(int|float|string $value, int $precision = 2): Decimal
1314
{
1415
return Decimal::parseLocale($value, $precision);
1516
}

tests/DecimalTest.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public static function provideEquals()
316316
return [
317317
[1, '1'],
318318
['1.00', '1'],
319-
[0.111, 0.112, 2, 2],
319+
[0.111, 0.112, 2],
320320
];
321321
}
322322

@@ -354,6 +354,9 @@ public static function provideDivide()
354354
[0.1, 2, '0.05'],
355355
['2', '2', '1.00'],
356356
['2.0', '2', '1', 0],
357+
['2.0', '20', '0', 0],
358+
['2.0', '20', '0.1', 1],
359+
['2.0', '200', '0.0', 1],
357360
];
358361
}
359362

@@ -398,8 +401,6 @@ public function testChaining()
398401

399402
public function testPreservesInternalPrecision()
400403
{
401-
$this->markTestSkipped('Higher internal precision is currently not preserved');
402-
403404
$a = decimal('2.30')->multiply('0.75')->toString();
404405
$this->assertEquals('1.73', $a);
405406

@@ -413,16 +414,6 @@ public function testDecimalIsNotEqual()
413414
$this->assertTrue(decimal(3)->notEquals(5));
414415
}
415416

416-
public function testComparePrecision()
417-
{
418-
$this->expectException(RuntimeException::class);
419-
420-
$a = decimal(3.00, 3);
421-
$b = decimal(5.00, 5);
422-
423-
$a->equals($b);
424-
}
425-
426417
public function testIsBiggerThan()
427418
{
428419
$a = 3.52;

0 commit comments

Comments
 (0)