Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace Brick\Math\Internal\Calculator;
6
 
7
use Brick\Math\Internal\Calculator;
8
 
9
/**
10
 * Calculator implementation using only native PHP code.
11
 *
12
 * @internal
13
 *
14
 * @psalm-immutable
15
 */
16
class NativeCalculator extends Calculator
17
{
18
    /**
19
     * The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
20
     * For multiplication, this represents the max sum of the lengths of both operands.
21
     *
22
     * For addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
23
     * Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
24
     *          64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
25
     */
26
    private int $maxDigits;
27
 
28
    /**
29
     * Class constructor.
30
     *
31
     * @codeCoverageIgnore
32
     */
33
    public function __construct()
34
    {
35
        switch (PHP_INT_SIZE) {
36
            case 4:
37
                $this->maxDigits = 9;
38
                break;
39
 
40
            case 8:
41
                $this->maxDigits = 18;
42
                break;
43
 
44
            default:
45
                throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.');
46
        }
47
    }
48
 
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function add(string $a, string $b) : string
53
    {
54
        /**
55
         * @psalm-var numeric-string $a
56
         * @psalm-var numeric-string $b
57
         */
58
        $result = $a + $b;
59
 
60
        if (is_int($result)) {
61
            return (string) $result;
62
        }
63
 
64
        if ($a === '0') {
65
            return $b;
66
        }
67
 
68
        if ($b === '0') {
69
            return $a;
70
        }
71
 
72
        [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
73
 
74
        $result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
75
 
76
        if ($aNeg) {
77
            $result = $this->neg($result);
78
        }
79
 
80
        return $result;
81
    }
82
 
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function sub(string $a, string $b) : string
87
    {
88
        return $this->add($a, $this->neg($b));
89
    }
90
 
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function mul(string $a, string $b) : string
95
    {
96
        /**
97
         * @psalm-var numeric-string $a
98
         * @psalm-var numeric-string $b
99
         */
100
        $result = $a * $b;
101
 
102
        if (is_int($result)) {
103
            return (string) $result;
104
        }
105
 
106
        if ($a === '0' || $b === '0') {
107
            return '0';
108
        }
109
 
110
        if ($a === '1') {
111
            return $b;
112
        }
113
 
114
        if ($b === '1') {
115
            return $a;
116
        }
117
 
118
        if ($a === '-1') {
119
            return $this->neg($b);
120
        }
121
 
122
        if ($b === '-1') {
123
            return $this->neg($a);
124
        }
125
 
126
        [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
127
 
128
        $result = $this->doMul($aDig, $bDig);
129
 
130
        if ($aNeg !== $bNeg) {
131
            $result = $this->neg($result);
132
        }
133
 
134
        return $result;
135
    }
136
 
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function divQ(string $a, string $b) : string
141
    {
142
        return $this->divQR($a, $b)[0];
143
    }
144
 
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function divR(string $a, string $b): string
149
    {
150
        return $this->divQR($a, $b)[1];
151
    }
152
 
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function divQR(string $a, string $b) : array
157
    {
158
        if ($a === '0') {
159
            return ['0', '0'];
160
        }
161
 
162
        if ($a === $b) {
163
            return ['1', '0'];
164
        }
165
 
166
        if ($b === '1') {
167
            return [$a, '0'];
168
        }
169
 
170
        if ($b === '-1') {
171
            return [$this->neg($a), '0'];
172
        }
173
 
174
        /** @psalm-var numeric-string $a */
175
        $na = $a * 1; // cast to number
176
 
177
        if (is_int($na)) {
178
            /** @psalm-var numeric-string $b */
179
            $nb = $b * 1;
180
 
181
            if (is_int($nb)) {
182
                // the only division that may overflow is PHP_INT_MIN / -1,
183
                // which cannot happen here as we've already handled a divisor of -1 above.
184
                $r = $na % $nb;
185
                $q = ($na - $r) / $nb;
186
 
187
                assert(is_int($q));
188
 
189
                return [
190
                    (string) $q,
191
                    (string) $r
192
                ];
193
            }
194
        }
195
 
196
        [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
197
 
198
        [$q, $r] = $this->doDiv($aDig, $bDig);
199
 
200
        if ($aNeg !== $bNeg) {
201
            $q = $this->neg($q);
202
        }
203
 
204
        if ($aNeg) {
205
            $r = $this->neg($r);
206
        }
207
 
208
        return [$q, $r];
209
    }
210
 
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function pow(string $a, int $e) : string
215
    {
216
        if ($e === 0) {
217
            return '1';
218
        }
219
 
220
        if ($e === 1) {
221
            return $a;
222
        }
223
 
224
        $odd = $e % 2;
225
        $e -= $odd;
226
 
227
        $aa = $this->mul($a, $a);
228
 
229
        /** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
230
        $result = $this->pow($aa, $e / 2);
231
 
232
        if ($odd === 1) {
233
            $result = $this->mul($result, $a);
234
        }
235
 
236
        return $result;
237
    }
238
 
239
    /**
240
     * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
241
     *
242
     * {@inheritdoc}
243
     */
244
    public function modPow(string $base, string $exp, string $mod) : string
245
    {
246
        // special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
247
        if ($base === '0' && $exp === '0' && $mod === '1') {
248
            return '0';
249
        }
250
 
251
        // special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
252
        if ($exp === '0' && $mod === '1') {
253
            return '0';
254
        }
255
 
256
        $x = $base;
257
 
258
        $res = '1';
259
 
260
        // numbers are positive, so we can use remainder instead of modulo
261
        $x = $this->divR($x, $mod);
262
 
263
        while ($exp !== '0') {
264
            if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd
265
                $res = $this->divR($this->mul($res, $x), $mod);
266
            }
267
 
268
            $exp = $this->divQ($exp, '2');
269
            $x = $this->divR($this->mul($x, $x), $mod);
270
        }
271
 
272
        return $res;
273
    }
274
 
275
    /**
276
     * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
277
     *
278
     * {@inheritDoc}
279
     */
280
    public function sqrt(string $n) : string
281
    {
282
        if ($n === '0') {
283
            return '0';
284
        }
285
 
286
        // initial approximation
287
        $x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
288
 
289
        $decreased = false;
290
 
291
        for (;;) {
292
            $nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
293
 
294
            if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
295
                break;
296
            }
297
 
298
            $decreased = $this->cmp($nx, $x) < 0;
299
            $x = $nx;
300
        }
301
 
302
        return $x;
303
    }
304
 
305
    /**
306
     * Performs the addition of two non-signed large integers.
307
     *
308
     * @param string $a The first operand.
309
     * @param string $b The second operand.
310
     *
311
     * @return string
312
     */
313
    private function doAdd(string $a, string $b) : string
314
    {
315
        [$a, $b, $length] = $this->pad($a, $b);
316
 
317
        $carry = 0;
318
        $result = '';
319
 
320
        for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
321
            $blockLength = $this->maxDigits;
322
 
323
            if ($i < 0) {
324
                $blockLength += $i;
325
                /** @psalm-suppress LoopInvalidation */
326
                $i = 0;
327
            }
328
 
329
            /** @psalm-var numeric-string $blockA */
330
            $blockA = \substr($a, $i, $blockLength);
331
 
332
            /** @psalm-var numeric-string $blockB */
333
            $blockB = \substr($b, $i, $blockLength);
334
 
335
            $sum = (string) ($blockA + $blockB + $carry);
336
            $sumLength = \strlen($sum);
337
 
338
            if ($sumLength > $blockLength) {
339
                $sum = \substr($sum, 1);
340
                $carry = 1;
341
            } else {
342
                if ($sumLength < $blockLength) {
343
                    $sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
344
                }
345
                $carry = 0;
346
            }
347
 
348
            $result = $sum . $result;
349
 
350
            if ($i === 0) {
351
                break;
352
            }
353
        }
354
 
355
        if ($carry === 1) {
356
            $result = '1' . $result;
357
        }
358
 
359
        return $result;
360
    }
361
 
362
    /**
363
     * Performs the subtraction of two non-signed large integers.
364
     *
365
     * @param string $a The first operand.
366
     * @param string $b The second operand.
367
     *
368
     * @return string
369
     */
370
    private function doSub(string $a, string $b) : string
371
    {
372
        if ($a === $b) {
373
            return '0';
374
        }
375
 
376
        // Ensure that we always subtract to a positive result: biggest minus smallest.
377
        $cmp = $this->doCmp($a, $b);
378
 
379
        $invert = ($cmp === -1);
380
 
381
        if ($invert) {
382
            $c = $a;
383
            $a = $b;
384
            $b = $c;
385
        }
386
 
387
        [$a, $b, $length] = $this->pad($a, $b);
388
 
389
        $carry = 0;
390
        $result = '';
391
 
392
        $complement = 10 ** $this->maxDigits;
393
 
394
        for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
395
            $blockLength = $this->maxDigits;
396
 
397
            if ($i < 0) {
398
                $blockLength += $i;
399
                /** @psalm-suppress LoopInvalidation */
400
                $i = 0;
401
            }
402
 
403
            /** @psalm-var numeric-string $blockA */
404
            $blockA = \substr($a, $i, $blockLength);
405
 
406
            /** @psalm-var numeric-string $blockB */
407
            $blockB = \substr($b, $i, $blockLength);
408
 
409
            $sum = $blockA - $blockB - $carry;
410
 
411
            if ($sum < 0) {
412
                $sum += $complement;
413
                $carry = 1;
414
            } else {
415
                $carry = 0;
416
            }
417
 
418
            $sum = (string) $sum;
419
            $sumLength = \strlen($sum);
420
 
421
            if ($sumLength < $blockLength) {
422
                $sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
423
            }
424
 
425
            $result = $sum . $result;
426
 
427
            if ($i === 0) {
428
                break;
429
            }
430
        }
431
 
432
        // Carry cannot be 1 when the loop ends, as a > b
433
        assert($carry === 0);
434
 
435
        $result = \ltrim($result, '0');
436
 
437
        if ($invert) {
438
            $result = $this->neg($result);
439
        }
440
 
441
        return $result;
442
    }
443
 
444
    /**
445
     * Performs the multiplication of two non-signed large integers.
446
     *
447
     * @param string $a The first operand.
448
     * @param string $b The second operand.
449
     *
450
     * @return string
451
     */
452
    private function doMul(string $a, string $b) : string
453
    {
454
        $x = \strlen($a);
455
        $y = \strlen($b);
456
 
457
        $maxDigits = \intdiv($this->maxDigits, 2);
458
        $complement = 10 ** $maxDigits;
459
 
460
        $result = '0';
461
 
462
        for ($i = $x - $maxDigits;; $i -= $maxDigits) {
463
            $blockALength = $maxDigits;
464
 
465
            if ($i < 0) {
466
                $blockALength += $i;
467
                /** @psalm-suppress LoopInvalidation */
468
                $i = 0;
469
            }
470
 
471
            $blockA = (int) \substr($a, $i, $blockALength);
472
 
473
            $line = '';
474
            $carry = 0;
475
 
476
            for ($j = $y - $maxDigits;; $j -= $maxDigits) {
477
                $blockBLength = $maxDigits;
478
 
479
                if ($j < 0) {
480
                    $blockBLength += $j;
481
                    /** @psalm-suppress LoopInvalidation */
482
                    $j = 0;
483
                }
484
 
485
                $blockB = (int) \substr($b, $j, $blockBLength);
486
 
487
                $mul = $blockA * $blockB + $carry;
488
                $value = $mul % $complement;
489
                $carry = ($mul - $value) / $complement;
490
 
491
                $value = (string) $value;
492
                $value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
493
 
494
                $line = $value . $line;
495
 
496
                if ($j === 0) {
497
                    break;
498
                }
499
            }
500
 
501
            if ($carry !== 0) {
502
                $line = $carry . $line;
503
            }
504
 
505
            $line = \ltrim($line, '0');
506
 
507
            if ($line !== '') {
508
                $line .= \str_repeat('0', $x - $blockALength - $i);
509
                $result = $this->add($result, $line);
510
            }
511
 
512
            if ($i === 0) {
513
                break;
514
            }
515
        }
516
 
517
        return $result;
518
    }
519
 
520
    /**
521
     * Performs the division of two non-signed large integers.
522
     *
523
     * @param string $a The first operand.
524
     * @param string $b The second operand.
525
     *
526
     * @return string[] The quotient and remainder.
527
     */
528
    private function doDiv(string $a, string $b) : array
529
    {
530
        $cmp = $this->doCmp($a, $b);
531
 
532
        if ($cmp === -1) {
533
            return ['0', $a];
534
        }
535
 
536
        $x = \strlen($a);
537
        $y = \strlen($b);
538
 
539
        // we now know that a >= b && x >= y
540
 
541
        $q = '0'; // quotient
542
        $r = $a; // remainder
543
        $z = $y; // focus length, always $y or $y+1
544
 
545
        for (;;) {
546
            $focus = \substr($a, 0, $z);
547
 
548
            $cmp = $this->doCmp($focus, $b);
549
 
550
            if ($cmp === -1) {
551
                if ($z === $x) { // remainder < dividend
552
                    break;
553
                }
554
 
555
                $z++;
556
            }
557
 
558
            $zeros = \str_repeat('0', $x - $z);
559
 
560
            $q = $this->add($q, '1' . $zeros);
561
            $a = $this->sub($a, $b . $zeros);
562
 
563
            $r = $a;
564
 
565
            if ($r === '0') { // remainder == 0
566
                break;
567
            }
568
 
569
            $x = \strlen($a);
570
 
571
            if ($x < $y) { // remainder < dividend
572
                break;
573
            }
574
 
575
            $z = $y;
576
        }
577
 
578
        return [$q, $r];
579
    }
580
 
581
    /**
582
     * Compares two non-signed large numbers.
583
     *
584
     * @param string $a The first operand.
585
     * @param string $b The second operand.
586
     *
587
     * @return int [-1, 0, 1]
588
     */
589
    private function doCmp(string $a, string $b) : int
590
    {
591
        $x = \strlen($a);
592
        $y = \strlen($b);
593
 
594
        $cmp = $x <=> $y;
595
 
596
        if ($cmp !== 0) {
597
            return $cmp;
598
        }
599
 
600
        return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1]
601
    }
602
 
603
    /**
604
     * Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
605
     *
606
     * The numbers must only consist of digits, without leading minus sign.
607
     *
608
     * @param string $a The first operand.
609
     * @param string $b The second operand.
610
     *
611
     * @return array{string, string, int}
612
     */
613
    private function pad(string $a, string $b) : array
614
    {
615
        $x = \strlen($a);
616
        $y = \strlen($b);
617
 
618
        if ($x > $y) {
619
            $b = \str_repeat('0', $x - $y) . $b;
620
 
621
            return [$a, $b, $x];
622
        }
623
 
624
        if ($x < $y) {
625
            $a = \str_repeat('0', $y - $x) . $a;
626
 
627
            return [$a, $b, $y];
628
        }
629
 
630
        return [$a, $b, $x];
631
    }
632
}