Open Journal Systems  3.3.0
GmpCalculator.php
1 <?php
2 
3 namespace Money\Calculator;
4 
6 use Money\Money;
7 use Money\Number;
8 
12 final class GmpCalculator implements Calculator
13 {
17  private $scale;
18 
22  public function __construct($scale = 14)
23  {
24  $this->scale = $scale;
25  }
26 
30  public static function supported()
31  {
32  return extension_loaded('gmp');
33  }
34 
38  public function compare($a, $b)
39  {
40  $aNum = Number::fromNumber($a);
41  $bNum = Number::fromNumber($b);
42 
43  if ($aNum->isDecimal() || $bNum->isDecimal()) {
44  $integersCompared = gmp_cmp($aNum->getIntegerPart(), $bNum->getIntegerPart());
45  if ($integersCompared !== 0) {
46  return $integersCompared;
47  }
48 
49  $aNumFractional = $aNum->getFractionalPart() === '' ? '0' : $aNum->getFractionalPart();
50  $bNumFractional = $bNum->getFractionalPart() === '' ? '0' : $bNum->getFractionalPart();
51 
52  return gmp_cmp($aNumFractional, $bNumFractional);
53  }
54 
55  return gmp_cmp($a, $b);
56  }
57 
61  public function add($amount, $addend)
62  {
63  return gmp_strval(gmp_add($amount, $addend));
64  }
65 
69  public function subtract($amount, $subtrahend)
70  {
71  return gmp_strval(gmp_sub($amount, $subtrahend));
72  }
73 
77  public function multiply($amount, $multiplier)
78  {
79  $multiplier = Number::fromNumber($multiplier);
80 
81  if ($multiplier->isDecimal()) {
82  $decimalPlaces = strlen($multiplier->getFractionalPart());
83  $multiplierBase = $multiplier->getIntegerPart();
84 
85  if ($multiplierBase) {
86  $multiplierBase .= $multiplier->getFractionalPart();
87  } else {
88  $multiplierBase = ltrim($multiplier->getFractionalPart(), '0');
89  }
90 
91  $resultBase = gmp_strval(gmp_mul(gmp_init($amount), gmp_init($multiplierBase)));
92 
93  if ('0' === $resultBase) {
94  return '0';
95  }
96 
97  $result = substr($resultBase, $decimalPlaces * -1);
98  $resultLength = strlen($result);
99  if ($decimalPlaces > $resultLength) {
100  return '0.'.str_pad('', $decimalPlaces - $resultLength, '0').$result;
101  }
102 
103  return substr($resultBase, 0, $decimalPlaces * -1).'.'.$result;
104  }
105 
106  return gmp_strval(gmp_mul(gmp_init($amount), gmp_init((string) $multiplier)));
107  }
108 
112  public function divide($amount, $divisor)
113  {
114  $divisor = Number::fromNumber($divisor);
115 
116  if ($divisor->isDecimal()) {
117  $decimalPlaces = strlen($divisor->getFractionalPart());
118 
119  if ($divisor->getIntegerPart()) {
120  $divisor = new Number($divisor->getIntegerPart().$divisor->getFractionalPart());
121  } else {
122  $divisor = new Number(ltrim($divisor->getFractionalPart(), '0'));
123  }
124 
125  $amount = gmp_strval(gmp_mul(gmp_init($amount), gmp_init('1'.str_pad('', $decimalPlaces, '0'))));
126  }
127 
128  list($integer, $remainder) = gmp_div_qr(gmp_init($amount), gmp_init((string) $divisor));
129 
130  if (gmp_cmp($remainder, '0') === 0) {
131  return gmp_strval($integer);
132  }
133 
134  $divisionOfRemainder = gmp_strval(
135  gmp_div_q(
136  gmp_mul($remainder, gmp_init('1'.str_pad('', $this->scale, '0'))),
137  gmp_init((string) $divisor),
138  GMP_ROUND_MINUSINF
139  )
140  );
141 
142  if ($divisionOfRemainder[0] === '-') {
143  $divisionOfRemainder = substr($divisionOfRemainder, 1);
144  }
145 
146  return gmp_strval($integer).'.'.str_pad($divisionOfRemainder, $this->scale, '0', STR_PAD_LEFT);
147  }
148 
152  public function ceil($number)
153  {
154  $number = Number::fromNumber($number);
155 
156  if ($number->isInteger()) {
157  return (string) $number;
158  }
159 
160  if ($number->isNegative()) {
161  return $this->add($number->getIntegerPart(), '0');
162  }
163 
164  return $this->add($number->getIntegerPart(), '1');
165  }
166 
170  public function floor($number)
171  {
172  $number = Number::fromNumber($number);
173 
174  if ($number->isInteger()) {
175  return (string) $number;
176  }
177 
178  if ($number->isNegative()) {
179  return $this->add($number->getIntegerPart(), '-1');
180  }
181 
182  return $this->add($number->getIntegerPart(), '0');
183  }
184 
188  public function absolute($number)
189  {
190  return ltrim($number, '-');
191  }
192 
196  public function round($number, $roundingMode)
197  {
198  $number = Number::fromNumber($number);
199 
200  if ($number->isInteger()) {
201  return (string) $number;
202  }
203 
204  if ($number->isHalf() === false) {
205  return $this->roundDigit($number);
206  }
207 
208  if (Money::ROUND_HALF_UP === $roundingMode) {
209  return $this->add(
210  $number->getIntegerPart(),
211  $number->getIntegerRoundingMultiplier()
212  );
213  }
214 
215  if (Money::ROUND_HALF_DOWN === $roundingMode) {
216  return $this->add($number->getIntegerPart(), '0');
217  }
218 
219  if (Money::ROUND_HALF_EVEN === $roundingMode) {
220  if ($number->isCurrentEven()) {
221  return $this->add($number->getIntegerPart(), '0');
222  }
223 
224  return $this->add(
225  $number->getIntegerPart(),
226  $number->getIntegerRoundingMultiplier()
227  );
228  }
229 
230  if (Money::ROUND_HALF_ODD === $roundingMode) {
231  if ($number->isCurrentEven()) {
232  return $this->add(
233  $number->getIntegerPart(),
234  $number->getIntegerRoundingMultiplier()
235  );
236  }
237 
238  return $this->add($number->getIntegerPart(), '0');
239  }
240 
241  if (Money::ROUND_HALF_POSITIVE_INFINITY === $roundingMode) {
242  if ($number->isNegative()) {
243  return $this->add(
244  $number->getIntegerPart(),
245  '0'
246  );
247  }
248 
249  return $this->add(
250  $number->getIntegerPart(),
251  $number->getIntegerRoundingMultiplier()
252  );
253  }
254 
255  if (Money::ROUND_HALF_NEGATIVE_INFINITY === $roundingMode) {
256  if ($number->isNegative()) {
257  return $this->add(
258  $number->getIntegerPart(),
259  $number->getIntegerRoundingMultiplier()
260  );
261  }
262 
263  return $this->add(
264  $number->getIntegerPart(),
265  '0'
266  );
267  }
268 
269  throw new \InvalidArgumentException('Unknown rounding mode');
270  }
271 
277  private function roundDigit(Number $number)
278  {
279  if ($number->isCloserToNext()) {
280  return $this->add(
281  $number->getIntegerPart(),
282  $number->getIntegerRoundingMultiplier()
283  );
284  }
285 
286  return $this->add($number->getIntegerPart(), '0');
287  }
288 
292  public function share($amount, $ratio, $total)
293  {
294  return $this->floor($this->divide($this->multiply($amount, $ratio), $total));
295  }
296 
300  public function mod($amount, $divisor)
301  {
302  // gmp_mod() only calculates non-negative integers, so we use absolutes
303  $remainder = gmp_mod($this->absolute($amount), $this->absolute($divisor));
304 
305  // If the amount was negative, we negate the result of the modulus operation
306  $amount = Number::fromNumber($amount);
307 
308  if ($amount->isNegative()) {
309  $remainder = gmp_neg($remainder);
310  }
311 
312  return gmp_strval($remainder);
313  }
314 
318  public function it_divides_bug538()
319  {
320  $this->assertSame('-4.54545454545455', $this->getCalculator()->divide('-500', 110));
321  }
322 }
Money\Money\ROUND_HALF_DOWN
const ROUND_HALF_DOWN
Definition: Money.php:22
Money\Calculator\GmpCalculator\subtract
subtract($amount, $subtrahend)
Definition: GmpCalculator.php:72
Money\Calculator\GmpCalculator\share
share($amount, $ratio, $total)
Definition: GmpCalculator.php:295
Money\Money
Definition: Money.php:16
Money\Number\getIntegerPart
getIntegerPart()
Definition: paymethod/paypal/vendor/moneyphp/money/src/Number.php:170
Money\Calculator\GmpCalculator\supported
static supported()
Definition: GmpCalculator.php:33
Money\Number\getIntegerRoundingMultiplier
getIntegerRoundingMultiplier()
Definition: paymethod/paypal/vendor/moneyphp/money/src/Number.php:186
Money\Calculator\GmpCalculator\divide
divide($amount, $divisor)
Definition: GmpCalculator.php:115
Money\Money\ROUND_HALF_NEGATIVE_INFINITY
const ROUND_HALF_NEGATIVE_INFINITY
Definition: Money.php:34
Money\Calculator\GmpCalculator
Definition: GmpCalculator.php:12
Money\Calculator\GmpCalculator\it_divides_bug538
it_divides_bug538()
Definition: GmpCalculator.php:321
Money\Money\ROUND_HALF_ODD
const ROUND_HALF_ODD
Definition: Money.php:26
Money\Calculator
Definition: BcMathCalculator.php:3
Money\Calculator
Definition: Calculator.php:10
Money\Calculator\GmpCalculator\round
round($number, $roundingMode)
Definition: GmpCalculator.php:199
Money\Number
Definition: paymethod/paypal/vendor/moneyphp/money/src/Number.php:10
Money\Number\fromNumber
static fromNumber($number)
Definition: paymethod/paypal/vendor/moneyphp/money/src/Number.php:84
Money\Calculator\GmpCalculator\floor
floor($number)
Definition: GmpCalculator.php:173
Money\Calculator\GmpCalculator\multiply
multiply($amount, $multiplier)
Definition: GmpCalculator.php:80
Money\Money\ROUND_HALF_EVEN
const ROUND_HALF_EVEN
Definition: Money.php:24
Money\Number\isCloserToNext
isCloserToNext()
Definition: paymethod/paypal/vendor/moneyphp/money/src/Number.php:138
Money\Calculator\GmpCalculator\absolute
absolute($number)
Definition: GmpCalculator.php:191
Money\Money\ROUND_HALF_POSITIVE_INFINITY
const ROUND_HALF_POSITIVE_INFINITY
Definition: Money.php:32
Money\Calculator\GmpCalculator\mod
mod($amount, $divisor)
Definition: GmpCalculator.php:303
Money\Money\ROUND_HALF_UP
const ROUND_HALF_UP
Definition: Money.php:20
Money\Calculator\GmpCalculator\__construct
__construct($scale=14)
Definition: GmpCalculator.php:25
Money\Calculator\GmpCalculator\ceil
ceil($number)
Definition: GmpCalculator.php:155
Money\Calculator\GmpCalculator\compare
compare($a, $b)
Definition: GmpCalculator.php:41
Money\Calculator\GmpCalculator\add
add($amount, $addend)
Definition: GmpCalculator.php:64