in reply to XS, C doubles and -Duselongdouble

I'm trying to figure out what your concern might actually be.

In theory, the NV for the constant 1.1 will be the closest long double to the exact value for 1.1. As far as I can tell, the cast to double rounds to the nearest possible double value, which is almost always going to be the closest double value to the original exact value. So I don't see how you could be worried about those.

The value returned by foo(), a long double laundered through a double, is almost always going to be different from the original value - given purely random long double input, something like 99.95 % of the time. Though you'll run across cases where no loss occurs more often than that, since many interesting values are exactly representable as a double or long double, e.g. 2.5, or 1.

So, why does it matter to you that precision may be (rather, is probably going to be) lost? What do you want to do differently based on whether precision is lost?

Update: maybe all you want is to use sprintf("%.15g", perl_foo($x)) instead of just perl_foo($x)?

Replies are listed 'Best First'.
Re^2: XS, C doubles and -Duselongdouble
by syphilis (Archbishop) on Jun 11, 2007 at 07:32 UTC
    I'm trying to figure out what your concern might actually be

    The GMP C library can assign longs (signed and unsigned), doubles and strings to the mpz_t (integer) struct. But it has no inbuilt way of assigning long longs and long doubles to an mpz_t.

    I've merely been looking at how to deal with some of the gotchas that arise when you interface a -Duse64bitint build of perl (with/without -Duselongdouble) to that GMP library.

    Let's say I have a perl built with 64-bit int support (but no long double) and I've done:
    use warnings; use Math::GMPz qw(:mpz); $num = 144115188075868217;# this is an IV $obj = Math::GMPz->new($num);
    What should happen there ? Should it croak with the message "Hey ... the GMP library doesn't handle ints bigger than 32-bit!!" ? Should it silently assign the value as a 32-bit int ? I've chosen to have the new() XS function read $num as a string using SvPV_nolen(), and then have the GMP library's mpz_set_str() function assign from that string - and that seems to assign the correct 64-bit value.

    Unfortunately, it doesn't appear to be so simple when it comes to NV's and -Duselongdouble builds of perl:
    use warnings; use Math::GMPz qw(:mpz); $num = 2 ** 57 + 12345;# this is an NV $obj = Math::GMPz->new($num);
    If, under a long double build of perl, the XS function now reads $num using SvPV_nolen(), and the value gets assigned using mpz_set_str(), then it gets the value wrong. There is no simple way (that I can see) for $obj to be assigned the value represented by $num - using the existing assignment operations provided by the GMP library. Of course, if $num fits into a C double anyway, then there's no problem. So ... I started to think along the lines that, since the GMP library did not support long doubles, it was unreasonable to expect that Math::GMPz should support long doubles (even though the build of perl did support them) - and that a croak/warning should be emitted iff precision was being lost.

    But I'm now not so sure about that - your mention of sprintf() made me aware that I can probably make use of the C sprintf() function. That is, the new() XS function assigns the $num arg to a long double, sprintf() converts that long double to a string, and *that* string is then handed over to the GMP library's mpz_set_str() function ... and $obj ends up with the correct value and no loss of precision.

    I'll give that a go ... in the meantime, thoughts and comments welcome.

    Thanks ysth.

    Cheers,
    Rob
      It's really difficult to get XS code right that does different things for different types of input; if I were you, I'd document that Math::GMPz->new takes a string of digits, and let the caller worry about anything beyond that.

      Update: or provide a perl new_from_number that looks something like this:

      use Config; sub new_from_number { my $class = shift; my $num = shift; my $fmt = '%.0f'; if (exists($Config{nv_preserves_uv_bits}) && $Config{nv_preserves_uv_bits} < 8 * $Config{uvsize} ) { $fmt = $num < 0 ? '%d' : '%u'; } $class->new(sprintf($fmt, $num)); }
      I'm probably forgetting something important there, though.

      Update 2: yes, I was forgetting to revert to %.0f for numbers outside [IV_MIN, UV_MAX].