Passed
Branch master (356932)
by Fabrice
02:58
created

MathBaseAbstract::__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\Math\OpinHelpers;
11
12
/**
13
 * Abstract class MathBaseAbstract
14
 */
15
abstract class MathBaseAbstract
16
{
17
    /**
18
     * Default precision
19
     */
20
    const PRECISION = 9;
21
22
    /**
23
     * base <= 64 charlist
24
     */
25
    const BASECHAR_64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
26
27
    /**
28
     * base <= 62 char list
29
     */
30
    const BASECHAR_62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
31
32
    /**
33
     * base <= 36 charlist
34
     */
35
    const BASECHAR_36 = '0123456789abcdefghijklmnopqrstuvwxyz';
36
37
    /**
38
     * base char cache for all supported bases (bellow 64)
39
     *
40
     * @var string[]
41
     */
42
    protected static $baseChars = [
43
        36 => self::BASECHAR_36,
44
        62 => self::BASECHAR_62,
45
        64 => self::BASECHAR_64,
46
    ];
47
48
    /**
49
     *  if set, will be used as default for all consecutive instances
50
     *
51
     * @var int
52
     */
53
    protected static $globalPrecision;
54
55
    /**
56
     * Used in static context, aligned with $globalPrecision, default to self::PRECISION
57
     *
58
     * @var int
59
     */
60
    protected static $staticPrecision = self::PRECISION;
61
62
    /**
63
     * @var bool
64
     */
65
    protected static $gmpSupport;
66
67
    /**
68
     * @var string
69
     */
70
    protected $number;
71
72
    /**
73
     * Instance precision, initialized with globalPrecision, default to self::PRECISION
74
     *
75
     * @var int
76
     */
77
    protected $precision = self::PRECISION;
78
79
    /**
80
     * @return string
81
     */
82
    public function getNumber()
83
    {
84
        return $this->number;
85
    }
86
87
    /**
88
     * @return bool
89
     */
90
    public function isPositive()
91
    {
92
        return $this->number[0] !== '-';
93
    }
94
95
    /**
96
     * @return bool
97
     */
98
    public function hasDecimals()
99
    {
100
        return strpos($this->number, '.') !== false;
101
    }
102
103
    /**
104
     * @return $this
105
     */
106
    public function normalize()
107
    {
108
        $this->number = static::normalizeNumber($this->number);
109
110
        return $this;
111
    }
112
113
    /**
114
     * @param int $precision
115
     *
116
     * @return $this
117
     */
118
    public function setPrecision($precision)
119
    {
120
        // even INT_32 should be enough precision
121
        $this->precision = max(0, (int) $precision);
122
123
        return $this;
124
    }
125
126
    /**
127
     * @param int $precision
128
     */
129
    public static function setGlobalPrecision($precision)
130
    {
131
        // even INT_32 should be enough precision
132
        static::$globalPrecision = max(0, (int) $precision);
133
        static::$staticPrecision = static::$globalPrecision;
134
    }
135
136
    /**
137
     * @param bool $disable
138
     *
139
     * @return bool
140
     */
141
    public static function gmpSupport($disable = false)
142
    {
143
        if ($disable) {
144
            return static::$gmpSupport = false;
145
        }
146
147
        return static::$gmpSupport = function_exists('gmp_init');
148
    }
149
150
    /**
151
     * There is no way around it, if you want to trust bcmath
152
     * you need to feed it with VALID numbers
153
     * Things like '1.1.1' or '12E16'are all 0 in bcmath world
154
     *
155
     * @param mixed $number
156
     *
157
     * @return bool
158
     */
159
    public static function isNumber($number)
160
    {
161
        return (bool) preg_match('`^([+-]{1})?([0-9]+(\.[0-9]+)?|\.[0-9]+)$`', $number);
162
    }
163
164
    /**
165
     * removes preceding / trailing 0, + and ws
166
     *
167
     * @param string      $number
168
     * @param string|null $default
169
     *
170
     * @return string|null
171
     */
172
    public static function normalizeNumber($number, $default = null)
173
    {
174
        if (!static::isNumber($number)) {
175
            return $default;
176
        }
177
178
        $sign   = $number[0] === '-' ? '-' : '';
179
        $number = ltrim((string) $number, '0+-');
180
181
        if (strpos($number, '.') !== false) {
182
            // also clear trailing 0
183
            list($number, $dec) = explode('.', $number);
184
            $dec                = rtrim($dec, '0.');
185
            $number             = ($number ? $number : '0') . ($dec ? '.' . $dec : '');
186
        }
187
188
        return $number ? $sign . $number : '0';
189
    }
190
191
    /**
192
     * @param int $base
193
     * @param int $max
194
     *
195
     * @throws \InvalidArgumentException
196
     *
197
     * @return string
198
     */
199
    public static function getBaseChar($base, $max = 64)
200
    {
201
        $base = (int) $base;
202
        if ($base < 2 || $base > $max || $base > 64) {
203
            throw new \InvalidArgumentException('Argument base is not valid, base 2 to 64 are supported');
204
        }
205
206
        if (!isset(static::$baseChars[$base])) {
207
            if ($base > 62) {
208
                static::$baseChars[$base] = ($base == 64) ? static::BASECHAR_64 : substr(static::BASECHAR_64, 0, $base);
209
            } elseif ($base > 36) {
210
                static::$baseChars[$base] = ($base == 62) ? static::BASECHAR_62 : substr(static::BASECHAR_62, 0, $base);
211
            } else {
212
                static::$baseChars[$base] = ($base == 36) ? static::BASECHAR_36 : substr(static::BASECHAR_36, 0, $base);
213
            }
214
        }
215
216
        return static::$baseChars[$base];
217
    }
218
219
    /**
220
     * Convert a from a given base (up to 62) to base 10.
221
     *
222
     * WARNING This method requires ext-gmp
223
     *
224
     * @param string $number
225
     * @param int    $fromBase
226
     * @param int    $toBase
227
     *
228
     * @return string
229
     *
230
     * @internal param int $base
231
     */
232
    public static function baseConvert($number, $fromBase = 10, $toBase = 62)
233
    {
234
        return gmp_strval(gmp_init($number, $fromBase), $toBase);
235
    }
236
237
    /**
238
     * @param string     $number
239
     * @param string|int $base
240
     * @param string     $baseChar
241
     *
242
     * @return string
243
     */
244
    protected static function bcDec2Base($number, $base, $baseChar)
245
    {
246
        $result    = '';
247
        $numberLen = strlen($number);
248
        // Now loop through each digit in the number
249
        for ($i = $numberLen - 1; $i >= 0; --$i) {
250
            $char = $number[$i]; // extract the last char from the number
251
            $ord  = strpos($baseChar, $char); // get the decimal value
252
            if ($ord === false || $ord > $base) {
253
                throw new \InvalidArgumentException('Argument number is invalid');
254
            }
255
256
            // Now convert the value+position to decimal
257
            $result = bcadd($result, bcmul($ord, bcpow($base, ($numberLen - $i - 1))));
258
        }
259
260
        return $result ? $result : '0';
261
    }
262
263
    /**
264
     * @param string|static $number
265
     *
266
     * @throws \InvalidArgumentException
267
     *
268
     * @return string
269
     */
270
    protected static function validateInputNumber($number)
271
    {
272
        if ($number instanceof static) {
273
            return $number->getNumber();
274
        }
275
276
        $number = trim($number);
277
        if (!static::isNumber($number)) {
278
            throw new \InvalidArgumentException('Argument number is not valid');
279
        }
280
281
        return $number;
282
    }
283
284
    /**
285
     * @param int|string $integer
286
     *
287
     * @throws \InvalidArgumentException
288
     *
289
     * @return string
290
     */
291
    protected static function validatePositiveInteger($integer)
292
    {
293
        $integer = max(0, (int) $integer);
294
        if (!$integer) {
295
            throw new \InvalidArgumentException('Argument number is not valid');
296
        }
297
298
        return (string) $integer;
299
    }
300
}
301