GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 6e6a53...7aa2fb )
by Joni
03:21
created

Real::nr3Val()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 13
c 0
b 0
f 0
nc 12
nop 0
dl 0
loc 23
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\ASN1\Type\Primitive;
6
7
use Sop\ASN1\Component\Identifier;
8
use Sop\ASN1\Component\Length;
9
use Sop\ASN1\Element;
10
use Sop\ASN1\Exception\DecodeException;
11
use Sop\ASN1\Feature\ElementBase;
12
use Sop\ASN1\Type\PrimitiveType;
13
use Sop\ASN1\Type\UniversalClass;
14
use Sop\ASN1\Util\BigInt;
15
16
/**
17
 * Implements *REAL* type.
18
 */
19
class Real extends Element
20
{
21
    use UniversalClass;
22
    use PrimitiveType;
23
24
    /**
25
     * Regex pattern to parse NR1 form number.
26
     *
27
     * @var string
28
     */
29
    const NR1_REGEX = '/^\s*' .
30
        '(?<s>[+\-])?' .    // sign
31
        '(?<i>\d+)' .       // integer
32
    '$/';
33
34
    /**
35
     * Regex pattern to parse NR2 form number.
36
     *
37
     * @var string
38
     */
39
    const NR2_REGEX = '/^\s*' .
40
        '(?<s>[+\-])?' .                            // sign
41
        '(?<d>(?:\d+[\.,]\d*)|(?:\d*[\.,]\d+))' .   // decimal number
42
    '$/';
43
44
    /**
45
     * Regex pattern to parse NR3 form number.
46
     *
47
     * @var string
48
     */
49
    const NR3_REGEX = '/^\s*' .
50
        '(?<ms>[+\-])?' .                           // mantissa sign
51
        '(?<m>(?:\d+[\.,]\d*)|(?:\d*[\.,]\d+))' .   // mantissa
52
        '[Ee](?<es>[+\-])?' .                       // exponent sign
53
        '(?<e>\d+)' .                               // exponent
54
    '$/';
55
56
    /**
57
     * Regex pattern to parse PHP exponent number format.
58
     *
59
     * @see http://php.net/manual/en/language.types.float.php
60
     *
61
     * @var string
62
     */
63
    const PHP_EXPONENT_DNUM = '/^' .
64
        '(?<ms>[+\-])?' .               // sign
65
        '(?<m>' .
66
            '\d+' .                     // LNUM
67
            '|' .
68
            '(?:\d*\.\d+|\d+\.\d*)' .   // DNUM
69
        ')[eE]' .
70
        '(?<es>[+\-])?(?<e>\d+)' .      // exponent
71
    '$/';
72
73
    /**
74
     * Exponent when value is positive or negative infinite.
75
     *
76
     * @var int
77
     */
78
    const INF_EXPONENT = 2047;
79
80
    /**
81
     * Exponent bias for IEEE 754 double precision float.
82
     *
83
     * @var int
84
     */
85
    const EXP_BIAS = -1023;
86
87
    /**
88
     * Signed integer mantissa.
89
     *
90
     * @var BigInt
91
     */
92
    private $_mantissa;
93
94
    /**
95
     * Signed integer exponent.
96
     *
97
     * @var BigInt
98
     */
99
    private $_exponent;
100
101
    /**
102
     * Abstract value base.
103
     *
104
     * Must be 2 or 10.
105
     *
106
     * @var int
107
     */
108
    private $_base;
109
110
    /**
111
     * Whether to encode strictly in DER.
112
     *
113
     * @var bool
114
     */
115
    private $_strictDer;
116
117
    /**
118
     * Number as a native float.
119
     *
120
     * @internal Lazily initialized
121
     *
122
     * @var null|float
123
     */
124
    private $_float;
125
126
    /**
127
     * Constructor.
128
     *
129
     * @param \GMP|int|string $mantissa Integer mantissa
130
     * @param \GMP|int|string $exponent Integer exponent
131
     * @param int             $base     Base, 2 or 10
132
     */
133 56
    public function __construct($mantissa, $exponent, int $base = 10)
134
    {
135 56
        if (10 !== $base && 2 !== $base) {
136 1
            throw new \UnexpectedValueException('Base must be 2 or 10.');
137
        }
138 55
        $this->_typeTag = self::TYPE_REAL;
139 55
        $this->_strictDer = true;
140 55
        $this->_mantissa = new BigInt($mantissa);
141 55
        $this->_exponent = new BigInt($exponent);
142 55
        $this->_base = $base;
143 55
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 1
    public function __toString(): string
149
    {
150 1
        return sprintf('%g', $this->floatVal());
151
    }
152
153
    /**
154
     * Create base 2 real number from float.
155
     *
156
     * @param float $number
157
     *
158
     * @return self
159
     */
160 36
    public static function fromFloat(float $number): self
161
    {
162 36
        if (is_infinite($number)) {
163 4
            return self::_fromInfinite($number);
164
        }
165 32
        if (is_nan($number)) {
166 1
            throw new \UnexpectedValueException('NaN values not supported.');
167
        }
168 31
        [$m, $e] = self::_parse754Double(pack('E', $number));
169 31
        return new self($m, $e, 2);
170
    }
171
172
    /**
173
     * Create base 10 real number from string.
174
     *
175
     * @param string $number Real number in base-10 textual form
176
     *
177
     * @return self
178
     */
179 10
    public static function fromString(string $number): self
180
    {
181 10
        [$m, $e] = self::_parseString($number);
182 9
        return new self($m, $e, 10);
1 ignored issue
show
Bug introduced by
It seems like $e can also be of type resource; however, parameter $exponent of Sop\ASN1\Type\Primitive\Real::__construct() does only seem to accept GMP|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

182
        return new self($m, /** @scrutinizer ignore-type */ $e, 10);
Loading history...
183
    }
184
185
    /**
186
     * Get self with strict DER flag set or unset.
187
     *
188
     * @param bool $strict whether to encode strictly in DER
189
     *
190
     * @return self
191
     */
192 20
    public function withStrictDER(bool $strict): self
193
    {
194 20
        $obj = clone $this;
195 20
        $obj->_strictDer = $strict;
196 20
        return $obj;
197
    }
198
199
    /**
200
     * Get the mantissa.
201
     *
202
     * @return BigInt
203
     */
204 1
    public function mantissa(): BigInt
205
    {
206 1
        return $this->_mantissa;
207
    }
208
209
    /**
210
     * Get the exponent.
211
     *
212
     * @return BigInt
213
     */
214 3
    public function exponent(): BigInt
215
    {
216 3
        return $this->_exponent;
217
    }
218
219
    /**
220
     * Get the base.
221
     *
222
     * @return int
223
     */
224 1
    public function base(): int
225
    {
226 1
        return $this->_base;
227
    }
228
229
    /**
230
     * Get number as a float.
231
     *
232
     * @return float
233
     */
234 46
    public function floatVal(): float
235
    {
236 46
        if (!isset($this->_float)) {
237 46
            $m = $this->_mantissa->intVal();
238 46
            $e = $this->_exponent->intVal();
239 46
            $this->_float = (float) ($m * pow($this->_base, $e));
240
        }
241 46
        return $this->_float;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->_float could return the type null which is incompatible with the type-hinted return double. Consider adding an additional type-check to rule them out.
Loading history...
242
    }
243
244
    /**
245
     * Get number as a NR3 form string conforming to DER rules.
246
     *
247
     * @return string
248
     */
249 5
    public function nr3Val(): string
250
    {
251
        // convert to base 10
252 5
        if (2 === $this->_base) {
253 1
            [$m, $e] = self::_parseString(sprintf('%15E', $this->floatVal()));
254
        } else {
255 4
            $m = $this->_mantissa->gmpObj();
256 4
            $e = $this->_exponent->gmpObj();
257
        }
258
        // shift trailing zeroes from the mantissa to the exponent
259
        // (X.690 07-2002, section 11.3.2.4)
260 5
        while (0 != $m && 0 == $m % 10) {
261 1
            $m /= 10;
262 1
            ++$e;
263
        }
264
        // if exponent is zero, it must be prefixed with a "+" sign
265
        // (X.690 07-2002, section 11.3.2.6)
266 5
        if (0 == $e) {
267 1
            $es = '+';
268
        } else {
269 4
            $es = $e < 0 ? '-' : '';
270
        }
271 5
        return sprintf('%s.E%s%s', gmp_strval($m), $es, gmp_strval(gmp_abs($e)));
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277 43
    protected function _encodedContentDER(): string
278
    {
279 43
        if (self::INF_EXPONENT == $this->_exponent->gmpObj()) {
1 ignored issue
show
introduced by
The condition self::INF_EXPONENT == $this->_exponent->gmpObj() is always false.
Loading history...
280 5
            return $this->_encodeSpecial();
281
        }
282
        // if the real value is the value zero, there shall be no contents
283
        // octets in the encoding. (X.690 07-2002, section 8.5.2)
284 38
        if (0 == $this->_mantissa->gmpObj()) {
1 ignored issue
show
introduced by
The condition 0 == $this->_mantissa->gmpObj() is always false.
Loading history...
285 2
            return '';
286
        }
287 36
        if (10 === $this->_base) {
288 1
            return $this->_encodeDecimal();
289
        }
290 35
        return $this->_encodeBinary();
291
    }
292
293
    /**
294
     * Encode in binary format.
295
     *
296
     * @return string
297
     */
298 35
    protected function _encodeBinary(): string
299
    {
300 35
        [$base, $sign, $m, $e] = $this->_prepareBinaryEncoding();
301 35
        $zero = gmp_init(0, 10);
302 35
        $byte = 0x80;
303 35
        if ($sign < 0) {
304 14
            $byte |= 0x40;
305
        }
306
        // normalization: mantissa must be 0 or odd
307 35
        if (2 === $base) {
308
            // while last bit is zero
309 22
            while ($m > 0 && 0 === gmp_cmp($m & 0x01, $zero)) {
310 1
                $m >>= 1;
311 1
                ++$e;
312
            }
313 13
        } elseif (8 === $base) {
314 5
            $byte |= 0x10;
315
            // while last 3 bits are zero
316 5
            while ($m > 0 && 0 === gmp_cmp($m & 0x07, $zero)) {
317 1
                $m >>= 3;
318 1
                ++$e;
319
            }
320
        } else { // base === 16
321 8
            $byte |= 0x20;
322
            // while last 4 bits are zero
323 8
            while ($m > 0 && 0 === gmp_cmp($m & 0x0f, $zero)) {
324 2
                $m >>= 4;
325 2
                ++$e;
326
            }
327
        }
328
        // scale factor
329 35
        $scale = 0;
330 35
        while ($m > 0 && 0 === gmp_cmp($m & 0x01, $zero)) {
331 1
            $m >>= 1;
332 1
            ++$scale;
333
        }
334 35
        $byte |= ($scale & 0x03) << 2;
335
        // encode exponent
336 35
        $exp_bytes = (new BigInt($e))->signedOctets();
337 35
        $exp_len = strlen($exp_bytes);
338 35
        if ($exp_len > 0xff) {
339 1
            throw new \RangeException('Exponent encoding is too long.');
340
        }
341 34
        if ($exp_len <= 3) {
342 32
            $byte |= ($exp_len - 1) & 0x03;
343 32
            $bytes = chr($byte);
344
        } else {
345 2
            $byte |= 0x03;
346 2
            $bytes = chr($byte) . chr($exp_len);
347
        }
348 34
        $bytes .= $exp_bytes;
349
        // encode mantissa
350 34
        $bytes .= (new BigInt($m))->unsignedOctets();
1 ignored issue
show
Bug introduced by
It seems like $m can also be of type resource; however, parameter $num of Sop\ASN1\Util\BigInt::__construct() does only seem to accept GMP|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
        $bytes .= (new BigInt(/** @scrutinizer ignore-type */ $m))->unsignedOctets();
Loading history...
351 34
        return $bytes;
352
    }
353
354
    /**
355
     * Encode in decimal format.
356
     *
357
     * @return strign
0 ignored issues
show
Bug introduced by
The type Sop\ASN1\Type\Primitive\strign was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
358
     */
359 1
    protected function _encodeDecimal(): string
360
    {
361
        // encode in NR3 decimal encoding
362 1
        return chr(0x03) . $this->nr3Val();
0 ignored issues
show
Bug Best Practice introduced by
The expression return chr(3) . $this->nr3Val() returns the type string which is incompatible with the documented return type Sop\ASN1\Type\Primitive\strign.
Loading history...
363
    }
364
365
    /**
366
     * Encode special value.
367
     *
368
     * @return string
369
     */
370 5
    protected function _encodeSpecial(): string
371
    {
372 5
        switch ($this->_mantissa->intVal()) {
373
            // positive infitinity
374 5
            case 1:
375 2
                return chr(0x40);
376
            // negative infinity
377
            case -1:
378 2
                return chr(0x41);
379
        }
380 1
        throw new \LogicException('Invalid special value.');
381
    }
382
383
    /**
384
     * {@inheritdoc}
385
     */
386 47
    protected static function _decodeFromDER(Identifier $identifier,
387
        string $data, int &$offset): ElementBase
388
    {
389 47
        $idx = $offset;
390 47
        $length = Length::expectFromDER($data, $idx)->intLength();
391
        // if length is zero, value is zero (spec 8.5.2)
392 47
        if (!$length) {
393 2
            $obj = new self(0, 0, 10);
394
        } else {
395 45
            $bytes = substr($data, $idx, $length);
396 45
            $byte = ord($bytes[0]);
397 45
            if (0x80 & $byte) { // bit 8 = 1
398 37
                $obj = self::_decodeBinaryEncoding($bytes);
399 8
            } elseif (0x00 === $byte >> 6) { // bit 8 = 0, bit 7 = 0
400 2
                $obj = self::_decodeDecimalEncoding($bytes);
401
            } else { // bit 8 = 0, bit 7 = 1
402 6
                $obj = self::_decodeSpecialRealValue($bytes);
403
            }
404
        }
405 40
        $offset = $idx + $length;
406 40
        return $obj;
407
    }
408
409
    /**
410
     * Decode binary encoding.
411
     *
412
     * @param string $data
413
     */
414 37
    protected static function _decodeBinaryEncoding(string $data)
415
    {
416 37
        $byte = ord($data[0]);
417
        // bit 7 is set if mantissa is negative
418 37
        $neg = (bool) (0x40 & $byte);
419
        // encoding base in bits 6 and 5
420 37
        switch (($byte >> 4) & 0x03) {
421 37
            case 0b00:
422 23
                $base = 2;
423 23
                break;
424 14
            case 0b01:
425 5
                $base = 8;
426 5
                break;
427 9
            case 0b10:
428 8
                $base = 16;
429 8
                break;
430
            default:
431 1
                throw new DecodeException(
432 1
                    'Reserved REAL binary encoding base not supported.');
433
        }
434
        // scaling factor in bits 4 and 3
435 36
        $scale = ($byte >> 2) & 0x03;
436 36
        $idx = 1;
437
        // content length in bits 2 and 1
438 36
        $len = ($byte & 0x03) + 1;
439
        // if both bits are set, the next octet encodes the length
440 36
        if ($len > 3) {
441 2
            if (strlen($data) < 2) {
442 1
                throw new DecodeException(
443 1
                    'Unexpected end of data while decoding REAL exponent length.');
444
            }
445 1
            $len = ord($data[1]);
446 1
            $idx = 2;
447
        }
448 35
        if (strlen($data) < $idx + $len) {
449 1
            throw new DecodeException(
450 1
                'Unexpected end of data while decoding REAL exponent.');
451
        }
452
        // decode exponent
453 34
        $octets = substr($data, $idx, $len);
454 34
        $exp = BigInt::fromSignedOctets($octets)->gmpObj();
455 34
        if (8 === $base) {
456 5
            $exp *= 3;
457 29
        } elseif (16 === $base) {
458 8
            $exp *= 4;
459
        }
460 34
        if (strlen($data) <= $idx + $len) {
461 1
            throw new DecodeException(
462 1
                'Unexpected end of data while decoding REAL mantissa.');
463
        }
464
        // decode mantissa
465 33
        $octets = substr($data, $idx + $len);
466 33
        $n = BigInt::fromUnsignedOctets($octets)->gmpObj();
467 33
        $n *= 2 ** $scale;
468 33
        if ($neg) {
469 14
            $n = gmp_neg($n);
470
        }
471 33
        return new self($n, $exp, 2);
1 ignored issue
show
Bug introduced by
It seems like $n can also be of type resource; however, parameter $mantissa of Sop\ASN1\Type\Primitive\Real::__construct() does only seem to accept GMP|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

471
        return new self(/** @scrutinizer ignore-type */ $n, $exp, 2);
Loading history...
472
    }
473
474
    /**
475
     * Decode decimal encoding.
476
     *
477
     * @param string $data
478
     *
479
     * @throws \RuntimeException
480
     *
481
     * @return self
482
     */
483 2
    protected static function _decodeDecimalEncoding(string $data): self
484
    {
485 2
        $nr = ord($data[0]) & 0x3f;
486 2
        if (!in_array($nr, [1, 2, 3])) {
487 1
            throw new DecodeException('Unsupported decimal encoding form.');
488
        }
489 1
        $str = substr($data, 1);
490 1
        return self::fromString($str);
491
    }
492
493
    /**
494
     * Decode special encoding.
495
     *
496
     * @param string $data
497
     *
498
     * @return self
499
     */
500 6
    protected static function _decodeSpecialRealValue(string $data): self
501
    {
502 6
        if (1 !== strlen($data)) {
503 1
            throw new DecodeException(
504 1
                'SpecialRealValue must have one content octet.');
505
        }
506 5
        $byte = ord($data[0]);
507 5
        if (0x40 === $byte) {   // positive infinity
508 2
            return self::_fromInfinite(INF);
509
        }
510 3
        if (0x41 === $byte) {   // negative infinity
511 2
            return self::_fromInfinite(-INF);
512
        }
513 1
        throw new DecodeException('Invalid SpecialRealValue encoding.');
514
    }
515
516
    /**
517
     * Prepare value for binary encoding.
518
     *
519
     * @return array (int) base, (int) sign, (\GMP) mantissa and (\GMP) exponent
520
     */
521 35
    protected function _prepareBinaryEncoding(): array
522
    {
523 35
        $base = 2;
524 35
        $m = $this->_mantissa->gmpObj();
525 35
        $ms = gmp_sign($m);
526 35
        $m = gmp_abs($m);
527 35
        $e = $this->_exponent->gmpObj();
528 35
        $es = gmp_sign($e);
529 35
        $e = gmp_abs($e);
530
        // DER uses only base 2 binary encoding
531 35
        if (!$this->_strictDer) {
532 17
            if (0 == $e % 4) {
533 8
                $base = 16;
534 8
                $e = gmp_div_q($e, 4);
535 9
            } elseif (0 == $e % 3) {
536 5
                $base = 8;
537 5
                $e = gmp_div_q($e, 3);
538
            }
539
        }
540 35
        return [$base, $ms, $m, $e * $es];
541
    }
542
543
    /**
544
     * Initialize from INF or -INF.
545
     *
546
     * @param float $inf
547
     *
548
     * @return self
549
     */
550 4
    private static function _fromInfinite(float $inf): self
551
    {
552 4
        return new self($inf === -INF ? -1 : 1, self::INF_EXPONENT, 2);
553
    }
554
555
    /**
556
     * Parse IEEE 754 big endian formatted double precision float to base 2
557
     * mantissa and exponent.
558
     *
559
     * @param string $octets 64 bits
560
     *
561
     * @return \GMP[] Tuple of mantissa and exponent
562
     */
563 31
    private static function _parse754Double(string $octets): array
564
    {
565 31
        $n = gmp_import($octets, 1, GMP_MSW_FIRST | GMP_BIG_ENDIAN);
566
        // sign bit
567 31
        $neg = gmp_testbit($n, 63);
1 ignored issue
show
Bug introduced by
It seems like $n can also be of type false; however, parameter $a of gmp_testbit() does only seem to accept GMP|resource|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

567
        $neg = gmp_testbit(/** @scrutinizer ignore-type */ $n, 63);
Loading history...
568
        // 11 bits of biased exponent
569 31
        $exp = (gmp_and($n, '0x7ff0000000000000') >> 52) + self::EXP_BIAS;
1 ignored issue
show
Bug introduced by
It seems like $n can also be of type false; however, parameter $a of gmp_and() does only seem to accept GMP|resource|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

569
        $exp = (gmp_and(/** @scrutinizer ignore-type */ $n, '0x7ff0000000000000') >> 52) + self::EXP_BIAS;
Loading history...
570
        // 52 bits of mantissa
571 31
        $man = gmp_and($n, '0xfffffffffffff');
572
        // zero, ASN.1 doesn't differentiate -0 from +0
573 31
        if (self::EXP_BIAS == $exp && 0 == $man) {
574 2
            return [gmp_init(0, 10), gmp_init(0, 10)];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array(gmp_init(0, 10), gmp_init(0, 10)) returns an array which contains values of type resource which are incompatible with the documented value type GMP.
Loading history...
575
        }
576
        // denormalized value, shift binary point
577 29
        if (self::EXP_BIAS == $exp) {
578 4
            ++$exp;
579
        }
580
        // normalized value, insert implicit leading one before the binary point
581
        else {
582 25
            gmp_setbit($man, 52);
583
        }
584
        // find the last fraction bit that is set
585 29
        $last = gmp_scan1($man, 0);
586 29
        $bits_for_fraction = 52 - $last;
587
        // adjust mantissa and exponent so that we have integer values
588 29
        $man >>= $last;
589 29
        $exp -= $bits_for_fraction;
590
        // negate mantissa if number was negative
591 29
        if ($neg) {
592 15
            $man = gmp_neg($man);
593
        }
594 29
        return [$man, $exp];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array($man, $exp) returns an array which contains values of type integer|resource which are incompatible with the documented value type GMP.
Loading history...
595
    }
596
597
    /**
598
     * Parse textual REAL number to base 10 mantissa and exponent.
599
     *
600
     * @param string $str Number
601
     *
602
     * @return \GMP[] Tuple of mantissa and exponent
603
     */
604 11
    private static function _parseString(string $str): array
605
    {
606
        // PHP exponent format
607 11
        if (preg_match(self::PHP_EXPONENT_DNUM, $str, $match)) {
608 2
            [$m, $e] = self::_parsePHPExponentMatch($match);
609
        }
610
        // NR3 format
611 9
        elseif (preg_match(self::NR3_REGEX, $str, $match)) {
612 3
            [$m, $e] = self::_parseNR3Match($match);
613
        }
614
        // NR2 format
615 6
        elseif (preg_match(self::NR2_REGEX, $str, $match)) {
616 2
            [$m, $e] = self::_parseNR2Match($match);
617
        }
618
        // NR1 format
619 4
        elseif (preg_match(self::NR1_REGEX, $str, $match)) {
620 3
            [$m, $e] = self::_parseNR1Match($match);
621
        }
622
        // invalid number
623
        else {
624 1
            throw new \UnexpectedValueException(
625 1
                "{$str} could not be parsed to REAL.");
626
        }
627
        // normalize so that mantsissa has no trailing zeroes
628 10
        while (0 != $m && 0 == $m % 10) {
629 1
            $m /= 10;
630 1
            ++$e;
631
        }
632 10
        return [$m, $e];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array($m, $e) returns an array which contains values of type integer|resource which are incompatible with the documented value type GMP.
Loading history...
633
    }
634
635
    /**
636
     * Parse PHP form float to base 10 mantissa and exponent.
637
     *
638
     * @param array $match Regexp match
639
     *
640
     * @return \GMP[] Tuple of mantissa and exponent
641
     */
642 2
    private static function _parsePHPExponentMatch(array $match): array
643
    {
644
        // mantissa sign
645 2
        $ms = '-' === $match['ms'] ? -1 : 1;
646 2
        $m_parts = explode('.', $match['m']);
647
        // integer part of the mantissa
648 2
        $int = ltrim($m_parts[0], '0');
649
        // exponent sign
650 2
        $es = '-' === $match['es'] ? -1 : 1;
651
        // signed exponent
652 2
        $e = gmp_init($match['e'], 10) * $es;
653
        // if mantissa had fractional part
654 2
        if (2 === count($m_parts)) {
655 2
            $frac = rtrim($m_parts[1], '0');
656 2
            $e -= strlen($frac);
657 2
            $int .= $frac;
658
        }
659 2
        $m = gmp_init($int, 10) * $ms;
660 2
        return [$m, $e];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array($m, $e) returns the type array<integer,integer> which is incompatible with the documented return type GMP[].
Loading history...
661
    }
662
663
    /**
664
     * Parse NR3 form number to base 10 mantissa and exponent.
665
     *
666
     * @param array $match Regexp match
667
     *
668
     * @return \GMP[] Tuple of mantissa and exponent
669
     */
670 3
    private static function _parseNR3Match(array $match): array
671
    {
672
        // mantissa sign
673 3
        $ms = '-' === $match['ms'] ? -1 : 1;
674
        // explode mantissa to integer and fraction parts
675 3
        [$int, $frac] = explode('.', str_replace(',', '.', $match['m']));
676 3
        $int = ltrim($int, '0');
677 3
        $frac = rtrim($frac, '0');
678
        // exponent sign
679 3
        $es = '-' === $match['es'] ? -1 : 1;
680
        // signed exponent
681 3
        $e = gmp_init($match['e'], 10) * $es;
682
        // shift exponent by the number of base 10 fractions
683 3
        $e -= strlen($frac);
684
        // insert fractions to integer part and produce signed mantissa
685 3
        $int .= $frac;
686 3
        if ('' === $int) {
687 1
            $int = '0';
688
        }
689 3
        $m = gmp_init($int, 10) * $ms;
690 3
        return [$m, $e];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array($m, $e) returns the type array<integer,integer> which is incompatible with the documented return type GMP[].
Loading history...
691
    }
692
693
    /**
694
     * Parse NR2 form number to base 10 mantissa and exponent.
695
     *
696
     * @param array $match Regexp match
697
     *
698
     * @return \GMP[] Tuple of mantissa and exponent
699
     */
700 2
    private static function _parseNR2Match(array $match): array
701
    {
702 2
        $sign = '-' === $match['s'] ? -1 : 1;
703
        // explode decimal number to integer and fraction parts
704 2
        [$int, $frac] = explode('.', str_replace(',', '.', $match['d']));
705 2
        $int = ltrim($int, '0');
706 2
        $frac = rtrim($frac, '0');
707
        // shift exponent by the number of base 10 fractions
708 2
        $e = gmp_init(0, 10);
709 2
        $e -= strlen($frac);
710
        // insert fractions to integer part and produce signed mantissa
711 2
        $int .= $frac;
712 2
        if ('' === $int) {
713 1
            $int = '0';
714
        }
715 2
        $m = gmp_init($int, 10) * $sign;
716 2
        return [$m, $e];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array($m, $e) returns an array which contains values of type integer|resource which are incompatible with the documented value type GMP.
Loading history...
717
    }
718
719
    /**
720
     * Parse NR1 form number to base 10 mantissa and exponent.
721
     *
722
     * @param array $match Regexp match
723
     *
724
     * @return \GMP[] Tuple of mantissa and exponent
725
     */
726 3
    private static function _parseNR1Match(array $match): array
727
    {
728 3
        $sign = '-' === $match['s'] ? -1 : 1;
729 3
        $int = ltrim($match['i'], '0');
730 3
        if ('' === $int) {
731 1
            $int = '0';
732
        }
733 3
        $m = gmp_init($int, 10) * $sign;
734 3
        return [$m, gmp_init(0, 10)];
1 ignored issue
show
Bug Best Practice introduced by
The expression return array($m, gmp_init(0, 10)) returns an array which contains values of type integer|resource which are incompatible with the documented value type GMP.
Loading history...
735
    }
736
}
737