syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

On a perl 5.18.0 built with nvtype of "long double" I would expect that the value returned by the XSub:
SV * get_problem_val(void) { return newSVnv(1e-298L); }
would be exactly equal to the perl value 1e-298.
But that's not the case.

Here's the demo script:
#!perl -l use warnings; use strict; use Config; use Inline C => Config => USING => 'ParseRegExp', BUILD_NOISY => 1; use Inline C => <<'EOC'; SV * get_problem_val(void) { return newSVnv(1e-298L); } EOC print "\n\$Config{nvtype}: $Config{nvtype}"; print "Different values" if 1e-298 != get_problem_val(); print scalar reverse unpack "b64", pack "D", 1e-298; print scalar reverse unpack "b64", pack "D", get_problem_val();
As well as confirming that nvtype is long double and that the values differ, it also reveals that the last 5 (least significant) bits of the 64-bit perl mantissa are 10000 whereas the 64-bit C mantissa terminates with 01110
Other than that, they agree.

Is there a sound reason for this variation in value ?
If not, is there anyone here with a "long double" build of perl that experiences *expected* behaviour from the above demo ?

(BTW, it looks to me that the perl value is correct but the C value is not. At least, perl agrees with the 64-bit mantissa that the mpfr library assigns.)

Cheers,
Rob

Replies are listed 'Best First'.
Re: When 1e-298L != 1e-298L
by BrowserUk (Patriarch) on Oct 24, 2013 at 14:23 UTC
    unpack "b64", pack "D", 1e-298;

    I suspect - but cannot verify as MSC maps long double to double -- that (a part of) the problem is the way you are inspecting the values.

    On intel, gcc maps long double to an 80-bit floating point number -- though it may store it in either 12- or 16-bytes for alignment -- and you are only inspecting 64-bits of that 80-bit value.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: When 1e-298L != 1e-298L
by kcott (Archbishop) on Oct 24, 2013 at 14:41 UTC

    G'day Rob,

    I don't have an explanation. For what it's worth, here's the output from running your code under v5.18.1 (darwin):

    validate Stage Starting Build Preprocess Stage get_maps Stage Finished Build Preprocess Stage Starting Build Parse Stage Finished Build Parse Stage Starting Build Glue 1 Stage Finished Build Glue 1 Stage Starting Build Glue 2 Stage Finished Build Glue 2 Stage Starting Build Glue 3 Stage Finished Build Glue 3 Stage Starting Build Compile Stage Starting "perl Makefile.PL" Stage Writing Makefile for pm_example_pl_ae0b Writing MYMETA.yml and MYMETA.json Finished "perl Makefile.PL" Stage Starting "make" Stage /Users/ken/perl5/perlbrew/perls/perl-5.18.1t/bin/perl /Users/ken/perl5 +/perlbrew/perls/perl-5.18.1t/lib/5.18.1/ExtUtils/xsubpp -typemap "/U +sers/ken/perl5/perlbrew/perls/perl-5.18.1t/lib/5.18.1/ExtUtils/typema +p" pm_example_pl_ae0b.xs > pm_example_pl_ae0b.xsc && mv pm_example_ +pl_ae0b.xsc pm_example_pl_ae0b.c cc -c -I"/Users/ken/tmp" -fno-common -DPERL_DARWIN -fno-strict-aliasi +ng -pipe -fstack-protector -I/usr/local/include -I/opt/local/include +-O3 -DVERSION=\"0.00\" -DXS_VERSION=\"0.00\" "-I/Users/ken/perl5/p +erlbrew/perls/perl-5.18.1t/lib/5.18.1/darwin-thread-multi-2level/CORE +" pm_example_pl_ae0b.c Running Mkbootstrap for pm_example_pl_ae0b () chmod 644 pm_example_pl_ae0b.bs rm -f blib/arch/auto/pm_example_pl_ae0b/pm_example_pl_ae0b.bundle env MACOSX_DEPLOYMENT_TARGET=10.3 cc -bundle -undefined dynamic_looku +p -L/usr/local/lib -L/opt/local/lib -fstack-protector pm_example_pl_a +e0b.o -o blib/arch/auto/pm_example_pl_ae0b/pm_example_pl_ae0b.bundle + \ \ chmod 755 blib/arch/auto/pm_example_pl_ae0b/pm_example_pl_ae0b.bundle cp pm_example_pl_ae0b.bs blib/arch/auto/pm_example_pl_ae0b/pm_example_ +pl_ae0b.bs chmod 644 blib/arch/auto/pm_example_pl_ae0b/pm_example_pl_ae0b.bs Finished "make" Stage Starting "make install" Stage Files found in blib/arch: installing files in blib/lib into architectu +re dependent library tree Installing /Users/ken/tmp/_Inline/lib/auto/pm_example_pl_ae0b/pm_examp +le_pl_ae0b.bs Installing /Users/ken/tmp/_Inline/lib/auto/pm_example_pl_ae0b/pm_examp +le_pl_ae0b.bundle Finished "make install" Stage Starting Cleaning Up Stage Finished Cleaning Up Stage Finished Build Compile Stage $Config{nvtype}: double Different values Invalid type 'D' in pack at ./pm_example.pl line 21.

    Here's my full perl -V:

    -- Ken

      Interesting that there's also a discrepancy with 1e-298 on perl's (such as yours) whose nvtype is 'double'.
      When I check on my 'double' build of perl 5.18.0, I also find a discrepancy.
      The script I originally posted is not properly portable to 'double' builds of perl. This rendition of the script should work as intended for both nvtypes:
      #!perl -l use warnings; use strict; use Config; use Inline C => Config => USING => 'ParseRegExp', BUILD_NOISY => 1; use Inline C => <<'EOC'; SV * get_problem_val(long nvsize) { if(nvsize == 8) return newSVnv(1e-298); return newSVnv(1e-298L); } EOC my $s = $Config{nvsize}; print "\n\$Config{nvtype}: $Config{nvtype}"; print "Different values" if 1e-298 != get_problem_val($s); if($Config{nvsize} != 8) { print scalar reverse unpack "b64", pack "D", 1e-298; print scalar reverse unpack "b64", pack "D", get_problem_val($s); } else { print scalar reverse unpack "b53", pack "F", 1e-298; print scalar reverse unpack "b53", pack "F", get_problem_val($s); }
      Thanks, Ken.

      Cheers,
      Rob
Re: When 1e-298L != 1e-298L
by BrowserUk (Patriarch) on Oct 24, 2013 at 17:27 UTC

    FWIW: this is the bit pattern you should be seeing for 1e-298:

    C:\test>r80test 1e-298 1e-298 : 00111100 00100001 10000101 11110000 01000110 10 +000010 10010011 11110000 11101011 01001110 smmmmmmm mmmmmmmm ffffffff ffffffff ffffffff ff +ffffff ffffffff ffffffff ffffffff ffffffff s = sign bit m = 15-bit mantissa f = 64-bit fraction

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      C:\test>r80test

      Is "r80test" readily available ? (Could be handy.)

      kcott's post demonstrates that the discrepancy also occurs when the nvtype is 'double' - but not universally according to some quick testing that I've just done. (The discrepancy occurs with gcc-4.6.3 on linux, and MSVC++ 7.0 on MS Windows, but not gcc-4.7.0 on MS Windows. In all instances the compiler I've used is the compiler that actually built perl. It seems that the behaviour does depend upon the compiler in use, not upon the version number of perl, but that's not properly tested yet.)

      I've just rewritten the demo code so that it's also portable to perls whose nvtype is 'double'. (See my reply to kcott's post.)

      And now I find that the mpfr library is providing varying results on linux and windows. It shouldn't do that, but I'll have to check the versions of mpfr that I'm running. (It may simply be a bug in an earlier version that has been fixed.)

      Thanks Buk.

      Cheers,
      Rob
        Is "r80test" readily available ? (Could be handy.)

        It's written in D, which has a native 80-bit FP type available. The code looks like this:

        import std.stdio; import std.format; void main( string[] args ) { real r; ubyte *ubp = cast(ubyte*)&r; foreach( line; stdin.byLine ) { formattedRead( line, " %f ", &r ); writef( "%20.18g : ", r ); for( int i = r.sizeof -1; i >= 0; --i ) { writef( "%08.8b ", ubp[i] ); } writeln(); } }

        If you don't want to be bothered with installing/learning D, I can send you the executable?


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: When 1e-298L != 1e-298L
by syphilis (Archbishop) on Oct 25, 2013 at 11:09 UTC
    Is there a sound reason for this variation in value ?

    When I finally got my thinking straightened out, it became obvious to me that this is a perl bug.
    However, just because it's "obvious to me" doesn't really prove that it *is* a bug, so I've sent off a perlbug report (ticket #120363) ... if I don't forget, I'll update this node with the outcome of that report.

    Thanks again Ken and Buk.

    Cheers,
    Rob
      I'll update this node with the outcome of that report

      Seems it's a known bug (ticket #41202 from nearly 3 years ago) - and perhaps it's worthy of a separate post instead of an update.

      I haven't found a build of perl that's not affected (and I've got quite a few) across intel (MS Windows), amd64 (Ubuntu) and PowerPC (debian) machines.
      However, on some systems it's an off-by-one discrepancy , whereas on other systems it's off-by-two. (These could well be different bugs.)
      And it seems to not occur across the full range of exponents ... so, for most perl purposes it's probably not much of an issue.

      Still, it annoyed me enough to write a Math::NV module whose nv($str) function will assign the "C" value of $str to an NV - the assumption being that "C" gets it right. (It's an assumption I've tested against what both Data::Float and the mpfr library reckon is the correct representation but, with so many different versions of compiler/libc around, there's always a possibility of a bug to muddy the waters.)

      Cheers,
      Rob