DecimalConverter::isStandardBase()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Riimu\Kit\BaseConversion;
4
5
/**
6
 * Converts numbers by using a mathematical algorithm that relies on integers.
7
 *
8
 * DecimalConverter employs arbitrary-precision integer arithmetic to first
9
 * convert the digits to decimal system and then to convert the digits to the
10
 * target base. DecimalConverter depends on the GMP extension to perform the
11
 * required arbitrary precision integer calculations.
12
 *
13
 * @author Riikka Kalliomäki <[email protected]>
14
 * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki
15
 * @license http://opensource.org/licenses/mit-license.php MIT License
16
 */
17
class DecimalConverter implements Converter
18
{
19
    /** @var int Precision for fraction conversions */
20
    private $precision;
21
22
    /** @var NumberBase Number base used by provided numbers */
23
    private $source;
24
25
    /** @var NumberBase Number base used by returned numbers */
26
    private $target;
27
28
    /** @var string Number base used by GMP for standard conversions */
29
    private static $standardBase = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
30
31
    /**
32
     * Creates a new instance of DecimalConverter.
33
     * @param NumberBase $source Number base used by the provided numbers
34
     * @param NumberBase $target Number base used by the returned numbers
35
     */
36 114
    public function __construct(NumberBase $source, NumberBase $target)
37
    {
38 114
        $this->precision = -1;
39 114
        $this->source = $source;
40 114
        $this->target = $target;
41 114
    }
42
43 33
    public function setPrecision($precision)
44
    {
45 33
        $this->precision = (int) $precision;
46 33
    }
47
48 60
    public function convertInteger(array $number)
49
    {
50 60
        $decimal = $this->getDecimal($number);
51
52 60
        if ($this->isStandardBase($this->target->getDigitList())) {
53 48
            return $this->target->canonizeDigits(str_split(gmp_strval($decimal, $this->target->getRadix())));
54
        }
55
56 12
        $zero = gmp_init('0');
57 12
        $radix = gmp_init($this->target->getRadix());
58 12
        $result = [];
59
60 12
        while (gmp_cmp($decimal, $zero) > 0) {
61 12
            list($decimal, $modulo) = gmp_div_qr($decimal, $radix);
62 12
            $result[] = gmp_intval($modulo);
63 4
        }
64
65 12
        return $this->target->getDigits(empty($result) ? [0] : array_reverse($result));
66
    }
67
68 60
    public function convertFractions(array $number)
69
    {
70 60
        $target = gmp_init($this->target->getRadix());
71 60
        $dividend = $this->getDecimal($number);
72 60
        $divisor = $this->getDecimal(
73 60
            [$this->source->getDigit(1)] + array_fill(1, max(count($number), 1), $this->source->getDigit(0))
74 20
        );
75 60
        $digits = $this->getFractionDigitCount(count($number));
76 60
        $zero = gmp_init('0');
77 60
        $result = [];
78
79 60
        for ($i = 0; $i < $digits && gmp_cmp($dividend, $zero) > 0; $i++) {
80 57
            list($digit, $dividend) = gmp_div_qr(gmp_mul($dividend, $target), $divisor);
81 57
            $result[] = gmp_intval($digit);
82 19
        }
83
84 60
        return $this->target->getDigits(empty($result) ? [0] : $result);
85
    }
86
87
    /**
88
     * Converts the number from source base to a decimal GMP resource.
89
     * @param array $number Digits for the number to convert
90
     * @return resource resulting number as a GMP resource
0 ignored issues
show
Documentation introduced by
Should the return type not be \GMP|resource?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
91
     */
92 105
    private function getDecimal(array $number)
93
    {
94 105
        if ($this->isStandardBase($this->source->getDigitList())) {
95 96
            return gmp_init(implode('', $this->source->canonizeDigits($number)), $this->source->getRadix());
96
        }
97
98 12
        $number = $this->source->getValues($number);
99 12
        $decimal = gmp_init('0');
100 12
        $count = count($number);
101 12
        $radix = gmp_init($this->source->getRadix());
102
103 12
        for ($i = 0; $i < $count; $i++) {
104 12
            $decimal = gmp_add($decimal, gmp_mul(gmp_init($number[$i]), gmp_pow($radix, $count - $i - 1)));
105 4
        }
106
107 12
        return $decimal;
108
    }
109
110
    /**
111
     * Tells if the list of digits match those used by GMP.
112
     * @param array $digits List of digits for the number base
113
     * @return bool True if the digits match, false if they do not
114
     */
115 105
    private function isStandardBase(array $digits)
116
    {
117 105
        if (count($digits) > strlen(self::$standardBase)) {
118 3
            return false;
119
        }
120
121 102
        return $digits === str_split(substr(self::$standardBase, 0, count($digits)));
122
    }
123
124
    /**
125
     * Determines the number of digits required in the target base.
126
     * @param int $count Number of digits in the original number
127
     * @return int Number of digits required in the target base
128
     */
129 60
    private function getFractionDigitCount($count)
130
    {
131 60
        if ($this->precision > 0) {
132 24
            return $this->precision;
133
        }
134
135 39
        $target = gmp_init($this->target->getRadix());
136 39
        $maxFraction = gmp_pow(gmp_init($this->source->getRadix()), $count);
137 39
        $digits = 1;
138
139 39
        while (gmp_cmp(gmp_pow($target, $digits), $maxFraction) < 0) {
140 30
            $digits++;
141 10
        }
142
143 39
        return $digits - $this->precision;
144
    }
145
}
146