Completed
Push — master ( 426134...338ce0 )
by Fabrice
06:11
created

Math::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of OpinHelpers.
5
 *     (c) Fabrice de Stefanis / https://github.com/fab2s/OpinHelpers
6
 * This source file is licensed under the MIT license which you will
7
 * find in the LICENSE file or at https://opensource.org/licenses/MIT
8
 */
9
10
namespace fab2s\OpinHelpers;
11
12
use fab2s\Math\OpinHelpers\MathAbstract;
13
14
/**
15
 * Class Math
16
 */
17
class Math extends MathAbstract
18
{
19
    /**
20
     * @param string[] $numbers
21
     *
22
     * @throws \InvalidArgumentException
23
     *
24
     * @return $this
25
     */
26
    public function add(...$numbers)
27
    {
28
        foreach ($numbers as $number) {
29
            $this->number = bcadd($this->number, static::validateInputNumber($number), $this->precision);
30
        }
31
32
        return $this;
33
    }
34
35
    /**
36
     * @param string[] $numbers
37
     *
38
     * @throws \InvalidArgumentException
39
     *
40
     * @return $this
41
     */
42
    public function sub(...$numbers)
43
    {
44
        foreach ($numbers as $number) {
45
            $this->number = bcsub($this->number, static::validateInputNumber($number), $this->precision);
46
        }
47
48
        return $this;
49
    }
50
51
    /**
52
     * @param string[] $numbers
53
     *
54
     * @throws \InvalidArgumentException
55
     *
56
     * @return $this
57
     */
58
    public function mul(...$numbers)
59
    {
60
        foreach ($numbers as $number) {
61
            $this->number = bcmul($this->number, static::validateInputNumber($number), $this->precision);
62
        }
63
64
        return $this;
65
    }
66
67
    /**
68
     * @param string[] $numbers
69
     *
70
     * @throws \InvalidArgumentException
71
     *
72
     * @return $this
73
     */
74
    public function div(...$numbers)
75
    {
76
        foreach ($numbers as $number) {
77
            $this->number = bcdiv($this->number, static::validateInputNumber($number), $this->precision);
78
        }
79
80
        return $this;
81
    }
82
83
    /**
84
     * @return $this
85
     */
86
    public function sqrt()
87
    {
88
        $this->number = bcsqrt($this->number, $this->precision);
89
90
        return $this;
91
    }
92
93
    /**
94
     * @param string $exponent
95
     *
96
     * @throws \InvalidArgumentException
97
     *
98
     * @return $this
99
     */
100
    public function pow($exponent)
101
    {
102
        $this->number = bcpow($this->number, static::validatePositiveInteger($exponent), $this->precision);
103
104
        return $this;
105
    }
106
107
    /**
108
     * @param string $modulus
109
     *
110
     * @throws \InvalidArgumentException
111
     *
112
     * @return $this
113
     */
114
    public function mod($modulus)
115
    {
116
        $this->number = bcmod($this->number, static::validatePositiveInteger($modulus));
117
118
        return $this;
119
    }
120
121
    /**
122
     * @param string $exponent
123
     * @param string $modulus
124
     *
125
     * @throws \InvalidArgumentException
126
     *
127
     * @return $this
128
     */
129
    public function powMod($exponent, $modulus)
130
    {
131
        $this->number = bcpowmod($this->number, static::validatePositiveInteger($exponent), static::validatePositiveInteger($modulus));
132
133
        return $this;
134
    }
135
136
    /**
137
     * @param int $precision
138
     *
139
     * @return $this
140
     */
141
    public function round($precision = 0)
142
    {
143
        $precision = max(0, (int) $precision);
144
        if ($this->hasDecimals()) {
145
            if ($this->isPositive()) {
146
                $this->number = bcadd($this->number, '0.' . str_repeat('0', $precision) . '5', $precision);
147
148
                return $this;
149
            }
150
151
            $this->number = bcsub($this->number, '0.' . str_repeat('0', $precision) . '5', $precision);
152
        }
153
154
        return $this;
155
    }
156
157
    /**
158
     * @param int    $decimals
159
     * @param string $decPoint
160
     * @param string $thousandsSep
161
     *
162
     * @return string
163
     */
164
    public function format($decimals = 0, $decPoint = '.', $thousandsSep = ' ')
165
    {
166
        $decimals = max(0, (int) $decimals);
167
        $dec      = '';
168
        // do not mutate
169
        $number   = (new static($this))->round($decimals)->normalize();
170
        $sign     = $number->isPositive() ? '' : '-';
171
        if ($number->abs()->hasDecimals()) {
172
            list($number, $dec) = explode('.', (string) $number);
173
        }
174
175
        if ($decimals) {
176
            $dec = sprintf("%'0-" . $decimals . 's', $dec);
177
        }
178
179
        return $sign . preg_replace("/(?<=\d)(?=(\d{3})+(?!\d))/", $thousandsSep, $number) . ($decimals ? $decPoint . $dec : '');
180
    }
181
182
    /**
183
     * @return $this
184
     */
185
    public function ceil()
186
    {
187
        if ($this->hasDecimals()) {
188
            if ($this->isPositive()) {
189
                $this->number = bcadd($this->number, (preg_match('`\.[0]*$`', $this->number) ? '0' : '1'), 0);
190
191
                return $this;
192
            }
193
194
            $this->number = bcsub($this->number, '0', 0);
195
        }
196
197
        return $this;
198
    }
199
200
    /**
201
     * @return $this
202
     */
203
    public function floor()
204
    {
205
        if ($this->hasDecimals()) {
206
            if ($this->isPositive()) {
207
                $this->number = bcadd($this->number, 0, 0);
208
209
                return $this;
210
            }
211
212
            $this->number = bcsub($this->number, (preg_match('`\.[0]*$`', $this->number) ? '0' : '1'), 0);
213
        }
214
215
        return $this;
216
    }
217
218
    /**
219
     * @return $this
220
     */
221
    public function abs()
222
    {
223
        $this->number = ltrim($this->number, '-');
224
225
        return $this;
226
    }
227
228
    /**
229
     * @param string $number
230
     *
231
     * @throws \InvalidArgumentException
232
     *
233
     * @return bool
234
     */
235
    public function gte($number)
236
    {
237
        return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) >= 0);
238
    }
239
240
    /**
241
     * @param string $number
242
     *
243
     * @throws \InvalidArgumentException
244
     *
245
     * @return bool
246
     */
247
    public function gt($number)
248
    {
249
        return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) === 1);
250
    }
251
252
    /**
253
     * @param string $number
254
     *
255
     * @throws \InvalidArgumentException
256
     *
257
     * @return bool
258
     */
259
    public function lte($number)
260
    {
261
        return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) <= 0);
262
    }
263
264
    /**
265
     * @param string $number
266
     *
267
     * @throws \InvalidArgumentException
268
     *
269
     * @return bool
270
     */
271
    public function lt($number)
272
    {
273
        return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) === -1);
274
    }
275
276
    /**
277
     * @param string $number
278
     *
279
     * @throws \InvalidArgumentException
280
     *
281
     * @return bool
282
     */
283
    public function eq($number)
284
    {
285
        return (bool) (bccomp($this->number, static::validateInputNumber($number), $this->precision) === 0);
286
    }
287
288
    /**
289
     * returns the highest number among all arguments
290
     *
291
     * @param string[] $numbers
292
     *
293
     * @throws \InvalidArgumentException
294
     *
295
     * @return $this
296
     */
297
    public function max(...$numbers)
298
    {
299
        foreach ($numbers as $number) {
300
            if (bccomp(static::validateInputNumber($number), $this->number, $this->precision) === 1) {
301
                $this->number = $number;
302
            }
303
        }
304
305
        return $this;
306
    }
307
308
    /**
309
     * returns the smallest number among all arguments
310
     *
311
     * @param string[] $numbers
312
     *
313
     * @throws \InvalidArgumentException
314
     *
315
     * @return $this
316
     */
317
    public function min(...$numbers)
318
    {
319
        foreach ($numbers as $number) {
320
            if (bccomp(static::validateInputNumber($number), $this->number, $this->precision) === -1) {
321
                $this->number = $number;
322
            }
323
        }
324
325
        return $this;
326
    }
327
328
    /**
329
     * convert decimal value to any other base bellow or equals to 64
330
     *
331
     * @param int $base
332
     *
333
     * @throws \InvalidArgumentException
334
     *
335
     * @return string
336
     */
337
    public function toBase($base)
338
    {
339
        if ($this->normalize()->hasDecimals()) {
340
            throw new \InvalidArgumentException('Argument number is not an integer in ' . __METHOD__);
341
        }
342
343
        // do not mutate, only support positive integers
344
        $number = ltrim((string) $this, '-');
345
        if (static::$gmpSupport && $base <= 62) {
346
            return static::baseConvert($number, 10, $base);
347
        }
348
349
        $result   = '';
350
        $baseChar = static::getBaseChar($base);
351
        while (bccomp($number, 0) != 0) { // still data to process
352
            $rem    = bcmod($number, $base); // calc the remainder
353
            $number = bcdiv(bcsub($number, $rem), $base);
354
            $result = $baseChar[$rem] . $result;
355
        }
356
357
        $result = $result ? $result : $baseChar[0];
358
359
        return (string) $result;
360
    }
361
362
    /**
363
     * convert any based value bellow or equals to 64 to its decimal value
364
     *
365
     * @param string $number
366
     * @param int    $base
367
     *
368
     * @throws \InvalidArgumentException
369
     *
370
     * @return static
371
     */
372
    public static function fromBase($number, $base)
373
    {
374
        // cleanup
375
        $number   = trim($number);
376
        $baseChar = static::getBaseChar($base);
377
        // Convert string to lower case since base36 or less is case insensitive
378
        if ($base < 37) {
379
            $number = strtolower($number);
380
        }
381
382
        // clean up the input string if it uses particular input formats
383
        switch ($base) {
384
            case 16:
385
                // remove 0x from start of string
386
                if (substr($number, 0, 2) === '0x') {
387
                    $number = substr($number, 2);
388
                }
389
                break;
390
            case 8:
391
                // remove the 0 from the start if it exists - not really required
392
                if ($number[0] === 0) {
393
                    $number = substr($number, 1);
394
                }
395
                break;
396
            case 2:
397
                // remove an 0b from the start if it exists
398
                if (substr($number, 0, 2) === '0b') {
399
                    $number = substr($number, 2);
400
                }
401
                break;
402
            case 64:
403
                // remove padding chars: =
404
                $number = rtrim($number, '=');
405
                break;
406
        }
407
408
        // only support positive integers
409
        $number = ltrim($number, '-');
410
        if ($number === '' || strpos($number, '.') !== false) {
411
            throw new \InvalidArgumentException('Argument number is not an integer');
412
        }
413
414
        if (trim($number, $baseChar[0]) === '') {
415
            return new static('0');
416
        }
417
418
        if (static::$gmpSupport && $base <= 62) {
419
            return new static(static::baseConvert($number, $base, 10));
420
        }
421
422
        // By now we know we have a correct base and number
423
        $result    = '';
424
        $numberLen = strlen($number);
425
        // Now loop through each digit in the number
426
        for ($i = $numberLen - 1; $i >= 0; --$i) {
427
            $char = $number[$i]; // extract the last char from the number
428
            $ord  = strpos($baseChar, $char); // get the decimal value
429
            if ($ord === false || $ord > $base) {
430
                throw new \InvalidArgumentException('Argument number is invalid');
431
            }
432
433
            // Now convert the value+position to decimal
434
            $result = bcadd($result, bcmul($ord, bcpow($base, ($numberLen - $i - 1))));
435
        }
436
437
        return new static($result ? $result : '0');
438
    }
439
440
    /**
441
     * Convert a from a given base (up to 62) to base 10.
442
     *
443
     * WARNING This method requires ext-gmp
444
     *
445
     * @param string $number
446
     * @param int    $fromBase
447
     * @param int    $toBase
448
     *
449
     * @return string
450
     *
451
     * @internal param int $base
452
     */
453
    public static function baseConvert($number, $fromBase = 10, $toBase = 62)
454
    {
455
        return gmp_strval(gmp_init($number, $fromBase), $toBase);
456
    }
457
}
458
459
// OMG a dynamic static anti pattern ^^
460
Math::gmpSupport();
461