[comp.lang.perl] unsigned long ints/mips/floats + FIX

eichin@athena.mit.edu (Mark W. Eichin) (12/15/90)

I've run across a few things that the perl self-tests don't notice,
and I'd like to know whether these things fall under "undefined
behavior" or are real bugs. Since I've found what I think is a fix,
I'd like to call them bugs :-)

I've narrowed a larger problem down to the following example. ./perl
in this case is pl41, under Ultrix 3.1, castneg=undef, waitpid=undef,
built with gcc-osf-1.9.2.13. Note that this version of perl *passes*
make test:
	All tests successful.
	u=1.06667  s=2.06667  cu=7.25  cs=12.6667

Much reduced example:
% ./perl -e ' printf "%02x " x 4, unpack("CCCC",pack("N",0x8000000f));'
7f ff ff ff 

substitute any value less than 0x80000000 and it prints the expected
byte stream. HOWEVER:

% ./perl -e ' printf "%02x " x 4, unpack("CCCC",pack("N",(1<<31)+1));'
80 00 00 01

works. (No, I don't fully understand why it works, yet.)

Adding some debug statements to doarg.c:do_pack determined that
this compiler and architecture do range checking on double->long
conversions, and that
	doarg.c,579:	along = (long)str_gnum(fromstr);
if str_gnum (which comes out a double?) is greater than 0x7fffffff,
along is set to 0x7fffffff. 

In fact, the debug statement I added (when combined with -D8) prints:

/perl -D2056 -e ' printf "%02x " x 4, unpack("CCCC",pack("N",0xf000000f));'

[some lines elided]
1   E! P: L: U: 1.SINGLE = 'CCCC'
1   E! P: L: U: 2.EXPR =>
1   E! P: L: U: PACK (1001c388) 2 args:
1   E! P: L: U: P: 1.SINGLE = 'N'
1   E! P: L: U: P: 2.EXPR =>
1   E! P: L: U: P: LIST (1001c448) 1 args:
1   E! P: L: U: P: L: 1.SINGLE = 'num(4.02653e+09)'
1   E! P: L: U: P: LIST RETURNS ("4026531855")
1   E! P: L: U: P: 2.EXPR = '4026531855'
1   E! P: L: U: P: pack(N) 2: str = 4026531855.000000, along = 7fffffff
1   E! P: L: U: PACK RETURNS ""
1   E! P: L: U: 2.EXPR = ''
1   E! P: L: UNPACK RETURNS 4 ARGS ("127",...,"255")
..

4026531855 is actually 0xf000000f, but the assignment forces it down
to MAXLONG. On this architecture, changing the (long) to (unsigned
long) is safe, since casting of negative floats to unsigned long
preserves the bit pattern.

Looking at the places castneg=undef propagates to, and noting the
#ifdef mips that's already in util.c, and openly making the assumption
that htonl doesn't care if it's arguments are signed or unsigned, I'd
suggest the following patch (after which it still passes all tests):

*** doarg.c     Sat Dec 15 05:47:32 1990
--- ../../src/perl/doarg.c      Tue Dec 11 02:18:51 1990
***************
*** 577,587 ****
        case 'N':
            while (len-- > 0) {
                fromstr = NEXTFROM;
!               aulong = U_L(str_gnum(fromstr));
  #ifdef HTONL
!               aulong = htonl(aulong);
  #endif
!               str_ncat(str,(char*)&aulong,sizeof(unsigned long));
            }
            break;
        case 'L':
--- 577,587 ----
        case 'N':
            while (len-- > 0) {
                fromstr = NEXTFROM;
!               along = (long)str_gnum(fromstr);
  #ifdef HTONL
!               along = htonl(along);
  #endif
!               str_ncat(str,(char*)&along,sizeof(long));
            }
            break;
        case 'L':

				_Mark_ <eichin@athena.mit.edu>