Search code examples
rustgmp

GMP - mpf_cmp_si not working correctly for negative values


I'm using the GNU multiple precision library through Rust, and I'm trying to write a wrapper for the mpf_sqrt() function. In order to do so, I need to make sure the number is positive, but mpf_cmp_si() isn't behaving.

EDIT: new example

extern crate libc;
use libc::{c_double, c_int, c_long, c_ulong, c_void,c_char};
use std::mem::uninitialized;

type mp_limb_t = usize; // TODO: Find a way to use __gmp_bits_per_limb instead.
type mp_bitcnt_t = c_ulong;
type mp_exp_t = c_long;

#[link(name = "gmp")]
extern "C" {
    fn __gmpf_init2(x: mpf_ptr, prec: mp_bitcnt_t);
    fn __gmpf_set_si(rop: mpf_ptr,op: c_int);
    fn __gmpf_cmp_si(op1: mpf_srcptr, op2: c_long) -> c_int;
}

#[repr(C)]
pub struct mpf_struct {
    _mp_prec: c_int,
    _mp_size: c_int,
    _mp_exp: mp_exp_t,
    _mp_d: *mut c_void
}

pub type mpf_srcptr = *const mpf_struct;
pub type mpf_ptr = *mut mpf_struct;

fn main() {
    let mut ten:mpf_struct;
    unsafe{
        ten = uninitialized();
        __gmpf_init2(&mut ten,512);
        __gmpf_set_si(&mut ten,10);
    }
    let mut minus_ten:mpf_struct;
    unsafe{
        minus_ten = uninitialized();
        __gmpf_init2(&mut minus_ten,512);
        __gmpf_set_si(&mut minus_ten,-10);
    }


    // compare things
    unsafe{
        println!("Result of comparison of -10 (mpf) and 10 (signed int) = {}",
            __gmpf_cmp_si(&minus_ten,10));
        println!("Result of comparison of -10 (mpf) and 0 (signed int) = {}",
            __gmpf_cmp_si(&minus_ten,0));
        println!("Result of comparison of 10 (mpf) and 0 (signed int) = {}",
            __gmpf_cmp_si(&ten,0));
    }


}

This returns:

Running `target/debug/so_test`
Result of comparison of -10 (mpf) and 10 (signed int) = 1
Result of comparison of -10 (mpf) and 0 (signed int) = 1
Result of comparison of 10 (mpf) and 0 (signed int) = 1

According to the docs, this is the behavior:

Function: int mpf_cmp_si (const mpf_t op1, signed long int op2)
Compare op1 and op2. Return a positive value if op1 > op2, zero if op1 = op2, and a negative value if op1 < op2.

I'm running rust 1.4.0, and GMP 6.1.0-1 on x64 Linux

Old code:

    pub fn sqrt(self) -> Mpf {
    let mut retval:Mpf;
    unsafe {
        retval = Mpf::new(__gmpf_get_prec(&self.mpf) as usize);
        retval.set_from_si(0);
        if __gmpf_cmp_ui(&self.mpf,0) > 0 {
            __gmpf_sqrt(&mut retval.mpf,&self.mpf);
        } else {
            panic!("Square root of negative/zero");
        }
    }
    retval
}

the mpf struct is defined like this:

#[repr(C)]
pub struct mpf_struct {
    _mp_prec: c_int,
    _mp_size: c_int,
    _mp_exp: mp_exp_t,
    _mp_d: *mut c_void
} and the function from gmp is imported like this:

#[link(name = "gmp")]
extern "C" {
    fn __gmpf_cmp_si(op1: mpf_srcptr, op2: c_long) -> c_int;

}

The problem I'm having is that mpf_cmp_si (which is exposed to Rust as __gmpf_cmp_si) doesn't return negative when it should.

This function should return negative if the value of my mpf is less than 0. But it doesn't so the function divides by zero and crashes (an "unknown error", not because of the panic!() call)


Solution

  • The signature of __gmpf_set_si is incorrect. The C definition is:

    void mpf_set_si (mpf_t rop, signed long int op)
    

    And hence the Rust FFI declaration should use c_long, not c_int:

    fn __gmpf_set_si(rop: mpf_ptr,op: c_long);
    

    Making this change makes the output (plus a few extra prints) as follows:

    Result of comparison of -10 (mpf) and -10 (signed int) = 0
    Result of comparison of -10 (mpf) and 0 (signed int) = -1
    Result of comparison of -10 (mpf) and 10 (signed int) = -1
    Result of comparison of 10 (mpf) and -10 (signed int) = 1
    Result of comparison of 10 (mpf) and 0 (signed int) = 1
    Result of comparison of 10 (mpf) and 10 (signed int) = 0
    

    (NB. adding the comparisons against -10/10 was how I got to the bottom of this: they failed for -10 compared to -10.)

    The problem is int and long aren't necessarily the same: on 64-bit platforms, they are typically 32-bits and 64-bits respectively. In either case, the argument is passed in the same (64-bit) register, but the type mismatch means that the register is only initialised with a 32-bit -10, which is very different to a 64-bit one. The bit patterns of each are:

    0000000000000000000000000000000011111111111111111111111111110110
    1111111111111111111111111111111111111111111111111111111111110110
    

    When interpreted as a signed 64-bit integer (like gmpf_set_si does internally), the first one is 232 - 10 = 4294967286, which is exactly what minus_ten is initialised with:

    extern crate libc;
    use libc::{c_double, c_int, c_long, c_ulong, c_void,c_char};
    use std::mem::uninitialized;
    
    type mp_limb_t = usize; // TODO: Find a way to use __gmp_bits_per_limb instead.
    type mp_bitcnt_t = c_ulong;
    type mp_exp_t = c_long;
    
    #[link(name = "gmp")]
    extern "C" {
        fn __gmpf_init2(x: mpf_ptr, prec: mp_bitcnt_t);
        fn __gmpf_set_si(rop: mpf_ptr,op: c_int);
        fn __gmp_printf(x: *const c_char, ...);
    }
    
    #[repr(C)]
    pub struct mpf_struct {
        _mp_prec: c_int,
        _mp_size: c_int,
        _mp_exp: mp_exp_t,
        _mp_d: *mut c_void
    }
    pub type mpf_ptr = *mut mpf_struct;
    
    
    fn main() {
        unsafe{
            let mut ten = uninitialized();
            __gmpf_init2(&mut ten,512);
            __gmpf_set_si(&mut ten,10);
    
            let mut minus_ten = uninitialized();
            __gmpf_init2(&mut minus_ten,512);
            __gmpf_set_si(&mut minus_ten,-10);
    
            __gmp_printf(b"10 == %Ff\n-10 == %Ff\n".as_ptr() as *const c_char,
                         &ten, &minus_ten);
        }
    }
    

    Output:

    10 == 10.000000
    -10 == 4294967286.000000
    

    As a final point, rust-bindgen is a great tool for avoiding errors in mechanical transcriptions like this: it will generate the right things based on a C header.