[comp.lang.perl] Bad ulong cast on 80x86/87 machines

tneff@bfmny0.UU.NET (Tom Neff) (04/08/90)

Ouch.  Guess who else has problems with casting double to unsigned
longs!  Try this, 80x86 Perl users:

	perl -e '~~1'

BOOM, right?  Dies with my AT&T cc 4.1.5 anyway.  Floating point
exception.  Here's the problem.

Bitwise operators in Perl use unsigned long -- with Intel that's 32
bits.  So ~1 yields 4294967294, which Perl stores as double float.  You
can print it (not printf "%d" though!), assign it, even add to it, no
problem.  But if you use it in an integer context (as a bitwise operand,
for instance), Perl tries to cast the 'double' back to unsigned long
and dies!  The 80x87 only supports signed integers, so its range is

	-2147483648 <= x < 2147483647

and the attempt to store 4e+09 as a 32 bit integer blows up.

The only solution is to use the castulong() function instead of the
direct cast in C.  But there are two problems.  

 (1) Right now Configure doesn't test for the problem I just described,
but only for problems casting SMALL negative floats (-123) to unsigned
long, which the 80x87 does fine.  So it doesn't even make use of
castulong() on the 80x86 right now.

The fix for this is for Configure to build this one-line program:

	unsigned long ul = 4294967295.0;

and then try to compile it.  If it compiles OK it probably means the
direct cast will work.  On my system you get

	"foo.c", line 1: floating point constant folding causes exception

and a return code of 1 from cc.  To create the test C file, you run

	main() {
	  printf("unsigned long ul = %u.0;\n", (unsigned long) -1);
	}

 (2) Anyway, castulong() in its present form doesn't handle the
condition -- it only checks for negative float values.  It also has to
check for large positives and fold them to negative before casting.

Here is a tested patch to util.c that does this. 

*** /tmp/,RCSt1a00811	Sat Apr  7 22:55:33 1990
--- /tmp/,RCSt2a00811	Sat Apr  7 22:55:34 1990
***************
*** 1332,1337 ****
--- 1335,1346 ----
  double f;
  {
      long along;
+ 
+     double longmax = 1+(((unsigned long) -1) / 2);
+ 				/*  this should express LONG_MAX   */
+ 
+     if (f >= longmax)
+ 	return (unsigned long)(f - longmax);
  
      if (f >= 0.0)
  	return (unsigned long)f;



-- 
The real problem with SDI is     %/    Tom Neff
that it doesn't kill anybody.    /%    tneff@bfmny0.UU.NET

tneff@bfmny0.UU.NET (Tom Neff) (04/08/90)

I included the wrong patch for util.c in the referenced article.
Sorry!  Try this.

*** /tmp/,RCSt1a02173	Sun Apr  8 00:26:17 1990
--- /tmp/,RCSt2a02173	Sun Apr  8 00:26:18 1990
***************
*** 1332,1337 ****
--- 1341,1354 ----
  double f;
  {
      long along;
+ 
+ #define ulmax ((unsigned long) -1)
+ 
+     double longmax = (ulmax/2)+1;
+ 				/*  this should express LONG_MAX   */
+ 
+     if (f >= longmax)
+ 	return (unsigned long)(f - ulmax - 1);
  
      if (f >= 0.0)
  	return (unsigned long)f;