1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace BCMathExtended; |
||||
6 | |||||
7 | use Closure; |
||||
8 | use InvalidArgumentException; |
||||
9 | |||||
10 | class BC |
||||
11 | { |
||||
12 | public const COMPARE_EQUAL = 0; |
||||
13 | |||||
14 | public const COMPARE_LEFT_GRATER = 1; |
||||
15 | |||||
16 | public const COMPARE_RIGHT_GRATER = -1; |
||||
17 | |||||
18 | protected const DEFAULT_SCALE = 100; |
||||
19 | |||||
20 | protected const MAX_BASE = 256; |
||||
21 | |||||
22 | protected const BIT_OPERATOR_AND = 'and'; |
||||
23 | |||||
24 | protected const BIT_OPERATOR_OR = 'or'; |
||||
25 | |||||
26 | protected const BIT_OPERATOR_XOR = 'xor'; |
||||
27 | |||||
28 | protected static bool $trimTrailingZeroes = true; |
||||
29 | |||||
30 | 2 | public static function rand(string $min, string $max): string |
|||
31 | { |
||||
32 | 2 | $max = static::convertScientificNotationToString($max); |
|||
33 | 2 | $min = static::convertScientificNotationToString($min); |
|||
34 | |||||
35 | 2 | $difference = static::add(static::sub($max, $min), '1'); |
|||
36 | 2 | $randPercent = static::div((string)mt_rand(), (string)mt_getrandmax(), 8); |
|||
37 | |||||
38 | 2 | return static::add($min, static::mul($difference, $randPercent, 8), 0); |
|||
39 | } |
||||
40 | |||||
41 | 397 | public static function convertScientificNotationToString(string $number): string |
|||
42 | { |
||||
43 | // check if number is in scientific notation, first use stripos as is faster than preg_match |
||||
44 | 397 | if (stripos($number, 'E') !== false && preg_match('/(-?(\d+\.)?\d+)E([+-]?)(\d+)/i', $number, $regs)) { |
|||
45 | // calculate final scale of number |
||||
46 | 76 | $scale = (int)$regs[4] + static::getDecimalsLength($regs[1]); |
|||
47 | 76 | $pow = static::pow('10', $regs[4], $scale); |
|||
48 | 76 | if ($regs[3] === '-') { |
|||
49 | 31 | $number = static::div($regs[1], $pow, $scale); |
|||
50 | } else { |
||||
51 | 45 | $number = static::mul($pow, $regs[1], $scale); |
|||
52 | } |
||||
53 | 76 | $number = static::formatTrailingZeroes($number); |
|||
54 | } |
||||
55 | |||||
56 | 397 | return static::parseNumber($number); |
|||
57 | } |
||||
58 | |||||
59 | 116 | public static function getDecimalsLength(string $number): int |
|||
60 | { |
||||
61 | 116 | if (static::isFloat($number)) { |
|||
62 | 105 | return strcspn(strrev($number), '.'); |
|||
63 | } |
||||
64 | |||||
65 | 11 | return 0; |
|||
66 | } |
||||
67 | |||||
68 | 362 | protected static function isFloat(string $number): bool |
|||
69 | { |
||||
70 | 362 | return strpos($number, '.') !== false; |
|||
71 | } |
||||
72 | |||||
73 | 170 | public static function pow(string $base, string $exponent, ?int $scale = null): string |
|||
74 | { |
||||
75 | 170 | $base = static::convertScientificNotationToString($base); |
|||
76 | 170 | $exponent = static::convertScientificNotationToString($exponent); |
|||
77 | |||||
78 | 170 | if (static::isFloat($exponent)) { |
|||
79 | 9 | $r = static::powFractional($base, $exponent, $scale); |
|||
80 | 162 | } elseif ($scale === null) { |
|||
81 | 78 | $r = bcpow($base, $exponent); |
|||
82 | } else { |
||||
83 | 87 | $r = bcpow($base, $exponent, $scale); |
|||
84 | } |
||||
85 | |||||
86 | 170 | return static::formatTrailingZeroes($r); |
|||
87 | } |
||||
88 | |||||
89 | 9 | protected static function powFractional(string $base, string $exponent, ?int $scale = null): string |
|||
90 | { |
||||
91 | // we need to increased scale to get correct results and avoid rounding error |
||||
92 | 9 | $currentScale = $scale ?? static::getScale(); |
|||
93 | 9 | $increasedScale = $currentScale * 2; |
|||
94 | |||||
95 | // add zero to trim scale |
||||
96 | 9 | return static::parseNumber( |
|||
97 | 9 | static::add( |
|||
98 | 9 | static::exp(static::mul($exponent, static::log($base), $increasedScale)), |
|||
99 | 9 | '0', |
|||
100 | 9 | $currentScale |
|||
101 | 9 | ) |
|||
102 | 9 | ); |
|||
103 | } |
||||
104 | |||||
105 | 40 | public static function getScale(): int |
|||
106 | { |
||||
107 | 40 | return bcscale(); |
|||
108 | } |
||||
109 | |||||
110 | 397 | protected static function parseNumber(string $number): string |
|||
111 | { |
||||
112 | 397 | $number = str_replace('+', '', (string)filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)); |
|||
113 | 397 | if ($number === '-0' || !is_numeric($number)) { |
|||
114 | 29 | return '0'; |
|||
115 | } |
||||
116 | |||||
117 | 386 | return $number; |
|||
118 | } |
||||
119 | |||||
120 | 183 | public static function add(string $leftOperand, string $rightOperand, ?int $scale = null): string |
|||
121 | { |
||||
122 | 183 | $leftOperand = static::convertScientificNotationToString($leftOperand); |
|||
123 | 183 | $rightOperand = static::convertScientificNotationToString($rightOperand); |
|||
124 | |||||
125 | 183 | if ($scale === null) { |
|||
126 | 41 | $r = bcadd($leftOperand, $rightOperand); |
|||
127 | } else { |
||||
128 | 145 | $r = bcadd($leftOperand, $rightOperand, $scale); |
|||
129 | } |
||||
130 | |||||
131 | 183 | return static::formatTrailingZeroes($r); |
|||
132 | } |
||||
133 | |||||
134 | 309 | protected static function formatTrailingZeroes(string $number): string |
|||
135 | { |
||||
136 | 309 | if (self::$trimTrailingZeroes) { |
|||
137 | 309 | return static::trimTrailingZeroes($number); |
|||
138 | } |
||||
139 | |||||
140 | 1 | return $number; |
|||
141 | } |
||||
142 | |||||
143 | 335 | protected static function trimTrailingZeroes(string $number): string |
|||
144 | { |
||||
145 | 335 | if (static::isFloat($number)) { |
|||
146 | 229 | $number = rtrim($number, '0'); |
|||
147 | } |
||||
148 | |||||
149 | 335 | return rtrim($number, '.') ?: '0'; |
|||
150 | } |
||||
151 | |||||
152 | 17 | public static function exp(string $number): string |
|||
153 | { |
||||
154 | 17 | $scale = static::DEFAULT_SCALE; |
|||
155 | 17 | $result = '1'; |
|||
156 | 17 | for ($i = 299; $i > 0; --$i) { |
|||
157 | 17 | $result = static::add(static::mul(static::div($result, (string)$i, $scale), $number, $scale), '1', $scale); |
|||
158 | } |
||||
159 | |||||
160 | 17 | return $result; |
|||
161 | } |
||||
162 | |||||
163 | 184 | public static function mul(string $leftOperand, string $rightOperand, ?int $scale = null): string |
|||
164 | { |
||||
165 | 184 | $leftOperand = static::convertScientificNotationToString($leftOperand); |
|||
166 | 184 | $rightOperand = static::convertScientificNotationToString($rightOperand); |
|||
167 | |||||
168 | 184 | if ($scale === null) { |
|||
169 | 55 | $r = bcmul($leftOperand, $rightOperand); |
|||
170 | } else { |
||||
171 | 132 | $r = bcmul($leftOperand, $rightOperand, $scale); |
|||
172 | } |
||||
173 | |||||
174 | 184 | return static::formatTrailingZeroes($r); |
|||
175 | } |
||||
176 | |||||
177 | 145 | public static function div(string $dividend, string $divisor, ?int $scale = null): string |
|||
178 | { |
||||
179 | 145 | $dividend = static::convertScientificNotationToString($dividend); |
|||
180 | 145 | $divisor = static::convertScientificNotationToString($divisor); |
|||
181 | |||||
182 | 145 | if (static::trimTrailingZeroes($divisor) === '0') { |
|||
183 | 1 | throw new InvalidArgumentException('Division by zero'); |
|||
184 | } |
||||
185 | |||||
186 | 144 | if ($scale === null) { |
|||
187 | 42 | $r = bcdiv($dividend, $divisor); |
|||
188 | } else { |
||||
189 | 111 | $r = bcdiv($dividend, $divisor, $scale); |
|||
190 | } |
||||
191 | |||||
192 | 144 | return static::formatTrailingZeroes((string)$r); |
|||
193 | } |
||||
194 | |||||
195 | 14 | public static function log(string $number): string |
|||
196 | { |
||||
197 | 14 | $number = static::convertScientificNotationToString($number); |
|||
198 | 14 | if ($number === '0') { |
|||
199 | 1 | return '-INF'; |
|||
200 | } |
||||
201 | 13 | if (static::COMPARE_RIGHT_GRATER === static::comp($number, '0')) { |
|||
202 | 1 | return 'NAN'; |
|||
203 | } |
||||
204 | 12 | $scale = static::DEFAULT_SCALE; |
|||
205 | 12 | $m = (string)log((float)$number); |
|||
206 | 12 | $x = static::sub(static::div($number, static::exp($m), $scale), '1', $scale); |
|||
207 | 12 | $res = '0'; |
|||
208 | 12 | $pow = '1'; |
|||
209 | 12 | $i = 1; |
|||
210 | do { |
||||
211 | 12 | $pow = static::mul($pow, $x, $scale); |
|||
212 | 12 | $sum = static::div($pow, (string)$i, $scale); |
|||
213 | 12 | if ($i % 2 === 1) { |
|||
214 | 12 | $res = static::add($res, $sum, $scale); |
|||
215 | } else { |
||||
216 | 11 | $res = static::sub($res, $sum, $scale); |
|||
217 | } |
||||
218 | 12 | ++$i; |
|||
219 | 12 | } while (static::comp($sum, '0', $scale)); |
|||
220 | |||||
221 | 12 | return static::add($res, $m, $scale); |
|||
222 | } |
||||
223 | |||||
224 | 66 | public static function comp(string $leftOperand, string $rightOperand, ?int $scale = null): int |
|||
225 | { |
||||
226 | 66 | $leftOperand = static::convertScientificNotationToString($leftOperand); |
|||
227 | 66 | $rightOperand = static::convertScientificNotationToString($rightOperand); |
|||
228 | |||||
229 | 66 | if ($scale === null) { |
|||
230 | 54 | return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand))); |
|||
231 | } |
||||
232 | |||||
233 | 24 | return bccomp( |
|||
234 | 24 | $leftOperand, |
|||
235 | 24 | $rightOperand, |
|||
236 | 24 | $scale |
|||
237 | 24 | ); |
|||
238 | } |
||||
239 | |||||
240 | 83 | public static function sub(string $leftOperand, string $rightOperand, ?int $scale = null): string |
|||
241 | { |
||||
242 | 83 | $leftOperand = static::convertScientificNotationToString($leftOperand); |
|||
243 | 83 | $rightOperand = static::convertScientificNotationToString($rightOperand); |
|||
244 | |||||
245 | 83 | if ($scale === null) { |
|||
246 | 44 | $r = bcsub($leftOperand, $rightOperand); |
|||
247 | } else { |
||||
248 | 42 | $r = bcsub($leftOperand, $rightOperand, $scale); |
|||
249 | } |
||||
250 | |||||
251 | 83 | return static::formatTrailingZeroes($r); |
|||
252 | } |
||||
253 | |||||
254 | 6 | public static function sqrt(string $number, ?int $scale = null): string |
|||
255 | { |
||||
256 | 6 | $number = static::convertScientificNotationToString($number); |
|||
257 | |||||
258 | 6 | if ($scale === null) { |
|||
259 | 1 | $r = bcsqrt($number); |
|||
260 | } else { |
||||
261 | 6 | $r = bcsqrt($number, $scale); |
|||
262 | } |
||||
263 | |||||
264 | 6 | return static::formatTrailingZeroes((string)$r); |
|||
265 | } |
||||
266 | |||||
267 | 1 | public static function setTrimTrailingZeroes(bool $flag): void |
|||
268 | { |
||||
269 | 1 | self::$trimTrailingZeroes = $flag; |
|||
270 | } |
||||
271 | |||||
272 | /** |
||||
273 | * @param mixed $values |
||||
274 | */ |
||||
275 | 1 | public static function max(...$values): ?string |
|||
276 | { |
||||
277 | 1 | $max = null; |
|||
278 | 1 | foreach (static::parseValues($values) as $number) { |
|||
279 | 1 | $number = static::convertScientificNotationToString((string)$number); |
|||
280 | 1 | if ($max === null) { |
|||
281 | 1 | $max = $number; |
|||
282 | 1 | } elseif (static::comp($max, $number) === static::COMPARE_RIGHT_GRATER) { |
|||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
283 | 1 | $max = $number; |
|||
284 | } |
||||
285 | } |
||||
286 | |||||
287 | 1 | return $max; |
|||
288 | } |
||||
289 | |||||
290 | 2 | protected static function parseValues(array $values): array |
|||
291 | { |
||||
292 | 2 | if (is_array($values[0])) { |
|||
293 | 2 | $values = $values[0]; |
|||
294 | } |
||||
295 | |||||
296 | 2 | return $values; |
|||
297 | } |
||||
298 | |||||
299 | /** |
||||
300 | * @param mixed $values |
||||
301 | */ |
||||
302 | 1 | public static function min(...$values): ?string |
|||
303 | { |
||||
304 | 1 | $min = null; |
|||
305 | 1 | foreach (static::parseValues($values) as $number) { |
|||
306 | 1 | $number = static::convertScientificNotationToString((string)$number); |
|||
307 | 1 | if ($min === null) { |
|||
308 | 1 | $min = $number; |
|||
309 | 1 | } elseif (static::comp($min, $number) === static::COMPARE_LEFT_GRATER) { |
|||
0 ignored issues
–
show
$min of type null is incompatible with the type string expected by parameter $leftOperand of BCMathExtended\BC::comp() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
310 | 1 | $min = $number; |
|||
311 | } |
||||
312 | } |
||||
313 | |||||
314 | 1 | return $min; |
|||
315 | } |
||||
316 | |||||
317 | 14 | public static function powMod( |
|||
318 | string $base, |
||||
319 | string $exponent, |
||||
320 | string $modulus, |
||||
321 | ?int $scale = null |
||||
322 | ): string { |
||||
323 | 14 | $base = static::convertScientificNotationToString($base); |
|||
324 | 14 | $exponent = static::convertScientificNotationToString($exponent); |
|||
325 | |||||
326 | 14 | if (static::isNegative($exponent)) { |
|||
327 | 1 | throw new InvalidArgumentException('Exponent can\'t be negative'); |
|||
328 | } |
||||
329 | |||||
330 | 13 | if (static::trimTrailingZeroes($modulus) === '0') { |
|||
331 | 1 | throw new InvalidArgumentException('Modulus can\'t be zero'); |
|||
332 | } |
||||
333 | |||||
334 | // bcpowmod don't support floats |
||||
335 | if ( |
||||
336 | 12 | static::isFloat($base) |
|||
337 | 9 | || static::isFloat($exponent) |
|||
338 | 12 | || static::isFloat($modulus) |
|||
339 | ) { |
||||
340 | 5 | $r = static::mod(static::pow($base, $exponent, $scale), $modulus, $scale); |
|||
341 | 7 | } elseif ($scale === null) { |
|||
342 | 1 | $r = bcpowmod($base, $exponent, $modulus); |
|||
343 | } else { |
||||
344 | 6 | $r = bcpowmod($base, $exponent, $modulus, $scale); |
|||
345 | } |
||||
346 | |||||
347 | 12 | return static::formatTrailingZeroes((string)$r); |
|||
348 | } |
||||
349 | |||||
350 | 182 | protected static function isNegative(string $number): bool |
|||
351 | { |
||||
352 | 182 | return strncmp('-', $number, 1) === 0; |
|||
353 | } |
||||
354 | |||||
355 | 55 | public static function mod(string $dividend, string $divisor, ?int $scale = null): string |
|||
356 | { |
||||
357 | // bcmod is not working properly - for example bcmod(9.9999E-10, -0.00056, 9) should return '-0.000559999' but returns 0.0000000 |
||||
358 | // let use this $x - floor($x/$y) * $y; |
||||
359 | 55 | return static::formatTrailingZeroes( |
|||
360 | 55 | static::sub( |
|||
361 | 55 | $dividend, |
|||
362 | 55 | static::mul(static::floor(static::div($dividend, $divisor, $scale)), $divisor, $scale), |
|||
363 | 55 | $scale |
|||
364 | 55 | ) |
|||
365 | 55 | ); |
|||
366 | } |
||||
367 | |||||
368 | 102 | public static function floor(string $number): string |
|||
369 | { |
||||
370 | 102 | $number = static::trimTrailingZeroes(static::convertScientificNotationToString($number)); |
|||
371 | 102 | if (static::isFloat($number)) { |
|||
372 | 46 | $result = 0; |
|||
373 | 46 | if (static::isNegative($number)) { |
|||
374 | 9 | --$result; |
|||
375 | } |
||||
376 | 46 | $number = static::add($number, (string)$result, 0); |
|||
377 | } |
||||
378 | |||||
379 | 102 | return static::parseNumber($number); |
|||
380 | } |
||||
381 | |||||
382 | 8 | public static function fact(string $number): string |
|||
383 | { |
||||
384 | 8 | $number = static::convertScientificNotationToString($number); |
|||
385 | |||||
386 | 8 | if (static::isFloat($number)) { |
|||
387 | 1 | throw new InvalidArgumentException('Number has to be an integer'); |
|||
388 | } |
||||
389 | 7 | if (static::isNegative($number)) { |
|||
390 | 1 | throw new InvalidArgumentException('Number has to be greater than or equal to 0'); |
|||
391 | } |
||||
392 | |||||
393 | 6 | $return = '1'; |
|||
394 | 6 | for ($i = 2; $i <= $number; ++$i) { |
|||
395 | 5 | $return = static::mul($return, (string)$i); |
|||
396 | } |
||||
397 | |||||
398 | 6 | return $return; |
|||
399 | } |
||||
400 | |||||
401 | 7 | public static function hexdec(string $hex): string |
|||
402 | { |
||||
403 | 7 | $remainingDigits = str_replace('0x', '', substr($hex, 0, -1)); |
|||
404 | 7 | $lastDigitToDecimal = (string)hexdec(substr($hex, -1)); |
|||
405 | |||||
406 | 7 | if ($remainingDigits === '') { |
|||
407 | 7 | return $lastDigitToDecimal; |
|||
408 | } |
||||
409 | |||||
410 | 7 | return static::add(static::mul('16', static::hexdec($remainingDigits)), $lastDigitToDecimal, 0); |
|||
411 | } |
||||
412 | |||||
413 | 6 | public static function dechex(string $decimal): string |
|||
414 | { |
||||
415 | 6 | $quotient = static::div($decimal, '16', 0); |
|||
416 | 6 | $remainderToHex = dechex((int)static::mod($decimal, '16')); |
|||
417 | |||||
418 | 6 | if (static::comp($quotient, '0') === static::COMPARE_EQUAL) { |
|||
419 | 6 | return $remainderToHex; |
|||
420 | } |
||||
421 | |||||
422 | 6 | return static::dechex($quotient) . $remainderToHex; |
|||
423 | } |
||||
424 | |||||
425 | 12 | public static function bitAnd( |
|||
426 | string $leftOperand, |
||||
427 | string $rightOperand |
||||
428 | ): string { |
||||
429 | 12 | return static::bitOperatorHelper($leftOperand, $rightOperand, static::BIT_OPERATOR_AND); |
|||
430 | } |
||||
431 | |||||
432 | 36 | protected static function bitOperatorHelper(string $leftOperand, string $rightOperand, string $operator): string |
|||
433 | { |
||||
434 | 36 | $leftOperand = static::convertScientificNotationToString($leftOperand); |
|||
435 | 36 | $rightOperand = static::convertScientificNotationToString($rightOperand); |
|||
436 | |||||
437 | 36 | if (static::isFloat($leftOperand)) { |
|||
438 | 3 | throw new InvalidArgumentException('Left operator has to be an integer'); |
|||
439 | } |
||||
440 | 33 | if (static::isFloat($rightOperand)) { |
|||
441 | 3 | throw new InvalidArgumentException('Right operator has to be an integer'); |
|||
442 | } |
||||
443 | |||||
444 | 30 | $leftOperandNegative = static::isNegative($leftOperand); |
|||
445 | 30 | $rightOperandNegative = static::isNegative($rightOperand); |
|||
446 | |||||
447 | 30 | $leftOperand = static::dec2bin(static::abs($leftOperand)); |
|||
448 | 30 | $rightOperand = static::dec2bin(static::abs($rightOperand)); |
|||
449 | |||||
450 | 30 | $maxLength = max(strlen($leftOperand), strlen($rightOperand)); |
|||
451 | |||||
452 | 30 | $leftOperand = static::alignBinLength($leftOperand, $maxLength); |
|||
453 | 30 | $rightOperand = static::alignBinLength($rightOperand, $maxLength); |
|||
454 | |||||
455 | 30 | if ($leftOperandNegative) { |
|||
456 | 7 | $leftOperand = static::recalculateNegative($leftOperand); |
|||
457 | } |
||||
458 | 30 | if ($rightOperandNegative) { |
|||
459 | 7 | $rightOperand = static::recalculateNegative($rightOperand); |
|||
460 | } |
||||
461 | |||||
462 | 30 | $isNegative = false; |
|||
463 | 30 | $result = ''; |
|||
464 | 30 | if (static::BIT_OPERATOR_AND === $operator) { |
|||
465 | 10 | $result = $leftOperand & $rightOperand; |
|||
466 | 10 | $isNegative = ($leftOperandNegative and $rightOperandNegative); |
|||
467 | 20 | } elseif (static::BIT_OPERATOR_OR === $operator) { |
|||
468 | 12 | $result = $leftOperand | $rightOperand; |
|||
469 | 12 | $isNegative = ($leftOperandNegative or $rightOperandNegative); |
|||
470 | 8 | } elseif (static::BIT_OPERATOR_XOR === $operator) { |
|||
471 | 8 | $result = $leftOperand ^ $rightOperand; |
|||
472 | 8 | $isNegative = ($leftOperandNegative xor $rightOperandNegative); |
|||
473 | } |
||||
474 | |||||
475 | 30 | if ($isNegative) { |
|||
476 | 8 | $result = static::recalculateNegative($result); |
|||
477 | } |
||||
478 | |||||
479 | 30 | $result = static::bin2dec($result); |
|||
480 | |||||
481 | 30 | return $isNegative ? '-' . $result : $result; |
|||
482 | } |
||||
483 | |||||
484 | 36 | public static function dec2bin(string $number, int $base = self::MAX_BASE): string |
|||
485 | { |
||||
486 | 36 | return static::decBaseHelper( |
|||
487 | 36 | $base, |
|||
488 | 36 | static function (int $base) use ($number) { |
|||
489 | 35 | $value = ''; |
|||
490 | 35 | if ($number === '0') { |
|||
491 | 30 | return chr((int)$number); |
|||
492 | } |
||||
493 | |||||
494 | 33 | while (self::comp($number, '0') !== self::COMPARE_EQUAL) { |
|||
495 | 33 | $rest = self::mod($number, (string)$base); |
|||
496 | 33 | $number = self::div($number, (string)$base); |
|||
497 | 33 | $value = chr((int)$rest) . $value; |
|||
498 | } |
||||
499 | |||||
500 | 33 | return $value; |
|||
501 | 36 | } |
|||
502 | 36 | ); |
|||
503 | } |
||||
504 | |||||
505 | 37 | protected static function decBaseHelper(int $base, Closure $closure): string |
|||
506 | { |
||||
507 | 37 | if ($base < 2 || $base > static::MAX_BASE) { |
|||
508 | 2 | throw new InvalidArgumentException('Invalid Base: ' . $base); |
|||
509 | } |
||||
510 | 35 | $orgScale = static::getScale(); |
|||
511 | 35 | static::setScale(0); |
|||
512 | |||||
513 | 35 | $value = $closure($base); |
|||
514 | |||||
515 | 35 | static::setScale($orgScale); |
|||
516 | |||||
517 | 35 | return $value; |
|||
518 | } |
||||
519 | |||||
520 | 402 | public static function setScale(int $scale): void |
|||
521 | { |
||||
522 | 402 | bcscale($scale); |
|||
523 | } |
||||
524 | |||||
525 | 45 | public static function abs(string $number): string |
|||
526 | { |
||||
527 | 45 | $number = static::convertScientificNotationToString($number); |
|||
528 | |||||
529 | 45 | if (static::isNegative($number)) { |
|||
530 | 19 | $number = (string)substr($number, 1); |
|||
531 | } |
||||
532 | |||||
533 | 45 | return static::parseNumber($number); |
|||
534 | } |
||||
535 | |||||
536 | 30 | protected static function alignBinLength(string $string, int $length): string |
|||
537 | { |
||||
538 | 30 | return str_pad($string, $length, static::dec2bin('0'), STR_PAD_LEFT); |
|||
539 | } |
||||
540 | |||||
541 | 11 | protected static function recalculateNegative(string $number): string |
|||
542 | { |
||||
543 | 11 | $xor = str_repeat(static::dec2bin((string)(static::MAX_BASE - 1)), strlen($number)); |
|||
544 | 11 | $number ^= $xor; |
|||
545 | 11 | for ($i = strlen($number) - 1; $i >= 0; --$i) { |
|||
546 | 11 | $byte = ord($number[$i]); |
|||
547 | 11 | if (++$byte !== static::MAX_BASE) { |
|||
548 | 11 | $number[$i] = chr($byte); |
|||
549 | 11 | break; |
|||
550 | } |
||||
551 | } |
||||
552 | |||||
553 | 11 | return $number; |
|||
554 | } |
||||
555 | |||||
556 | 36 | public static function bin2dec(string $binary, int $base = self::MAX_BASE): string |
|||
557 | { |
||||
558 | 36 | return static::decBaseHelper( |
|||
559 | 36 | $base, |
|||
560 | 36 | static function (int $base) use ($binary) { |
|||
561 | 35 | $size = strlen($binary); |
|||
562 | 35 | $return = '0'; |
|||
563 | 35 | for ($i = 0; $i < $size; ++$i) { |
|||
564 | 35 | $element = ord($binary[$i]); |
|||
565 | 35 | $power = self::pow((string)$base, (string)($size - $i - 1)); |
|||
566 | 35 | $return = self::add($return, self::mul((string)$element, $power)); |
|||
567 | } |
||||
568 | |||||
569 | 35 | return $return; |
|||
570 | 36 | } |
|||
571 | 36 | ); |
|||
572 | } |
||||
573 | |||||
574 | 14 | public static function bitOr(string $leftOperand, string $rightOperand): string |
|||
575 | { |
||||
576 | 14 | return static::bitOperatorHelper($leftOperand, $rightOperand, static::BIT_OPERATOR_OR); |
|||
577 | } |
||||
578 | |||||
579 | 10 | public static function bitXor(string $leftOperand, string $rightOperand): string |
|||
580 | { |
||||
581 | 10 | return static::bitOperatorHelper($leftOperand, $rightOperand, static::BIT_OPERATOR_XOR); |
|||
582 | } |
||||
583 | |||||
584 | 26 | public static function roundHalfEven(string $number, int $precision = 0): string |
|||
585 | { |
||||
586 | 26 | $number = static::convertScientificNotationToString($number); |
|||
587 | 26 | if (!static::isFloat($number)) { |
|||
588 | 1 | return $number; |
|||
589 | } |
||||
590 | |||||
591 | 25 | $precessionPos = strpos($number, '.') + $precision + 1; |
|||
592 | 25 | if (strlen($number) <= $precessionPos) { |
|||
593 | 1 | return static::round($number, $precision); |
|||
594 | } |
||||
595 | |||||
596 | 24 | if ($number[-1] !== '5') { |
|||
597 | 9 | return static::round($number, $precision); |
|||
598 | } |
||||
599 | |||||
600 | 15 | $isPrevEven = $number[$precessionPos - 1] === '.' |
|||
601 | 4 | ? (int)$number[$precessionPos - 2] % 2 === 0 |
|||
602 | 15 | : (int)$number[$precessionPos - 1] % 2 === 0; |
|||
603 | 15 | $isNegative = static::isNegative($number); |
|||
604 | |||||
605 | 15 | if ($isPrevEven === $isNegative) { |
|||
606 | 7 | return static::roundUp($number, $precision); |
|||
607 | } |
||||
608 | |||||
609 | 8 | return static::roundDown($number, $precision); |
|||
610 | } |
||||
611 | |||||
612 | 58 | public static function round(string $number, int $precision = 0): string |
|||
613 | { |
||||
614 | 58 | $number = static::convertScientificNotationToString($number); |
|||
615 | 58 | if (static::isFloat($number)) { |
|||
616 | 47 | if (static::isNegative($number)) { |
|||
617 | 9 | $number = static::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision); |
|||
618 | } else { |
||||
619 | 38 | $number = static::add($number, '0.' . str_repeat('0', $precision) . '5', $precision); |
|||
620 | } |
||||
621 | } |
||||
622 | |||||
623 | 58 | return static::parseNumber($number); |
|||
624 | } |
||||
625 | |||||
626 | 24 | public static function roundUp(string $number, int $precision = 0): string |
|||
627 | { |
||||
628 | 24 | $number = static::convertScientificNotationToString($number); |
|||
629 | 24 | if (!static::isFloat($number)) { |
|||
630 | 4 | return $number; |
|||
631 | } |
||||
632 | 20 | $multiply = static::pow('10', (string)abs($precision)); |
|||
633 | |||||
634 | 20 | return static::parseNumber( |
|||
635 | 20 | $precision < 0 |
|||
636 | 20 | ? |
|||
637 | 4 | static::mul( |
|||
638 | 4 | static::ceil(static::div($number, $multiply, static::getDecimalsLength($number))), |
|||
639 | 4 | $multiply, |
|||
640 | 4 | (int)abs($precision) |
|||
641 | 4 | ) |
|||
642 | 20 | : |
|||
643 | 20 | static::div( |
|||
644 | 20 | static::ceil(static::mul($number, $multiply, static::getDecimalsLength($number))), |
|||
645 | 20 | $multiply, |
|||
646 | 20 | $precision |
|||
647 | 20 | ) |
|||
648 | 20 | ); |
|||
649 | } |
||||
650 | |||||
651 | 44 | public static function ceil(string $number): string |
|||
652 | { |
||||
653 | 44 | $number = static::trimTrailingZeroes(static::convertScientificNotationToString($number)); |
|||
654 | 44 | if (static::isFloat($number)) { |
|||
655 | 29 | $result = 1; |
|||
656 | 29 | if (static::isNegative($number)) { |
|||
657 | 6 | --$result; |
|||
658 | } |
||||
659 | 29 | $number = static::add($number, (string)$result, 0); |
|||
660 | } |
||||
661 | |||||
662 | 44 | return static::parseNumber($number); |
|||
663 | } |
||||
664 | |||||
665 | 26 | public static function roundDown(string $number, int $precision = 0): string |
|||
666 | { |
||||
667 | 26 | $number = static::convertScientificNotationToString($number); |
|||
668 | 26 | if (!static::isFloat($number)) { |
|||
669 | 4 | return $number; |
|||
670 | } |
||||
671 | 22 | $multiply = static::pow('10', (string)abs($precision)); |
|||
672 | |||||
673 | 22 | return static::parseNumber( |
|||
674 | 22 | $precision < 0 |
|||
675 | 22 | ? |
|||
676 | 4 | static::mul( |
|||
677 | 4 | static::floor(static::div($number, $multiply, static::getDecimalsLength($number))), |
|||
678 | 4 | $multiply, |
|||
679 | 4 | (int)abs($precision) |
|||
680 | 4 | ) |
|||
681 | 22 | : |
|||
682 | 22 | static::div( |
|||
683 | 22 | static::floor(static::mul($number, $multiply, static::getDecimalsLength($number))), |
|||
684 | 22 | $multiply, |
|||
685 | 22 | $precision |
|||
686 | 22 | ) |
|||
687 | 22 | ); |
|||
688 | } |
||||
689 | } |
||||
690 |