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

Dear Monks,

Recently, I have noticed an increase in questions about the accuracy of Perl's floating point results, with all the typical answers about it being the same as C, C++, etc. ( It always brings a smile :-).

But this time, I goggled to find if anything has changed in the non-Perl world. I was very surprised to find that IBM has been shipping a DFP hardware module since 2007 as part of the p-series power6, power7, power8 boxes. Further that many standards have been implemented to support hardware DPF.

Further, since GNU's introduction of gcc version 4.3 hardware and software DPF has been supported. So I looked into the IBM implementation, and found that real results are available. please see the following IBM wiki ( Please Note: It comes up with an 'not-found' error and after several seconds, shows the document. )

Has anyone done any work in this area specific to Perl?

Regards...Ed

"Well done is better than well said." - Benjamin Franklin

  • Comment on Decimal Floating Point (DFP) and does Perl needs DFP?

Replies are listed 'Best First'.
Re: Decimal Floating Point (DFP) and does Perl needs DFP?
by syphilis (Archbishop) on Jan 17, 2015 at 01:20 UTC
    It always brings a smile

    It makes me smile, too - and I smile again at the responses that suggest "this is the way it has to be and you just have to live with it".
    It's crazy - for years we've been putting up with using base2 approximations of the actual (decimal) values that we're interested in.

    Jarrko Hietaniemi has added an option (current blead) that allows perl to be built with an nvtype of __float128.
    And this capability will be available in perl-5.22 for any system/compilers that support the (quad) __float128 type - which includes most Linux and windows systems. (It utilises libquadmath.)

    Update: Ooops ... libquadmath is available for windows (and the potential exists), but setting nvtype to __float128 is currently done via the configure script, which therefore excludes Windows (as Windows don't run no configure script).

    Further, Jarrko did consider enabling perl to be built with NV type of _Decimal64 or _Decimal128. He didn't think about it for very long before deciding it was a crazy idea ... but he did at least ask himself the question.
    I don't know his reasons for deeming it crazy, but my reason for deeming it crazy is that gcc's implementation of decimal math is quite lacking in terms of providing the usual math library functions.
    AFAIK, all that's provided by gcc are the basic arithmetic functions (add, subtract, multiply, divide). Therefore if this type were to be the NV, then perl would have to provide sqrt, pow, log, exp, and trig functions for it.
    Furthermore, gcc provides no strtod64 or strtod128 function, and nor does it provide (s)printf formatting of those types ... more work to be done by perl in assigning/printing.

    I think the IBM compiler provides all of those things that are missing in gcc - and if gcc ever catches up, then I'd be pushing for perl to have the capability of setting its NV to one of these decimal data types.

    Hopefully, gcc's coverage will eventually improve but, wrt fp arithmetic, most of us are currently stuck with using base2 approximations (for anything other than the basic arithmetic operations).

    Has anyone done any work in this area specific to Perl?

    I have written Math::Decimal64 and Math::Decimal128 which wrap the _Decimal64 and _Decimal128 math operations.
    Have a play with them if you're interested.
    They match gcc's coverage - ie no sqrt, pow, log, exp, trig functions and, given the absence of strtod64(), strtod128() and sprint formatters, the input/output functions are a bit kludgy.
    (And now I've *exceeded* this month's quota of self-promotion.)

    Cheers,
    Rob

    Update: Math::Decimal should probably be mentioned here - though it performs its decimal operations via strings, not via decimal floating point data types.

      Hello syphilis,

      Now, your answer is what I was looking for!

      This isn't about finding fault with the current Perl implementation, but about the possibility of improving the future of Perl5.

      Were you able to view the referenced article? If not, I'll try to get a pdf of the article for you.

      IBM added 54 DFP instructions and the XLC compiler uses them. I haven't looked at 'C' code in more than 20 years, but the idea of getting 34 digits of decimal accuracy when using Perl is down right 'sexy'. What was interesting in the wiki article was that the hardware was 76 times faster than the software for the demo. The wiki is from power6, and power7 was twice the speed of power6, and power8 is more than 3 times the performance of power7. But what is also interesting is that the software implementation is supported by Linux. So if some OpenPower hardware company developed a DFP plug-in, then Perl could have hardware Decimal Floating Point in the future on less expensive hardware. Developers could use the software versions for design/development/testing, and then production could use the hardware.

      You gave me a lot to look into, so thanks for your work on this subject.

      Regards...Ed

      "Well done is better than well said." - Benjamin Franklin

        Were you able to view the referenced article?

        Yes, but the only IBM (I think) processor that I have is a ppc64 on an old mac box - now running Debian Wheezy with gcc-4.6.3.
        I think that pre-dates the hardware improvements that you're talking about. Certainly, the _Decimal64/128 operations on that box are generally slower than on my (newer) Intel (Windows) and AMD (Ubuntu) boxes.
        My ppc64 processor does, however, utilise IBM's favoured "Densely Packed Decimal" format instead of Intel's/AMD's preferred "Binary Integer Decimal" format.
        DPD format is really nice - I can encode/decode that fairly quickly using a hash lookup, whereas BID format necessitates that the value be calculated.
        I assume that each of my processors has some special capacity to handle DFP operations, and that gcc is making use of that capability - but there's no guarantee that either of those assumptions is correct.

        the idea of getting 34 digits of decimal accuracy when using Perl is down right 'sexy'

        I agree, and Math::Decimal128 will give you that - but only for the basic arithmetic operations at the moment.
        Perl will improve markedly when someone gets around to (optionally) providing DFP processing built into perl. (But I think the time for that is not yet quite right.)

        You've actually now got me wishing I had a power8 box to play with.
        Which OS should it run to best test/experience these IBM capabilities ? ... AIX ?
        Is power8 bigendian ?

        Cheers,
        Rob
Re: Decimal Floating Point (DFP) and does Perl needs DFP?
by Laurent_R (Canon) on Jan 17, 2015 at 10:16 UTC
    As a side note, Perl 6 has a built-in Rat (rational number) class, so that if I write:
    my $c = 1/3;
    it will apparently store internally "1/3", a Rat type, not "0.333333...".

    I suppose this will remove some of the problems associated with floating point calculations, but probably not all of them.

    Je suis Charlie.
      Perl 6 has a built-in Rat (rational number) class

      It'll be interesting to see how well that performs and what limitations it has. (Are denominator and numerator limited to ivsize ?)
      Perl5 does provide access to rational arithmetic via Math::BigRat (which is slow) and Math::GMPq (which is fast, but requires the gmp library).

      Cheers,
      Rob

      Hello Laurent_R,

      Yes, but it's still in the software. It is one of the features of Perl 6 that I look forward to...Ed

      Regards...Ed

      "Well done is better than well said." - Benjamin Franklin

Re: Decimal Floating Point (DFP) and does Perl needs DFP?
by LanX (Saint) on Jan 16, 2015 at 19:23 UTC
    You misunderstood.

    The only advantage of decimal calculation is that the resulting problems are easier understood because humans have 10 fingers.

    But the problems won't go away, eg 1/3 stays periodic in decimal and binary representation.

    It's not C ... it's math!

    See also Humans have too many fingers

    Cheers Rolf

    PS: Je suis Charlie!

    Update

    Some languages offer support for rational numbers, i e 1/3 would be represented by two integers 1 and 3 instead of 0.333333...3, but this still keeps the representation of irrational numbers like the root of 2 or pi open.

      LanX,

        Did you look at the article I quoted?

      It has an example of a comparison of Binary Floating Point ( BFP ) and a DFP using both hardware and software to show:

      # ./dfp_hw 10 1.000001 60000000 double fund= 10.0000000000 interest= 1.000000999999999917733362053700 Decimal fund= 10.0000000000 interest= 1.000001000000000000000000000000 Print final funds double fund = 1141973124493563816969240576.0000000000 Decimal fund = 1141973130130727445029596475.9717600000

      That's a big difference! The difference starts after '11419731'...

      Also, my under graduate degree was in math!

      Regards...Ed

      "Well done is better than well said." - Benjamin Franklin

        > Did you look at the article I quoted?

        No it didn't load, but I never considered leaving my money for 60,000,000 years on a bank account.

        Did you read my post about calculating in cents right away? (The one I linked)

        Maybe a lexical option for decimal precision could be a reasonable feature request, such that for precision 2 the literal 10.01 is always internally represented as 1001.

        Cheers Rolf

        PS: Je suis Charlie!

        They example you used cheats by using a number that's rational in decimal but irrational in binary. If they had picked a number that's irrational in both (e.g. 1000001/999999 instead of 1000001/1000000), both the BFP and the DFP calculations would suffer from the same problem.

        DFP helps in some cases, but it might not be the best solution.

      It's not C ... it's math!

      Hi LanX, why do you keep shouting at perlmonks?

Re: Decimal Floating Point (DFP) and does Perl needs DFP?
by flexvault (Monsignor) on Jan 18, 2015 at 14:14 UTC

    Dear Monks,

    Just an update on this thread. I ran the following testcase on a Debian system w/o hardware DFP. I then did a Perl one-liner, that I believe does the same thing. I needed some printf statements to help figure out what I was doing, so I left them in to help any one else.

    /* ------------------------------------------------------------------ To compile: gcc -o dfpal2 dfpal2.c -L . -lDFPAL ------------------------------------------------------------------ +*/ #include <stdio.h> #include <stdlib.h> #include "dfpal.h" int main(int32_t argc, char **argv) { int i, count; char mystring[100]; char sn1[100]; char sn2[100]; int32_t init_err, init_os_err; char *err_str = NULL; dfpalflag_t st; decimal128 n1, n2; /* Declare the decimal floin +ting point numbers */ printf("Start:\n"); if (dfpalInit((void *)malloc(dfpalMemSize())) /* start the lib +rary */ != DFPAL_ERR_NO_ERROR) { fprintf(stderr,"DFPAL init error\n"); printf("Error:\n"); dfpalEnd(free); return (1); } n1 = dec128FromString(argv[1]); n2 = dec128FromString(argv[2]); /* assign a value by co +nverting the input string to a demical */ count = 60000000; printf("Initialized: %s \t %s\n",dec128ToString(n1,sn1),dec128ToSt +ring(n2,sn2) ); printf("for loop:\n"); for (i = 0; i < count; i++) { // printf("Step %d: %s \t %s\n",i,dec128ToString(n1,sn1),dec1 +28ToString(n2,sn2) ); n1 = dec128Multiply(n1, n2); /* do the maths */ // printf("Step %d: %d \t %d\n",i,i,i ); } printf("Final amount=%s\n",dec128ToString(n1,mystring)); /* pri +nt the results */ dfpalEnd(free); /* end the library cleanly */ }
    The results were better than I expected. YMMV.
    ~/dfpal# time ./dfpal2 10. 1.000001 Start: Initialized: 10 1.000001 for loop: Final amount=1141973130130727445029596475.971760 real 0m10.722s user 0m10.725s sys 0m0.000s ~/dfpal# time perl -e '$n1=10;$n2=1.000001;for ($i=0;$i<60_000_000;$i+ ++) { $n1=$n1*$n2;} print "Final amount=$n1\t$n2\n";' Final amount=1.14197312449358e+27 1.000001 real 0m8.698s user 0m8.701s sys 0m0.000s ~/dfpal#
    I had expected that the software only solution would be several times slower than the binary floating point hardware. Big Surprise!

    Comments welcome!

    Regards...Ed

    "Well done is better than well said." - Benjamin Franklin

      real 0m10.722s user 0m10.725s sys 0m0.000s


      With the Math::Decimal128 one-liner, on my Windows 7 box, perl-5.20.0:
      C:\>perl -MMath::Decimal128=":all" -le "$t = time();$n1=Math::Decimal1 +28->new('10', 0);$n2=Math::Decimal128->new('1000001', -6);for(1..6000 +0000){$n1 *= $n2}print $n1;$t2 = time() - $t;print $t2;" 114197313013072744502959647597176e-5 25
      25 seconds is not great, but OTOH it's not too bad for 60 million overloaded XSub calls.

      Cheers,
      Rob

        Hello syphilis ,

        I got 35-37 seconds on a Debian Linux with Perl 5.14.2.

        Regards...Ed

        "Well done is better than well said." - Benjamin Franklin

      I tried two extended double versions of given loop: __float80 (hardware) and __float128 (software emulation). The results were, respectively:

      1141973130132393144424071168.000000
      1141973130132392916052606976.000000
      
      Difference in speed -- about 60-fold.

      ps. would you mind stating the mathematically correct result (say, to six digits after decimal point)?
        would you mind stating the mathematically correct result (say, to six digits after decimal point)?

        According to Math::Decimal128:
        C:\>perl -MMath::Decimal128=":all" -le "$n1=Math::Decimal128->new('10' +, 0);$n2=Math::Decimal128->new('1000001', -6);for(1..60000000){$n1 *= + $n2}print $n1;" 114197313013072744502959647597176e-5
        Or, to 6 decimal places: 1141973130130727445029596475.971760 which is the same as flexvault got.

        With 80-bit long doubles, the calculation came out as:
        C:\>perl -MMath::LongDouble=":all" -le "$n1=Math::LongDouble->new('10' +);$n2=Math::LongDouble->new('1.000001');for(1..60000000){$n1 *= $n2}p +rint $n1;" 1.14197313013239314e+027
        Or, printing to 28 decimal digits (which implies a level of precision that we don't have):
        C:\>perl -MMath::LongDouble=":all" -le "$n1=Math::LongDouble->new('10' +);$n2=Math::LongDouble->new('1.000001');for(1..60000000){$n1 *= $n2}p +rint LDtoSTRP($n1, 28);" 1.141973130132393144424071168e+027
        This figure is equivalent to the first one you quoted.

        With __float128:
        C:\>perl -MMath::Float128=":all" -le "$n1=Math::Float128->new('10');$n +2=Math::Float128->new('1.000001');for(1..60000000){$n1 *= $n2}print $ +n1;" 1.14197313013072744502959647306684e+27
        This differs from your second figure - and I don't think there's any valid reason that they should differ.
        I'm inclined to think that mine is correct (surprise :-) because it matches up pretty well with the _Decimal128 calculation (where the respective precisions are reasonably close to each other).
        Might you have assigned a long double or double value that got cast to a __float128, instead of assigning an actual __float128 value ?

        As LanX points out, rounding is involved. The above one-liner programs all employ rounding to nearest with ties to even - the different results of course arising from the different precisions involved.

        Cheers,
        Rob
        > would you mind stating the mathematically correct result (say, to six digits after decimal point)?

        This should be correct for at least 50 digits after decimal point:

        1141973130130727445029596475.97165373568561208619839490929389898529956 +04918087368059784750017004880305063884032742217053556637130

        calculated using Math::BigFloat (which operates decimal with configurable precision) and taking advantage of exponentiation laws described here.

        (NB: BigFloat's own ->bpow() method is far slower)

        use strict; use warnings; use Math::BigFloat; my $exp=6e7; Math::BigFloat->precision(-100); my $x=Math::BigFloat->new('1.000001'); my $val=Math::BigFloat->new('1'); $\="\n"; for my $bit (reverse split //,sprintf '%b',$exp) { # print "$bit ",$x; $val->bmul($x) if $bit; $x->bpow(2); } $val->bmul(10); print "Result : $val"; my $syphilis=Math::BigFloat->new('1141973130130727445029596475.971760' +); print "diff: ", $val - $syphilis;

        took about a second on a netbook.

        Compilation started at Tue Jan 20 02:19:30 /usr/bin/perl -w /home/lanx/pm/big_expo.pl Result : 1141973130130727445029596475.97165373568561208619839490929389 +89852995604918087368059784750017004880305063884032742217053556637130 diff: -0.0001062643143879138016050907061010147004395081912631940215249 +982995119694936115967257782946443362870 Compilation finished at Tue Jan 20 02:19:31

        The results from the C-libs are as you can see only accurate till the third digit after decimal point.

        I'm not a big expert on BigFloat, if you see a problem, all feedback welcome. :)

        Cheers Rolf

        PS: Je suis Charlie!

        update

        you can easily increase the accuracy to 1000 digits after decimal point by setting precision to -1050, only takes a second.

        Someone please correct me, but taken that each operation causes up to one bit of error and that we perform 60e6 operations ...

        We'll need calculate with 60.000.000 bits to be 100% sure of avoiding rounding errors.

        So a brute force calculation should be ruled out.

        Furthermore what is mathematical correct?

        Though it's taught in school, any rounding that maps 0.5 to 1 is with 50% probability wrong.

        Cheers Rolf

        PS: Je suis Charlie!

        Update.

        In above, s/52606976/something else/ — a cast to __float80 was used for the lack of proper printf format. But the error is magnitudes higher than that, so... Regarding the expected result:

        $ echo 'scale=80; 10 * (1.000001^78125)^768' | bc
        1141973130130727445029596475.971653735685612
        
        Looks like the 60M figure was carefully picked :-)