Created
May 11, 2019 20:42
-
-
Save sgolemon/126455b9d32e3ff3c20f7cc42195371a to your computer and use it in GitHub Desktop.
GMP via FFI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare(strict_types=1); | |
namespace MyGMP { | |
class Exception extends \Exception {} | |
} // namespace MyGMP | |
namespace { | |
final class MyGMP { | |
private static ?\FFI $instance = null; | |
/* @@gmp_randstate_t */ | |
private static ?object $randstate = null; | |
/* @@mpz_t */ | |
private static ?object $mpz = null; | |
public function __construct($initialValue = null) { | |
$instance = self::_getInstance(); | |
$this->mpz = $instance->new('mpz_t'); | |
$instance->__gmpz_init($this->mpz); | |
if ($initialValue === null) { | |
$this->setValue(0); | |
} else { | |
$this->setValue($initialValue); | |
} | |
} | |
public function setValue($value): self { | |
$instance = self::_getInstance(); | |
if ($value instanceof \MyGMP) { | |
$instance->__gmpz_set($this->mpz, $value->mpz); | |
} elseif (is_int($value)) { | |
$instance->__gmpz_set_si($this->mpz, $value); | |
} elseif (is_string($value)) { | |
$instance->__gmpz_set_str($this->mpz, $value, 0 /* base */); | |
} else { | |
throw new \MyGMP\Exception("Invalid type passed to " . __METHOD__ . ": " . gettype($value)); | |
} | |
return $this; | |
} | |
public function getString(int $base = 10): string { | |
$instance = self::_getInstance(); | |
$len = $instance->__gmpz_sizeinbase($this->mpz, $base) + 2; | |
$ret = $instance->new("char[$len]"); | |
$instance->__gmpz_get_str($ret, $base, $this->mpz); | |
$str = ''; | |
for ($i = 0; $i < $len; ++$i) { | |
if (ord($ret[$i]) == 0) break; | |
$str .= $ret[$i]; | |
} | |
return $str; | |
} | |
public function add($value): \MyGMP { | |
if (!($value instanceof \MyGMP)) { | |
$value = new \MyGMP($value); | |
} | |
$ret = new \MyGMP($this); | |
self::_getInstance()->__gmpz_add($ret->mpz, $this->mpz, $value->mpz); | |
return $ret; | |
} | |
public function __toString(): string { | |
return $this->getString(10); | |
} | |
public function __debugInfo(): array { | |
return [ | |
2 => $this->getString(2), | |
8 => $this->getString(8), | |
10 => $this->getString(10), | |
16 => $this->getString(16), | |
]; | |
} | |
public function __destruct() { | |
(self::_getInstance())->__gmpz_clear($this->mpz); | |
$this->mpz = null; | |
} | |
public static function getRandom(int $max = PHP_INT_MAX): int { | |
$instance = self::_getInstance(); | |
if (self::$randstate === null) { | |
self::$randstate = (self::$instance)->new('gmp_randstate_t'); | |
$instance->__gmp_randinit_mt(self::$randstate); | |
$instance->__gmp_randseed_ui(self::$randstate, random_int(0, PHP_INT_MAX)); | |
} | |
return $instance->__gmp_urandomm_ui(self::$randstate, $max); | |
} | |
private static function _getInstance(): \FFI { | |
if (self::$instance) { | |
return self::$instance; | |
} | |
$header = <<<HEADER | |
typedef unsigned long long int mp_limb_t; | |
typedef long long int mp_limb_signed_t; | |
typedef struct { | |
int _mp_alloc; | |
int _mp_size; | |
mp_limb_t *_mp_d; | |
} __mpz_struct; | |
typedef __mpz_struct mpz_t[1]; | |
typedef __mpz_struct *mpz_ptr; | |
typedef const __mpz_struct *mpz_srcptr; | |
void __gmpz_init(mpz_ptr); | |
void __gmpz_clear(mpz_ptr); | |
void __gmpz_set(mpz_ptr, mpz_srcptr); | |
void __gmpz_set_si(mpz_ptr, signed long int); | |
int __gmpz_set_str(mpz_ptr, const char *, int); | |
size_t __gmpz_sizeinbase(mpz_srcptr, int); | |
char *__gmpz_get_str(char *, int, mpz_srcptr); | |
void __gmpz_add(mpz_ptr, mpz_srcptr, mpz_srcptr); | |
typedef enum { | |
GMP_RAND_ALG_DEFAULT = 0, | |
GMP_RAND_ALG_LC = GMP_RAND_ALG_DEFAULT, | |
} gmp_randalg_t; | |
typedef struct { | |
mpz_t _mp_seed; | |
gmp_randalg_t _mp_alg; | |
union { | |
void *_mp_lc; | |
} _mp_algdata; | |
} __gmp_randstate_struct; | |
typedef __gmp_randstate_struct gmp_randstate_t[1]; | |
void __gmp_randinit_mt (gmp_randstate_t); | |
void __gmp_randseed_ui (gmp_randstate_t, unsigned long int); | |
unsigned long __gmp_urandomm_ui (gmp_randstate_t, unsigned long); | |
HEADER; | |
return self::$instance = \FFI::cdef($header, 'libgmp.so'); | |
} | |
} | |
} // namespace |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment