|
4 | 4 |
|
5 | 5 | namespace Fruitcake\Decimal; |
6 | 6 |
|
7 | | -final class Decimal |
8 | | -{ |
9 | | - protected string $value; |
| 7 | +use Brick\Math\BigDecimal; |
| 8 | +use Brick\Math\RoundingMode; |
10 | 9 |
|
11 | | - protected int $precision = 2; |
| 10 | +final readonly class Decimal |
| 11 | +{ |
| 12 | + private BigDecimal $bigDecimal; |
| 13 | + private int $precision; |
12 | 14 |
|
13 | | - public function __construct(mixed $value, int $precision = 2) |
| 15 | + public function __construct(self|BigDecimal|int|float|string $value, int $precision = 2) |
14 | 16 | { |
15 | 17 | $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 | + } |
17 | 26 | } |
18 | 27 |
|
19 | | - public static function fromUnitValue(mixed $value, int $precision): self |
| 28 | + public static function fromUnitValue(int|float|string $value, int $precision): self |
20 | 29 | { |
21 | | - $decimal = new Decimal(0.0, $precision); |
22 | | - $decimal->setUnitValue($value); |
23 | | - |
24 | | - return $decimal; |
| 30 | + return new self(BigDecimal::ofUnscaledValue($value, $precision)); |
25 | 31 | } |
26 | 32 |
|
27 | 33 | /** |
28 | 34 | * Parse the input from user input, with different comma/dot |
29 | 35 | * |
30 | 36 | */ |
31 | | - public static function parseLocale(mixed $value, int $precision = 2): self |
| 37 | + public static function parseLocale(int|float|string $value, int $precision = 2): self |
32 | 38 | { |
33 | 39 | if (!is_string($value)) { |
34 | 40 | $value = (string) $value; |
@@ -65,166 +71,113 @@ public static function parseLocale(mixed $value, int $precision = 2): self |
65 | 71 |
|
66 | 72 | public function isZero(): bool |
67 | 73 | { |
68 | | - return $this->getUnitValue() === '0'; |
| 74 | + return $this->bigDecimal->isZero(); |
69 | 75 | } |
70 | 76 |
|
71 | 77 | public function isPositive(): bool |
72 | 78 | { |
73 | | - return $this->getUnitValue() > 0; |
| 79 | + return $this->bigDecimal->isPositive(); |
74 | 80 | } |
75 | 81 |
|
76 | 82 | public function isNegative(): bool |
77 | 83 | { |
78 | | - return $this->getUnitValue() < 0; |
| 84 | + return $this->bigDecimal->isNegative(); |
79 | 85 | } |
80 | 86 |
|
81 | 87 | public function isZeroOrPositive(): bool |
82 | 88 | { |
83 | | - return $this->isZero() || $this->isPositive(); |
| 89 | + return $this->bigDecimal->isPositiveOrZero(); |
84 | 90 | } |
85 | 91 |
|
86 | 92 | public function isZeroOrNegative(): bool |
87 | 93 | { |
88 | | - return $this->isZero() || $this->isNegative(); |
| 94 | + return $this->bigDecimal->isNegativeOrZero(); |
89 | 95 | } |
90 | 96 |
|
91 | 97 | public function getUnitValue(): mixed |
92 | 98 | { |
93 | | - return $this->value; |
| 99 | + return $this->bigDecimal->getUnscaledValue(); |
94 | 100 | } |
95 | 101 |
|
96 | | - public function equals(mixed $value): bool |
| 102 | + public function equals(self|int|float|string $value): bool |
97 | 103 | { |
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)); |
105 | 105 | } |
106 | 106 |
|
107 | | - public function isBiggerThan(mixed $value): bool |
| 107 | + public function isBiggerThan(self|int|float|string $value): bool |
108 | 108 | { |
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)); |
116 | 110 | } |
117 | 111 |
|
118 | | - public function isBiggerOrEqualThan(mixed $value): bool |
| 112 | + public function isBiggerOrEqualThan(self|int|float|string $value): bool |
119 | 113 | { |
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)); |
127 | 115 | } |
128 | 116 |
|
129 | | - public function isSmallerThan(mixed $value): bool |
| 117 | + public function isSmallerThan(self|int|float|string $value): bool |
130 | 118 | { |
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)); |
138 | 120 | } |
139 | 121 |
|
140 | | - public function isSmallerOrEqualThan(mixed $value): bool |
| 122 | + public function isSmallerOrEqualThan(self|int|float|string $value): bool |
141 | 123 | { |
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)); |
149 | 125 | } |
150 | 126 |
|
151 | | - public function notEquals(mixed $value): bool |
| 127 | + public function notEquals(self|int|float|string $value): bool |
152 | 128 | { |
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)); |
160 | 130 | } |
161 | 131 |
|
162 | | - public function add(mixed $value): self |
| 132 | + public function add(self|int|float|string $value): self |
163 | 133 | { |
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); |
171 | 135 | } |
172 | 136 |
|
173 | | - public function sub(mixed $value): self |
| 137 | + public function sub(self|int|float|string $value): self |
174 | 138 | { |
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); |
182 | 140 | } |
183 | 141 |
|
184 | | - public function multiply(mixed $multiplier): self |
| 142 | + public function multiply(self|int|float|string $multiplier): self |
185 | 143 | { |
186 | | - return Decimal::fromUnitValue($this->getUnitValue() * $multiplier, $this->getPrecision()); |
| 144 | + return new self($this->bigDecimal->multipliedBy($this->prepareValue($multiplier)), $this->precision); |
187 | 145 | } |
188 | 146 |
|
189 | | - public function divide(mixed $division): self |
| 147 | + public function divide(self|int|float|string $division): self |
190 | 148 | { |
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); |
192 | 152 | } |
193 | 153 |
|
194 | 154 | public function toString(?int $precision = null): string |
195 | 155 | { |
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); |
197 | 159 | } |
198 | 160 |
|
199 | 161 | public function __toString() |
200 | 162 | { |
201 | 163 | return $this->toString(); |
202 | 164 | } |
203 | 165 |
|
204 | | - private function parseAsString(mixed $value, ?int $precision = null): string |
| 166 | + private function prepareValue(self|BigDecimal|int|float|string $value): BigDecimal |
205 | 167 | { |
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 | + } |
213 | 171 |
|
214 | | - private function getMultiplier(): int |
215 | | - { |
216 | | - return 10 ** $this->getPrecision(); |
217 | | - } |
| 172 | + if ($value instanceof self) { |
| 173 | + return $value->getInternalDecimal(); |
| 174 | + } |
218 | 175 |
|
219 | | - private function setUnitValue(mixed $value): void |
220 | | - { |
221 | | - $this->value = number_format($value, 0, '', ''); |
| 176 | + return (new self($value))->getInternalDecimal(); |
222 | 177 | } |
223 | 178 |
|
224 | | - private function comparePrecision(self $other): void |
| 179 | + private function getInternalDecimal(): BigDecimal |
225 | 180 | { |
226 | | - if ($other->getPrecision() !== $this->getPrecision()) { |
227 | | - throw new \RuntimeException('Precision must match'); |
228 | | - } |
| 181 | + return $this->bigDecimal; |
229 | 182 | } |
230 | 183 | } |
0 commit comments