krowinski /
bcmath-extended
| 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 |