racket/collects/math/private/bigfloat/gmp.rkt
Neil Toronto fcc08fa89e Fixed bigfloats on Win64. Win64's long type is 32 bits and GMP's default is to
use longs for the "limbs" of bigints. However, when GMP's configure script
detects that mingw64 is compiling, it defines LONG_LONG_LIMB, which makes the
type of limbs long long, or 64 bits. This is fine; a 64-bit machine should use
64-bit ints for the digits of its bigints. It would have been nice to know
this special case earlier, though I can see why it's not advertised: most
users don't need to know, and it seems like it's obviously the right choice to
make when dealing with Win64's annoying ABI.

Made "mpfr.rkt" search for 'mpfr_set_z_exp if 'mpfr_set_z_2exp isn't found.
Hopefully this allows the bigfloat tests to finish on DrDr. If not, DrDr
will need a libmpfr upgrade.

Made some minor doc fixups
2012-12-05 20:29:59 -07:00

169 lines
5.7 KiB
Racket

#lang racket/base
(require (for-syntax racket/base)
ffi/unsafe
ffi/unsafe/define
racket/runtime-path)
(provide
_mp_size_t
_mp_limb_t
sizeof-mp_limb_t
gmp-limb-bits
_mpz-pointer
new-mpz
integer->mpz
mpz->integer)
;; ===================================================================================================
;; Setup
(define-runtime-path libgmp-so
(case (system-type)
[(macosx) '(so "libgmp.10.dylib")]
[(windows) '(so "libgmp-10.dll")]
[else '(so "libgmp")]))
(define gmp-lib (ffi-lib libgmp-so '("10" "3" "") #:fail (λ () #f)))
(define (get-gmp-fun name type)
(get-ffi-obj name gmp-lib type (make-not-available name)))
;; ===================================================================================================
;; Types
(define 64-bit? (= 8 (ctype-sizeof _intptr)))
(define win64? (and 64-bit? (eq? (system-type) 'windows)))
;; The Win64 build of libgmp is compiled using mingw64, for which GMP's configure script defines
;; LONG_LONG_LIMB
(define _mp_limb_t (if win64? _ullong _ulong))
(define _mp_size_t _long) ; used for limb counts
(define _mp_bitcnt_t _ulong)
(define sizeof-mp_limb_t (ctype-sizeof _mp_limb_t))
;; Number of bits in a limb
(define gmp-limb-bits (* 8 sizeof-mp_limb_t))
;; We don't support "nail" builds, which haven't worked since before GMP 4.3 anyway (a "nail" takes
;; two bits from each limb, and is supposed to speed up carries between limbs on certain systems)
;; GMP's big integers
(define-cstruct _mpz ([alloc _int] [size _int] [limbs _pointer]))
;; ALWAYS let GMP manage the memory pointed at by the limbs field in an _mpz. It likes to reallocate
;; limbs, particularly when the _mpz is an output argument.
;; This means that _mpz instances take up memory that Racket can't account for, so they should only
;; be used as temporary values. Possibly permanent values should be either instances of _mpfr or
;; Racket's own exact integers.
(define mpz-init (get-gmp-fun '__gmpz_init (_fun _mpz-pointer -> _void)))
(define mpz-init2 (get-gmp-fun '__gmpz_init2 (_fun _mpz-pointer _mp_bitcnt_t -> _void)))
(define mpz-clear (get-gmp-fun '__gmpz_clear (_fun _mpz-pointer -> _void)))
;; new-mpz : [_mp_bitcnt_t] -> _mpz-pointer
;; Creates an _mpz that is managed by the garbage collector, but whose limbs are not, and which is
;; not relocatable. These are always safe to pass to mpz_* functions.
(define (new-mpz [bits 0])
(define z (make-mpz 0 0 #f))
(if (= bits 0) (mpz-init z) (mpz-init2 z bits))
(register-finalizer z (λ (z) (mpz-clear z)))
z)
;; ===================================================================================================
;; Conversions
(define mpz-get-si (get-gmp-fun '__gmpz_get_si (_fun _mpz-pointer -> _long)))
(define mpz-fits-long? (get-gmp-fun '__gmpz_fits_slong_p (_fun _mpz-pointer -> _int)))
;; integer->mpz : integer -> _mpz
;; Converts a Racket integer to an _mpz.
(define (integer->mpz n)
(define abs-n (abs n))
(define z (new-mpz (integer-length abs-n)))
(define len (mpz-alloc z))
(define limbs (mpz-limbs z))
(let loop ([i 0])
(when (i . < . len)
(define bit (* i gmp-limb-bits))
(ptr-set! limbs _mp_limb_t i (bitwise-bit-field abs-n bit (+ bit gmp-limb-bits)))
(loop (+ i 1))))
(define size (ceiling (/ (integer-length abs-n) gmp-limb-bits)))
(set-mpz-size! z (if (< n 0) (- size) size))
z)
;; size+limbs->integer : integer (listof integer) -> integer
;; Converts a size (which may be negative) and a limb list into an integer.
(define (size+limbs->integer size limbs)
(define len (abs size))
(define num
(let loop ([i 0] [res 0])
(cond [(i . < . len)
(define v (ptr-ref limbs _mp_limb_t i))
(loop (+ i 1) (bitwise-ior res (arithmetic-shift v (* i gmp-limb-bits))))]
[else res])))
(if (negative? size) (- num) num))
;; mpz->integer : _mpz -> integer
;; Converts an mpz_t to a Racket integer.
(define (mpz->integer z)
(if (zero? (mpz-fits-long? z))
(size+limbs->integer (mpz-size z) (mpz-limbs z))
(mpz-get-si z)))
;; ===================================================================================================
;; Number Theoretic Functions
;; http://gmplib.org/manual/Number-Theoretic-Functions.html#Number-Theoretic-Functions
(define mpz-probab-prime-p
(get-gmp-fun '__gmpz_probab_prime_p (_fun _mpz-pointer _int -> _int)))
(define (probably-prime n [repetitions 10])
(case (mpz-probab-prime-p (integer->mpz n) repetitions)
[(0) 'composite]
[(1) 'probably-prime]
[(2) 'prime]))
(define (prime? n)
(case (probably-prime n)
[(probably-prime prime) #t]
[else #f]))
(define mpz-nextprime
(get-gmp-fun '__gmpz_nextprime (_fun _mpz-pointer _mpz-pointer -> _void)))
(define (next-prime op)
(define result (new-mpz))
(mpz-nextprime result (integer->mpz op))
(mpz->integer result))
(define mpz-invert
(get-gmp-fun '__gmpz_invert (_fun _mpz-pointer _mpz-pointer _mpz-pointer -> _void)))
(define (mod-inverse n modulus)
(define result (new-mpz))
(mpz-invert result (integer->mpz n) (integer->mpz modulus))
(mpz->integer result))
(define mpz-jacobi
(get-gmp-fun '__gmpz_jacobi (_fun _mpz-pointer _mpz-pointer -> _int)))
(define (jacobi a b)
(mpz-jacobi (integer->mpz a) (integer->mpz b)))
(define mpz-legendre
(get-gmp-fun '__gmpz_legendre (_fun _mpz-pointer _mpz-pointer -> _int)))
(define (legendre a b)
(mpz-legendre (integer->mpz a) (integer->mpz b)))
(define mpz-remove
(get-gmp-fun '__gmpz_remove (_fun _mpz-pointer _mpz-pointer _mpz-pointer -> _int)))
(define (remove-factor z f)
(define result (new-mpz))
(define n (mpz-remove result (integer->mpz z) (integer->mpz f)))
(define z/f (mpz->integer result))
(values z/f n))