├── LICENSE.md ├── README.md ├── composer.json ├── lib └── BigInteger.php └── test ├── perf.php ├── test.php ├── testbc.php └── testgmp.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | ## MIT LICENSE 3 | 4 | Copyright (C) 2018 Simplito 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to permit 11 | persons to whom the Software is furnished to do so, subject to the 12 | following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 21 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 22 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 23 | USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # BigInteger wrapper library for PHP 3 | 4 | ## Information 5 | 6 | This library is a common interface for php_gmp and php_bcmath modules. It automatically detects supported modules and uses the best of them (gmp>bcmath). Gmp is a lot faster, but is also missing on many hosting services -- that is why this wrapper has been created. It is used for example in encryption functions of the [PrivMX WebMail](https://privmx.com) software. 7 | 8 | ## Installation 9 | 10 | You can install this library via Composer: 11 | ``` 12 | composer require simplito/bigint-wrapper-php 13 | ``` 14 | 15 | ## Documentation 16 | 17 | If you want to force using a specific implementation, then define constant S_MATH_BIGINTEGER_MODE - set it to "gmp" or "bcmath". If you do not do this, mode of operation and the constant will be set automatically. 18 | 19 | If there are no gmp and bcmath modules, an exception will be thrown. If you want to prevent this, then simply define S_MATH_BIGINTEGER_QUIET constant. 20 | 21 | All functions of this library are implemented as members of class BigInteger, which is located under BI namespace. Instances of BigInteger are immutable - member functions usually return new instances of the BigInteger class. 22 | 23 | 24 | ### ConvertibleToBi - a placeholder type 25 | 26 | To make the below documentation more readable we use the "ConvertibleToBi" type symbol, which in reality can be one of the following types: 27 | - an instance of the BigInteger class 28 | - an integer 29 | - a decimal string 30 | - a gmp resource or class (only when you are in gmp mode) 31 | 32 | If you have a non-decimal string and want to use it -- first you have to convert it to BigInteger class using: 33 | ``` 34 | new BigInteger($myNonDecimalString, $baseOfMyNonDecimalString) 35 | ``` 36 | 37 | ### BI\BigInteger class members 38 | 39 | #### construct(ConvertibleToBi $value = 0, int $base = 10) 40 | Creates a new instance of BigInteger. If you pass an invalid value, an exception will be thrown. If $base === true then passed $value will be used without any check and conversion. Supported bases: 2, 10, 16, 256. 41 | - **GMP implementation:** gmp_init + bin2hex for 256 base 42 | - **Bcmath implementation:** custom(bcadd + bcmul) 43 | 44 | #### static BigInteger|false createSafe(ConvertibleToBi $value = 0, int $base = 10) 45 | Creates a new BigInteger instance in the same way as constructor, but if there is an error, false will be returned instead of throwing an exception. 46 | 47 | #### BigInteger add(ConvertibleToBi $x) 48 | Adds numbers 49 | - **GMP implementation:** gmp_add 50 | - **Bcmath implementation:** bcadd 51 | 52 | #### BigInteger sub(ConvertibleToBi $x) 53 | Subtracts numbers 54 | - **GMP implementation:** gmp_sub 55 | - **Bcmath implementation:** bcsub 56 | 57 | #### BigInteger mul(ConvertibleToBi $x) 58 | Multiplies numbers 59 | - **GMP implementation:** gmp_mul 60 | - **Bcmath implementation:** bcmul 61 | 62 | #### BigInteger div(ConvertibleToBi $x) 63 | Divides numbers 64 | - **GMP implementation:** gmp_div_q 65 | - **Bcmath implementation:** bcdiv 66 | 67 | #### BigInteger divR(ConvertibleToBi $x) 68 | Returns a remainder of the division of numbers. The remainder has the sign of the divided number. 69 | - **GMP implementation:** gmp_div_r 70 | - **Bcmath implementation:** bcmod 71 | 72 | #### array(BigInteger, BigInteger) divQR(ConvertibleToBi $x) 73 | Divides numbers and returns quotient and remainder. Returns an array(), with the first element being quotient, and the second being remainder. 74 | - **GMP implementation:** gmp_div_qr 75 | - **Bcmath implementation:** div + divR 76 | 77 | #### BigInteger mod(ConvertibleToBi $x) 78 | The "division modulo" operation. The result is always non-negative, the sign of divider is ignored. 79 | - **GMP implementation:** gmp_mod 80 | - **Bcmath implementation:** custom (bcmod + bcadd) 81 | 82 | #### BigInteger gcd(ConvertibleToBi $x) 83 | Calculates greatest common divisor 84 | - **GMP implementation:** gmp_gcd 85 | - **Bcmath implementation:** custom (bccomp + bcdiv + bcsub + bcmul) 86 | 87 | #### BigInteger|false modInverse(ConvertibleToBi $x) 88 | Inverses by modulo, returns false if inversion does not exist. 89 | - **GMP implementation:** gmp_invert 90 | - **Bcmath implementation:** custom (gcd) 91 | 92 | #### BigInteger pow(ConvertibleToBi $x) 93 | The power function. 94 | - **GMP implementation:** gmp_pow 95 | - **Bcmath implementation:** bcpow 96 | 97 | #### BigInteger powMod(ConvertibleToBi $x, ConvertibleToBi $n) 98 | The modular power function. 99 | - **GMP implementation:** gmp_powm 100 | - **Bcmath implementation:** bcpowmod 101 | 102 | #### BigInteger abs() 103 | Returns absolute value. 104 | - **GMP implementation:** gmp_abs 105 | - **Bcmath implementation:** check first character 106 | 107 | #### BigInteger neg() 108 | Negates the number 109 | - **GMP implementation:** gmp_neg 110 | - **Bcmath implementation:** check first character 111 | 112 | #### BigInteger binaryAnd(ConvertibleToBi $x) 113 | Bitwise AND. 114 | - **GMP implementation:** gmp_and 115 | - **Bcmath implementation:** custom (toBytes + php string and) 116 | 117 | #### BigInteger binaryOr(ConvertibleToBi $x) 118 | Bitwise OR 119 | - **GMP implementation:** gmp_or 120 | - **Bcmath implementation:** custom (toBytes + php string or) 121 | 122 | #### BigInteger binaryXor(ConvertibleToBi $x) 123 | Bitwise XOR 124 | - **GMP implementation:** gmp_xor 125 | - **Bcmath implementation:** custom (toBytes + php string xor) 126 | 127 | #### BigInteger setbit($index, $bitOn = true) 128 | Sets bit at given index 129 | - **GMP implementation:** gmp_setbit 130 | - **Bcmath implementation:** custom (toBits) 131 | 132 | #### bool testbit($index) 133 | Tests if a bit at given index is set 134 | - **GMP implementation:** gmp_testbit 135 | - **Bcmath implementation:** custom (toBits) 136 | 137 | #### int scan0($start) 138 | Scans for 0, and returns index of first found bit 139 | - **GMP implementation:** gmp_scan0 140 | - **Bcmath implementation:** custom (toBits) 141 | 142 | #### int scan1($start) 143 | Scans for 1, and returns index of first found bit 144 | - **GMP implementation:** gmp_scan1 145 | - **Bcmath implementation:** custom (toBits) 146 | 147 | #### int cmp(ConvertibleToBi $x) 148 | Compares numbers, returns <0, 0, >0 149 | - **GMP implementation:** gmp_cmp 150 | - **Bcmath implementation:** bccomp 151 | 152 | #### bool equals(ConvertibleToBi $x) 153 | Checks if numbers are equal 154 | - **GMP implementation:** gmp_cmp 155 | - **Bcmath implementation:** bccomp 156 | 157 | #### int sign() 158 | Sign of number, returns -1, 0, 1 159 | - **GMP implementation:** gmp_sign 160 | - **Bcmath implementation:** check first character 161 | 162 | #### int toNumber() 163 | Converts to number (use only with small 32/64bit numbers) 164 | - **GMP implementation:** gmp_intval 165 | - **Bcmath implementation:** intval 166 | 167 | #### string toDec() 168 | Converts to decimal string 169 | - **GMP implementation:** gmp_strval 170 | - **Bcmath implementation:** just the value 171 | 172 | #### string toHex() 173 | Converts to hex string 174 | - **GMP implementation:** gmp_strval 175 | - **Bcmath implementation:** toBytes + bin2hex 176 | 177 | #### string toBytes 178 | Converts to binary string 179 | - **GMP implementation:** gmp_strval + hex2bin 180 | - **Bcmath implementation:** custom (bcmod + bcdiv + bccomp) 181 | 182 | #### string toBits() 183 | Converts to bits string (0 and 1 characters) 184 | - **GMP implementation:** gmp_strval 185 | - **Bcmath implementation:** toBytes + decbin 186 | 187 | #### string toString(int $base = 10) 188 | Converts to string using given base (supported bases 2-62, 256) 189 | - **GMP implementation:** all above toX functions, and for non standard gmp_strval 190 | - **Bcmath implementation:** all above toX functions, and for non standard bcmod + bcdiv + bccomp 191 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplito/bigint-wrapper-php", 3 | "description": "Common interface for php_gmp and php_bcmath modules", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Simplito Team", 8 | "email": "s.smyczynski@simplito.com", 9 | "homepage": "https://simplito.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "BI\\": "lib/" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/BigInteger.php: -------------------------------------------------------------------------------- 1 | value = $base === true ? $value : BigInteger::getGmp($value, $base); 31 | } 32 | 33 | public static function createSafe($value = 0, $base = 10) { 34 | try { 35 | return new BigInteger($value, $base); 36 | } 37 | catch (\Exception $e) { 38 | return false; 39 | } 40 | } 41 | 42 | public static function isGmp($var) { 43 | if (is_resource($var)) { 44 | return get_resource_type($var) == "GMP integer"; 45 | } 46 | if (class_exists("GMP") && $var instanceof \GMP) { 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | public static function getGmp($value = 0, $base = 10) { 53 | if ($value instanceof BigInteger) { 54 | return $value->value; 55 | } 56 | if (BigInteger::isGmp($value)) { 57 | return $value; 58 | } 59 | $type = gettype($value); 60 | if ($type == "integer") { 61 | $gmp = gmp_init($value); 62 | if ($gmp === false) { 63 | throw new \Exception("Cannot initialize"); 64 | } 65 | return $gmp; 66 | } 67 | if ($type == "string") { 68 | if ($base != 2 && $base != 10 && $base != 16 && $base != 256) { 69 | throw new \Exception("Unsupported BigInteger base"); 70 | } 71 | if ($base == 256) { 72 | $value = bin2hex($value); 73 | $base = 16; 74 | } 75 | $level = error_reporting(); 76 | error_reporting(0); 77 | $gmp = gmp_init($value, $base); 78 | error_reporting($level); 79 | if ($gmp === false) { 80 | throw new \Exception("Cannot initialize"); 81 | } 82 | return $gmp; 83 | } 84 | throw new \Exception("Unsupported value, only string and integer are allowed, receive " . $type . ($type == "object" ? ", class: " . get_class($value) : "")); 85 | } 86 | 87 | public function toDec() { 88 | return gmp_strval($this->value, 10); 89 | } 90 | 91 | public function toHex() { 92 | $hex = gmp_strval($this->value, 16); 93 | return strlen($hex) % 2 == 1 ? "0". $hex : $hex; 94 | } 95 | 96 | public function toBytes() { 97 | return hex2bin($this->toHex()); 98 | } 99 | 100 | public function toBase($base) { 101 | if ($base < 2 || $base > 62) { 102 | throw new \Exception("Invalid base"); 103 | } 104 | return gmp_strval($this->value, $base); 105 | } 106 | 107 | public function toBits() { 108 | return gmp_strval($this->value, 2); 109 | } 110 | 111 | public function toString($base = 10) { 112 | if ($base == 2) { 113 | return $this->toBits(); 114 | } 115 | if ($base == 10) { 116 | return $this->toDec(); 117 | } 118 | if ($base == 16) { 119 | return $this->toHex(); 120 | } 121 | if ($base == 256) { 122 | return $this->toBytes(); 123 | } 124 | return $this->toBase($base); 125 | } 126 | 127 | public function __toString() { 128 | return $this->toString(); 129 | } 130 | 131 | public function toNumber() { 132 | return gmp_intval($this->value); 133 | } 134 | 135 | public function add($x) { 136 | return new BigInteger(gmp_add($this->value, BigInteger::getGmp($x)), true); 137 | } 138 | 139 | public function sub($x) { 140 | return new BigInteger(gmp_sub($this->value, BigInteger::getGmp($x)), true); 141 | } 142 | 143 | public function mul($x) { 144 | return new BigInteger(gmp_mul($this->value, BigInteger::getGmp($x)), true); 145 | } 146 | 147 | public function div($x) { 148 | return new BigInteger(gmp_div_q($this->value, BigInteger::getGmp($x)), true); 149 | } 150 | 151 | public function divR($x) { 152 | return new BigInteger(gmp_div_r($this->value, BigInteger::getGmp($x)), true); 153 | } 154 | 155 | public function divQR($x) { 156 | $res = gmp_div_qr($this->value, BigInteger::getGmp($x)); 157 | return array(new BigInteger($res[0], true), new BigInteger($res[1], true)); 158 | } 159 | 160 | public function mod($x) { 161 | return new BigInteger(gmp_mod($this->value, BigInteger::getGmp($x)), true); 162 | } 163 | 164 | public function gcd($x) { 165 | return new BigInteger(gmp_gcd($this->value, BigInteger::getGmp($x)), true); 166 | } 167 | 168 | public function modInverse($x) { 169 | $res = gmp_invert($this->value, BigInteger::getGmp($x)); 170 | return $res === false ? false : new BigInteger($res, true); 171 | } 172 | 173 | public function pow($x) { 174 | return new BigInteger(gmp_pow($this->value, (new BigInteger($x))->toNumber()), true); 175 | } 176 | 177 | public function powMod($x, $n) { 178 | return new BigInteger(gmp_powm($this->value, BigInteger::getGmp($x), BigInteger::getGmp($n)), true); 179 | } 180 | 181 | public function abs() { 182 | return new BigInteger(gmp_abs($this->value), true); 183 | } 184 | 185 | public function neg() { 186 | return new BigInteger(gmp_neg($this->value), true); 187 | } 188 | 189 | public function binaryAnd($x) { 190 | return new BigInteger(gmp_and($this->value, BigInteger::getGmp($x)), true); 191 | } 192 | 193 | public function binaryOr($x) { 194 | return new BigInteger(gmp_or($this->value, BigInteger::getGmp($x)), true); 195 | } 196 | 197 | public function binaryXor($x) { 198 | return new BigInteger(gmp_xor($this->value, BigInteger::getGmp($x)), true); 199 | } 200 | 201 | public function setbit($index, $bitOn = true) { 202 | $cpy = gmp_init(gmp_strval($this->value, 16), 16); 203 | gmp_setbit($cpy, $index, $bitOn); 204 | return new BigInteger($cpy, true); 205 | } 206 | 207 | public function testbit($index) { 208 | return gmp_testbit($this->value, $index); 209 | } 210 | 211 | public function scan0($start) { 212 | return gmp_scan0($this->value, $start); 213 | } 214 | 215 | public function scan1($start) { 216 | return gmp_scan1($this->value, $start); 217 | } 218 | 219 | public function cmp($x) { 220 | return gmp_cmp($this->value, BigInteger::getGmp($x)); 221 | } 222 | 223 | public function equals($x) { 224 | return $this->cmp($x) === 0; 225 | } 226 | 227 | public function sign() { 228 | return gmp_sign($this->value); 229 | } 230 | } 231 | 232 | } 233 | else if (S_MATH_BIGINTEGER_MODE == "bcmath") { 234 | 235 | if (!extension_loaded("bcmath")) { 236 | throw new \Exception("Extension bcmath not loaded"); 237 | } 238 | 239 | class BigInteger{ 240 | 241 | public static $chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"; 242 | public $value; 243 | 244 | public function __construct($value = 0, $base = 10) { 245 | $this->value = $base === true ? $value : BigInteger::getBC($value, $base); 246 | } 247 | 248 | public static function createSafe($value = 0, $base = 10) { 249 | try { 250 | return new BigInteger($value, $base); 251 | } 252 | catch (\Exception $e) { 253 | return false; 254 | } 255 | } 256 | 257 | public static function checkBinary($str) { 258 | $len = strlen($str); 259 | for ($i = 0; $i < $len; $i++) { 260 | $c = ord($str[$i]); 261 | if (($i != 0 || $c != 45) && ($c < 48 || $c > 49)) { 262 | return false; 263 | } 264 | } 265 | return true; 266 | } 267 | 268 | public static function checkDecimal($str) { 269 | $len = strlen($str); 270 | for ($i = 0; $i < $len; $i++) { 271 | $c = ord($str[$i]); 272 | if (($i != 0 || $c != 45) && ($c < 48 || $c > 57)) { 273 | return false; 274 | } 275 | } 276 | return true; 277 | } 278 | 279 | public static function checkHex($str) { 280 | $len = strlen($str); 281 | for ($i = 0; $i < $len; $i++) { 282 | $c = ord($str[$i]); 283 | if (($i != 0 || $c != 45) && ($c < 48 || $c > 57) && ($c < 65 || $c > 70) && ($c < 97 || $c > 102)) { 284 | return false; 285 | } 286 | } 287 | return true; 288 | } 289 | 290 | public static function getBC($value = 0, $base = 10) { 291 | if ($value instanceof BigInteger) { 292 | return $value->value; 293 | } 294 | $type = gettype($value); 295 | if ($type == "integer") { 296 | return strval($value); 297 | } 298 | if ($type == "string") { 299 | if ($base == 2) { 300 | $value = str_replace(" ", "", $value); 301 | if (!BigInteger::checkBinary($value)) { 302 | throw new \Exception("Invalid characters"); 303 | } 304 | $minus = $value[0] == "-"; 305 | if ($minus) { 306 | $value = substr($value, 1); 307 | } 308 | $len = strlen($value); 309 | $m = 1; 310 | $res = "0"; 311 | for ($i = $len - 1; $i >= 0; $i -= 8) { 312 | $h = $i - 7 < 0 ? substr($value, 0, $i + 1) : substr($value, $i - 7, 8); 313 | $res = bcadd($res, bcmul(bindec($h), $m, 0), 0); 314 | $m = bcmul($m, "256", 0); 315 | } 316 | return ($minus ? "-" : "") . $res; 317 | } 318 | if ($base == 10) { 319 | $value = str_replace(" ", "", $value); 320 | if (!BigInteger::checkDecimal($value)) { 321 | throw new \Exception("Invalid characters"); 322 | } 323 | return $value; 324 | } 325 | if ($base == 16) { 326 | $value = str_replace(" ", "", $value); 327 | if (!BigInteger::checkHex($value)) { 328 | throw new \Exception("Invalid characters"); 329 | } 330 | $minus = $value[0] == "-"; 331 | if ($minus) { 332 | $value = substr($value, 1); 333 | } 334 | $len = strlen($value); 335 | $m = 1; 336 | $res = "0"; 337 | for ($i = $len - 1; $i >= 0; $i -= 2) { 338 | $h = $i == 0 ? "0" . substr($value, 0, 1) : substr($value, $i - 1, 2); 339 | $res = bcadd($res, bcmul(hexdec($h), $m, 0), 0); 340 | $m = bcmul($m, "256", 0); 341 | } 342 | return ($minus ? "-" : "") . $res; 343 | } 344 | if ($base == 256) { 345 | $len = strlen($value); 346 | $m = 1; 347 | $res = "0"; 348 | for ($i = $len - 1; $i >= 0; $i -= 6) { 349 | $h = $i - 5 < 0 ? substr($value, 0, $i + 1) : substr($value, $i - 5, 6); 350 | $res = bcadd($res, bcmul(base_convert(bin2hex($h), 16, 10), $m, 0), 0); 351 | $m = bcmul($m, "281474976710656", 0); 352 | } 353 | return $res; 354 | } 355 | throw new \Exception("Unsupported BigInteger base"); 356 | } 357 | throw new \Exception("Unsupported value, only string and integer are allowed, receive " . $type . ($type == "object" ? ", class: " . get_class($value) : "")); 358 | } 359 | 360 | public function toDec() { 361 | return $this->value; 362 | } 363 | 364 | public function toHex() { 365 | return bin2hex($this->toBytes()); 366 | } 367 | 368 | public function toBytes() { 369 | $value = ""; 370 | $current = $this->value; 371 | if ($current[0] == "-") { 372 | $current = substr($current, 1); 373 | } 374 | while (bccomp($current, "0", 0) > 0) { 375 | $temp = bcmod($current, "281474976710656"); 376 | $value = hex2bin(str_pad(base_convert($temp, 10, 16), 12, "0", STR_PAD_LEFT)) . $value; 377 | $current = bcdiv($current, "281474976710656", 0); 378 | } 379 | return ltrim($value, chr(0)); 380 | } 381 | 382 | public function toBase($base) { 383 | if ($base < 2 || $base > 62) { 384 | throw new \Exception("Invalid base"); 385 | } 386 | $value = ''; 387 | $current = $this->value; 388 | $base = BigInteger::getBC($base); 389 | 390 | if ($current[0] == '-') { 391 | $current = substr($current, 1); 392 | } 393 | 394 | while (bccomp($current, '0', 0) > 0) { 395 | $v = bcmod($current, $base); 396 | $value = BigInteger::$chars[$v] . $value; 397 | $current = bcdiv($current, $base, 0); 398 | } 399 | return $value; 400 | } 401 | 402 | public function toBits() { 403 | $bytes = $this->toBytes(); 404 | $res = ""; 405 | $len = strlen($bytes); 406 | for ($i = 0; $i < $len; $i++) { 407 | $b = decbin(ord($bytes[$i])); 408 | $res .= strlen($b) != 8 ? str_pad($b, 8, "0", STR_PAD_LEFT) : $b; 409 | } 410 | $res = ltrim($res, "0"); 411 | return strlen($res) == 0 ? "0" : $res; 412 | } 413 | 414 | public function toString($base = 10) { 415 | if ($base == 2) { 416 | return $this->toBits(); 417 | } 418 | if ($base == 10) { 419 | return $this->toDec(); 420 | } 421 | if ($base == 16) { 422 | return $this->toHex(); 423 | } 424 | if ($base == 256) { 425 | return $this->toBytes(); 426 | } 427 | return $this->toBase($base); 428 | } 429 | 430 | public function __toString() { 431 | return $this->toString(); 432 | } 433 | 434 | public function toNumber() { 435 | return intval($this->value); 436 | } 437 | 438 | public function add($x) { 439 | return new BigInteger(bcadd($this->value, BigInteger::getBC($x), 0), true); 440 | } 441 | 442 | public function sub($x) { 443 | return new BigInteger(bcsub($this->value, BigInteger::getBC($x), 0), true); 444 | } 445 | 446 | public function mul($x) { 447 | return new BigInteger(bcmul($this->value, BigInteger::getBC($x), 0), true); 448 | } 449 | 450 | public function div($x) { 451 | return new BigInteger(bcdiv($this->value, BigInteger::getBC($x), 0), true); 452 | } 453 | 454 | public function divR($x) { 455 | return new BigInteger(bcmod($this->value, BigInteger::getBC($x)), true); 456 | } 457 | 458 | public function divQR($x) { 459 | return array( 460 | $this->div($x), 461 | $this->divR($x) 462 | ); 463 | } 464 | 465 | public function mod($x) { 466 | $xv = BigInteger::getBC($x); 467 | $mod = bcmod($this->value, $xv); 468 | if ($mod[0] == "-") { 469 | $mod = bcadd($mod, $xv[0] == "-" ? substr($xv, 1) : $xv, 0); 470 | } 471 | return new BigInteger($mod, true); 472 | } 473 | 474 | public function extendedGcd($n) { 475 | $u = $this->value; 476 | $v = (new BigInteger($n))->abs()->value; 477 | 478 | $a = "1"; 479 | $b = "0"; 480 | $c = "0"; 481 | $d = "1"; 482 | 483 | while (bccomp($v, "0", 0) != 0) { 484 | $q = bcdiv($u, $v, 0); 485 | 486 | $temp = $u; 487 | $u = $v; 488 | $v = bcsub($temp, bcmul($v, $q, 0), 0); 489 | 490 | $temp = $a; 491 | $a = $c; 492 | $c = bcsub($temp, bcmul($a, $q, 0), 0); 493 | 494 | $temp = $b; 495 | $b = $d; 496 | $d = bcsub($temp, bcmul($b, $q, 0), 0); 497 | } 498 | 499 | return array( 500 | "gcd" => new BigInteger($u, true), 501 | "x" => new BigInteger($a, true), 502 | "y" => new BigInteger($b, true) 503 | ); 504 | } 505 | 506 | public function gcd($x) { 507 | return $this->extendedGcd($x)["gcd"]; 508 | } 509 | 510 | public function modInverse($n) { 511 | $n = (new BigInteger($n))->abs(); 512 | 513 | if ($this->sign() < 0) { 514 | $temp = $this->abs(); 515 | $temp = $temp->modInverse($n); 516 | return $n->sub($temp); 517 | } 518 | 519 | extract($this->extendedGcd($n)); 520 | 521 | if (!$gcd->equals(1)) { 522 | return false; 523 | } 524 | 525 | $x = $x->sign() < 0 ? $x->add($n) : $x; 526 | 527 | return $this->sign() < 0 ? $n->sub($x) : $x; 528 | } 529 | 530 | public function pow($x) { 531 | return new BigInteger(bcpow($this->value, BigInteger::getBC($x), 0), true); 532 | } 533 | 534 | public function powMod($x, $n) { 535 | return new BigInteger(bcpowmod($this->value, BigInteger::getBC($x), BigInteger::getBC($n), 0), true); 536 | } 537 | 538 | public function abs() { 539 | return new BigInteger($this->value[0] == "-" ? substr($this->value, 1) : $this->value, true); 540 | } 541 | 542 | public function neg() { 543 | return new BigInteger($this->value[0] == "-" ? substr($this->value, 1) : "-" . $this->value, true); 544 | } 545 | 546 | public function binaryAnd($x) { 547 | $left = $this->toBytes(); 548 | $right = (new BigInteger($x))->toBytes(); 549 | 550 | $length = max(strlen($left), strlen($right)); 551 | 552 | $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); 553 | $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); 554 | 555 | return new BigInteger($left & $right, 256); 556 | } 557 | 558 | public function binaryOr($x) { 559 | $left = $this->toBytes(); 560 | $right = (new BigInteger($x))->toBytes(); 561 | 562 | $length = max(strlen($left), strlen($right)); 563 | 564 | $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); 565 | $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); 566 | 567 | return new BigInteger($left | $right, 256); 568 | } 569 | 570 | public function binaryXor($x) { 571 | $left = $this->toBytes(); 572 | $right = (new BigInteger($x))->toBytes(); 573 | 574 | $length = max(strlen($left), strlen($right)); 575 | 576 | $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); 577 | $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); 578 | 579 | return new BigInteger($left ^ $right, 256); 580 | } 581 | 582 | public function setbit($index, $bitOn = true) { 583 | $bits = $this->toBits(); 584 | $bits[strlen($bits) - $index - 1] = $bitOn ? "1" : "0"; 585 | return new BigInteger($bits, 2); 586 | } 587 | 588 | public function testbit($index) { 589 | $bytes = $this->toBytes(); 590 | $bytesIndex = intval($index / 8); 591 | $len = strlen($bytes); 592 | $b = $bytesIndex >= $len ? 0 : ord($bytes[$len - $bytesIndex - 1]); 593 | $v = 1 << ($index % 8); 594 | return ($b & $v) === $v; 595 | } 596 | 597 | public function scan0($start) { 598 | $bits = $this->toBits(); 599 | $len = strlen($bits); 600 | if ($start < 0 || $start >= $len) { 601 | return -1; 602 | } 603 | $pos = strrpos($bits, "0", -1 - $start); 604 | return $pos === false ? -1 : $len - $pos - 1; 605 | } 606 | 607 | public function scan1($start) { 608 | $bits = $this->toBits(); 609 | $len = strlen($bits); 610 | if ($start < 0 || $start >= $len) { 611 | return -1; 612 | } 613 | $pos = strrpos($bits, "1", -1 - $start); 614 | return $pos === false ? -1 : $len - $pos - 1; 615 | } 616 | 617 | public function cmp($x) { 618 | return bccomp($this->value, BigInteger::getBC($x)); 619 | } 620 | 621 | public function equals($x) { 622 | return $this->value === BigInteger::getBC($x); 623 | } 624 | 625 | public function sign() { 626 | return $this->value[0] === "-" ? -1 : ($this->value === "0" ? 0 : 1); 627 | } 628 | } 629 | 630 | } 631 | else { 632 | if (!defined("S_MATH_BIGINTEGER_QUIET")) { 633 | throw new \Exception("Unsupported S_MATH_BIGINTEGER_MODE " . S_MATH_BIGINTEGER_MODE); 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /test/perf.php: -------------------------------------------------------------------------------- 1 | binaryAnd($b); 20 | //$v->toBytes(); 21 | //BigInteger::getBC($v, 256); 22 | } 23 | $end = microtime(true); 24 | error_log($end - $start); 25 | } 26 | 27 | test($a, $a); 28 | test($b, $b); 29 | //test($bytes, $b); -------------------------------------------------------------------------------- /test/test.php: -------------------------------------------------------------------------------- 1 | toString() === $b ? "PASS" : "FAIL get: " . $a . ", expected: " . $b) . " " . $message); 11 | } 12 | 13 | function testSerialization($b, $msg = "") { 14 | test($b->toBits(), "1010000", $msg . " toBits"); 15 | test($b->toBytes(), hex2bin("50"), $msg . " toBytes"); 16 | test($b->toHex(), "50", $msg . " toHex"); 17 | test($b->toDec(), "80", $msg . " toDec"); 18 | test($b->toNumber(), 80, $msg . " toNumber"); 19 | test($b->toBase(58), "1M", $msg . " to58"); 20 | } 21 | 22 | function testCreate() { 23 | error_log("=============\nTest serialization\n============="); 24 | testSerialization(new BigInteger("1010000", 2), "bits"); 25 | testSerialization(new BigInteger(hex2bin("50"), 256), "bytes"); 26 | testSerialization(new BigInteger("50", 16), "hex"); 27 | testSerialization(new BigInteger("80", 10), "dec"); 28 | testSerialization(new BigInteger("80"), "dec2"); 29 | testSerialization(new BigInteger(80), "number"); 30 | } 31 | 32 | function testCreateSafeSingle($value, $base, $msg) { 33 | try { 34 | $z = new BigInteger($value, $base); 35 | error_log("FAIL exception during create " . $msg); 36 | } 37 | catch (\Exception $e) { 38 | error_log("PASS exception during create " . $msg); 39 | } 40 | test(BigInteger::createSafe($value, $base), false, "createSafe " . $msg); 41 | } 42 | 43 | function testCreateSafe() { 44 | error_log("=============\nTest create safe\n============="); 45 | testCreateSafeSingle("zz", 2, "bin"); 46 | testCreateSafeSingle("zz", 10, "dec"); 47 | testCreateSafeSingle("zz", 16, "hex"); 48 | } 49 | 50 | function testSpaces() { 51 | error_log("=============\nTest spaces\n============="); 52 | test((new BigInteger("11 0 1", 2))->toBits(), "1101", "bin"); 53 | test((new BigInteger("6 2 0 6", 10))->toDec(), "6206", "dec"); 54 | test((new BigInteger("f3 5 12 ac 0", 16))->toHex(), "f3512ac0", "hex"); 55 | } 56 | 57 | function testOp() { 58 | error_log("=============\nTest op\n============="); 59 | testB((new BigInteger(20))->add(34), "54", "add"); 60 | testB((new BigInteger(20))->sub(14), "6", "sub"); 61 | testB((new BigInteger(20))->mul(12), "240", "mul"); 62 | testB((new BigInteger(20))->div(4), "5", "div"); 63 | testB((new BigInteger(20))->divR(7), "6", "divR"); 64 | $qr = (new BigInteger(20))->divQR(6); 65 | testB($qr[0], "3", "divQR[0]"); 66 | testB($qr[1], "2", "divQR[1]"); 67 | testB((new BigInteger(20))->mod(3), "2", "mod"); 68 | testB((new BigInteger(54))->gcd(81), "27", "gcd"); 69 | testB((new BigInteger(3))->modInverse(10), "7", "modInverse"); 70 | testB((new BigInteger(3))->pow(4), "81", "pow"); 71 | testB((new BigInteger(3))->powMod(4, 10), "1", "powMod"); 72 | testB((new BigInteger(20))->abs(), "20", "abs"); 73 | testB((new BigInteger(20))->neg(), "-20", "neg"); 74 | testB((new BigInteger(20))->binaryAnd(18), "16", "binaryAnd"); 75 | testB((new BigInteger(20))->binaryOr(18), "22", "binaryOr"); 76 | testB((new BigInteger(20))->binaryXor(18), "6", "binaryXor"); 77 | testB((new BigInteger(20))->setbit(3), "28", "setbit"); 78 | test((new BigInteger(20))->testbit(4), true, "testbit true"); 79 | test((new BigInteger(20))->testbit(3), false, "testbit false"); 80 | test((new BigInteger(5))->testbit(0), true, "testbit 0 true"); 81 | test((new BigInteger(6))->testbit(0), false, "testbit 0 false"); 82 | test((new BigInteger(6))->testbit(1), true, "testbit 1 true"); 83 | test((new BigInteger(5))->testbit(1), false, "testbit 1 false"); 84 | test((new BigInteger(132))->testbit(7), true, "testbit 7 true"); 85 | test((new BigInteger(81))->testbit(7), false, "testbit 7 false"); 86 | test((new BigInteger(258))->testbit(8), true, "testbit 8 true"); 87 | test((new BigInteger(253))->testbit(8), false, "testbit 8 false"); 88 | test((new BigInteger(20))->scan0(2), 3, "scan0"); 89 | test((new BigInteger(20))->scan1(3), 4, "scan1"); 90 | test((new BigInteger(20))->cmp(22), -1, "cmp -1"); 91 | test((new BigInteger(20))->cmp(20), 0, "cmp 0"); 92 | test((new BigInteger(20))->cmp(18), 1, "cmp 1"); 93 | test((new BigInteger(20))->equals(20), true, "equals true"); 94 | test((new BigInteger(20))->equals(21), false, "equals false"); 95 | test((new BigInteger(-20))->sign(), -1, "sign -1"); 96 | test((new BigInteger(0))->sign(), 0, "sign 0"); 97 | test((new BigInteger(20))->sign(), 1, "sign 1"); 98 | testB(new BigInteger("-20"), "-20", "minus"); 99 | testB(new BigInteger("-14", 16), "-20", "minus"); 100 | testB(new BigInteger("-10100", 2), "-20", "minus"); 101 | } 102 | 103 | function testBig() { 104 | error_log("=============\nTest big\n============="); 105 | $bits = "1001010111010010100001000101110110100001000101101000110101010101001"; 106 | $hex = "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; 107 | $dec = "436529472098746319073192837123683467019263172846"; 108 | $bytes = hex2bin($hex); 109 | test((new BigInteger($bits, 2))->toBits(), $bits, "init big from binary"); 110 | test((new BigInteger($dec, 10))->toDec(), $dec, "init big from dec"); 111 | test((new BigInteger($hex, 16))->toHex(), $hex, "init big from hex"); 112 | test((new BigInteger($bytes, 256))->toBytes(), $bytes, "init big from buffer"); 113 | } 114 | 115 | testCreate(); 116 | testCreateSafe(); 117 | testSpaces(); 118 | testOp(); 119 | testBig(); -------------------------------------------------------------------------------- /test/testbc.php: -------------------------------------------------------------------------------- 1 |