http://www.perlmonks.org?node_id=1189780

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

Hi,

This demo script requires a compiler (eg gcc) that provides the _Decimal64 type:
use strict; use warnings; use Inline C => Config => BUILD_NOISY => 1, USING => 'ParseRegExp', #CLEAN_AFTER_BUILD => 0, ; use Inline C => <<'EOC'; #include <stdio.h> int foo(void) { _Decimal64 d64 = 0.DD; d64 *= -1.DD; if(d64 != 0.DD) printf("!= 0\n"); else printf("== 0\n"); if(d64 != -0.DD) printf("!= -0\n"); else printf("== -0\n"); return 0; } EOC foo();
That outputs:
!= 0 == -0
which tells me that -0 != 0 (and that is contrary to IEEE standards).

However, if I copy'n'past that C code across to a separate file and run it as a C program (using the very same compiler) I get correct output:
#include <stdio.h> int main(void) { _Decimal64 d64 = 0.DD; d64 *= -1.DD; if(d64 != 0.DD) printf("!= 0\n"); else printf("== 0\n"); if(d64 != -0.DD) printf("!= -0\n"); else printf("== -0\n"); return 0; }
AFAICS, all that has changed is that "foo" has been renamed to "main".
Yet, this now correctly outputs:
== 0 == -0
I'm seeing this behaviour on both Windows and Linux for a variety of perl versions and a variety of gcc compilers from 4.7.0 to 7.1.0 - so I'm hopeful that anyone else who has a gcc-4.x.x compiler (or later) will have no trouble reproducing the behaviour. Only other thing needed is Inline::C.

I've gazed upon the C code that Inline::C generates, and I've run the script through the C pre-processor, but I haven't managed to spot anything that helps me understand how this anomaly exists.
I've also spent some time trying various things that I hoped might trick Inline::C into behaving properly and I did find that this works:
use strict; use warnings; use Inline C => Config => BUILD_NOISY => 1, USING => 'ParseRegExp', #CLEAN_AFTER_BUILD => 0, ; use Inline C => <<'EOC'; #include <stdio.h> void _is_zero(_Decimal64 d64) { if(d64 != 0.DD) printf("!= 0\n"); else printf("== 0\n"); if(d64 != -0.DD) printf("!= -0\n"); else printf("== -0\n"); } int foo(void) { _Decimal64 d64 = 0.DD; d64 *= -1.DD; _is_zero(d64); return 0; } EOC foo();
All I've done there is to move the test that foo() was previously doing, into a separate sub. That produces correct output of:
== 0 == -0
Unfortunately the same approach doesn't work for the _Decimal128 type, where the same issue also arises.

There's nothing of much immediate importance with this ... but I can't stop scratching at it ;-)

Your insights are most welcome

Cheers,
Rob

Replies are listed 'Best First'.
Re: Inline::C producing an absurd result
by BrowserUk (Patriarch) on May 08, 2017 at 10:55 UTC

    My only suggestion is that you dump the values as bits and see where they differ. It might be easier to pass the values back to perl so that you can use unpack.

    Of course, that would probably mean assigning your D64 constants to variables, and that might change things.


    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". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit

      syphilis, I see the same problem with strawberry perl 5.24.1 for MSWin32-x64 with gcc 4.9.2, so I added a function in the c to dump the bytes (I didn't want the bytes leaving the c-environment, just in case trying to follow BrowserUK's unpack advice introduced a translation error (as he said, "might change things"):

      ////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// // https://perlmonks.org/?node_id=1189780 ////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> void _dump64(char* varname, _Decimal64 d64) { int i; void* ptr = (void*)(&d64); printf("%s = 0x", varname); for(i=0; i < sizeof(d64); i++) { printf("%02X ", 0xFF & *(char*)(ptr+i)); } printf("\n"); } #define dump64(v) _dump64(#v, v) int foo(void) { _Decimal64 pz64 = 0.DD, nz64 = 0.DD, no64 = -1.DD; nz64 = pz64 * no64; dump64(pz64); dump64(nz64); dump64(no64); if(nz64 != 0.DD) printf("!= +0\n"); else printf("== +0\n"); if(nz64 != -0.DD) printf("!= -0\n"); else printf("== -0\n"); return 0; } int main(int argc, char* argv) { foo(); } ----------------------------------------- pz64 = 0x00 00 00 00 00 00 C0 31 nz64 = 0x00 00 00 00 00 00 C0 B1 no64 = 0x01 00 00 00 00 00 C0 B1 == +0 == -0

      When I converted that to perl, and run using Inline::C 0.76, I get

      pz64 = 0x00 00 00 00 00 00 C0 31 nz64 = 0x00 00 00 00 00 00 C0 B1 no64 = 0x01 00 00 00 00 00 C0 B1 != +0 == -0

      That's definitely a strange one: inside C, the bits are the same, but the comparison-behavior is different.

      I see a lot of compiler options during the make- and make-install stages, and I'm wondering if one of them is messing up (intentionally modifying?) gcc behavior. What I would probably do next (if it were a problem I had to solve -- or if I had more time to spend on it today for my own education, but unfortunately, $work beckons for now): start making your pure-c compile look more like the Inline::C compile, adding more of the options, until such time as the pure-c failed...

      Let us know if you find anything more...

        I see a lot of compiler options during the make- and make-install stages, and I'm wondering if one of them is messing up (intentionally modifying?) gcc behaviour

        Yes, as Anonymous Monk has subsequently confirmed, that's precisely what's happening (in the form of the optimization option).

        Cheers,
        Rob
      My only suggestion is that you dump the values as bits and see where they differ

      Definitely worth a try, but it appears not to have helped much.
      So .... I changed the original Inline::C script to:
      use strict; use warnings; use Inline C => Config => BUILD_NOISY => 1, USING => 'ParseRegExp', #CLEAN_AFTER_BUILD => 0, ; use Inline C => <<'EOC'; #include <stdio.h> int foo(void) { _Decimal64 d64 = 0.DD, neg_zero, pos_zero = 0.DD; void *pd64 = &d64, *pneg_zero = &neg_zero, *ppos_zero = &pos_zero; int i; neg_zero = pos_zero * -1.DD; d64 *= -1.DD; if(d64 != pos_zero) printf("!= 0\n"); else printf("== 0\n"); if(d64 != neg_zero) printf("!= -0\n"); else printf("== -0\n"); for(i = 7; i >= 0; i--) printf("%02X", ((unsigned char*)pd64)[i]); printf("\n"); for(i = 7; i >= 0; i--) printf("%02X", ((unsigned char*)pneg_zero)[i]); printf("\n"); for(i = 7; i >= 0; i--) printf("%02X", ((unsigned char*)ppos_zero)[i]); printf("\n"); return 0; } EOC foo();
      It now outputs:
      != 0 == -0 B1C0000000000000 B1C0000000000000 31C0000000000000
      "B1C000..." is a _Decimal64 -0, and "31C000..." a _Decimal64 0.
      The first bit is the sign bit, the next 10 bits encode the exponent, and the 54-bit significand is an implicit 0 bit followed by the remaining 53 bits (all 0).
      When I run that revised C code as a C program it outputs (correctly):
      == 0 == -0 B1C0000000000000 B1C0000000000000 31C0000000000000
      I also modified the Inline::C rendition that was getting it right to:
      use strict; use warnings; use Inline C => Config => BUILD_NOISY => 1, USING => 'ParseRegExp', #CLEAN_AFTER_BUILD => 0, ; use Inline C => <<'EOC'; #include <stdio.h> void _is_zero(_Decimal64 d64) { _Decimal64 neg_zero, pos_zero = 0.DD; void *pd64 = &d64, *pneg_zero = &neg_zero, *ppos_zero = &pos_zero; int i; neg_zero = pos_zero * -1.DD; if(d64 != pos_zero) printf("!= 0\n"); else printf("== 0\n"); if(d64 != neg_zero) printf("!= -0\n"); else printf("== -0\n"); for(i = 7; i >= 0; i--) printf("%02X", ((unsigned char*)pd64)[i]); printf("\n"); for(i = 7; i >= 0; i--) printf("%02X", ((unsigned char*)pneg_zero)[i]); printf("\n"); for(i = 7; i >= 0; i--) printf("%02X", ((unsigned char*)ppos_zero)[i]); printf("\n"); } int foo(void) { _Decimal64 d64 = 0.DD; d64 *= -1.DD; _is_zero(d64); return 0; } EOC foo();
      It still outputs (correctly):
      == 0 == -0 B1C0000000000000 B1C0000000000000 31C0000000000000
      It's as though the != operation (in the problem script) believes that the values it's comparing are unsigned.

      Cheers,
      Rob
Re: Inline::C producing an absurd result
by Anonymous Monk on May 08, 2017 at 20:02 UTC

    I tried the C program with gcc-4.8.2 and gcc-6.3.0 — both gave the buggy output if -O optimization is enabled. Tried icc as well (correct result) and clang-4.0 (GNU decimal type extension not supported).

    GCC docs say "support of decimal float as specified by the draft technical report is incomplete". But see if you can find something on gcc.gnu.org/bugzilla .

      both gave the buggy output if -O optimization is enabled

      Yeah ... I should've thought of testing that. I see the same - and I'll report it to gcc bugzilla tonight.
      Thanks to all.

      UPDATE: Bug report is at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80692

      Cheers,
      Rob