[comp.lang.c] Bug converting unsigned to double in BSD 4.23

jsdy@hadron.UUCP (Joseph S. D. Yao) (11/18/86)

In article <213@cartan.Berkeley.EDU> ballou@brahms (Kenneth R. Ballou) writes:
>main ()
>{
>  printf ("%u\n", ~ ((unsigned) 0));
>  printf ("%lf\n", (double) (~ ((unsigned) 0)));
>  printf ("%lf\n", 4294967295.0); /* surely double is large enough for this? */
>}
>% bug
>4294967295
>-1.000000
>4294967295.000000

This didn't work (actually, worked as expected!) on our ISI 68000-
based machine.  It worked as above when I compiled and ran it on
the VAX.  What's happening on the VAX is that it's folding the
constant (~((unsigned int) 0)) to the bit pattern $-1, while the
68K is clearing and negating a register: no problem so far!  But
apparently type is lost or ignored on the VAX, because it then
does a cvtld (convert long to double).  This is clever-not-clever
use of the VAX FP instruction set, which doesn't have an unsigned
long data type.  The 68K calls a subroutine:
	jbsr	dpufloat
which comprehends and translates unsigned longs, as dpfloat does
for signed longs.  (Doing it in software has occasional advantages
over doing it in hardware.)
-- 

	Joe Yao		hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}
			jsdy@hadron.COM (not yet domainised)

rgenter@labs-b.bbn.com (Rick Genter) (11/18/86)

In article <618@hadron.UUCP> jsdy%hadron.uucp@brl.ARPA (Joseph S. D. Yao)
writes:

> In article <213@cartan.Berkeley.EDU> ballou@brahms (Kenneth R. Ballou) writes:
> [ program which shows that (double) ~ ((unsigned) 0) is -1.00, not 2**32-1 ]
>
> This didn't work (actually, worked as expected!) on our ISI 68000-
> based machine.  It worked as above when I compiled and ran it on
> the VAX.  What's happening on the VAX is that it's folding the
> [...]

This is indeed a bug in the 4.[23] BSD VAX compilers, though it can also be
attributed to a (mis)interpretation of how to deal with ~.  I don't have K & R
or H & S in front of me, but from the April 1985 draft of X3J11:

	"The result of the ~ operator is the bitwise complement of its
	 operand (that is, each bit in the result is set if and only if
	 the corresponding bit in the converted operand is not set).
	 The operand must have integral type.  The integral widening
	 conversion is performed, and the result has the widened type."

From this it is clear that the result of ~ing an (unsinged int) [integral type]
should be an (unsigned int) [widened type].

> (Doing it in software has occasional advantages
> over doing it in hardware.)

Allow me to rephrase that: Doing it *right* in software is almost always 
preferable to doing it *wrong* in hardware.

					- Rick
--------
Rick Genter 				BBN Laboratories Inc.
(617) 497-3848				10 Moulton St.  6/512
rgenter@labs-b.bbn.COM  (Internet new)	Cambridge, MA   02238
rgenter@bbn-labs-b.ARPA (Internet old)	seismo!bbncca!rgenter (UUCP)

woods@hao.UUCP (Greg Woods) (11/20/86)

In article <618@hadron.UUCP>, jsdy@hadron.UUCP (Joseph S. D. Yao) writes:
> In article <213@cartan.Berkeley.EDU> ballou@brahms (Kenneth R. Ballou) writes:
> >main ()
> >{
> >  printf ("%u\n", ~ ((unsigned) 0));
> >  printf ("%lf\n", (double) (~ ((unsigned) 0)));
> >  printf ("%lf\n", 4294967295.0); /* surely double is large enough for this? */
> >}
> >% bug
> >4294967295
> >-1.000000
> >4294967295.000000
> 
> This didn't work (actually, worked as expected!) on our ISI 68000-
> based machine. 

  I've seen something similar, and I think your bug probably has to do with
the same thing. On a VAX, and every machine I've worked on EXCEPT the ISI-68K,
the first 32 bits of a double form a float. Not true on the 68000. Consider
the following trivial C program:

main() { float f=1.0; test1(f); exit(0); }
test1(f) float f; { printf("test1: f=%f\n",f); test2(&f); }
test2(f) float *f; { printf("test2: f=%f\n",*f);

What one observes is that on the VAX, both printf's print the same value,
as they do on a Pyramid 90X,
but on the ISI 68020/68881 (both with and without the -f option to enable
use of the 68881) they are different. Since I was curious, I ran the same
program on a Sun-2 and a Sun-3 that I have access to. Different values there
too. The reason is that according to the C standard, when f is passed to test1,
it is converted to a double and placed on the stack. When test1 calls test2,
it DOESN'T CONVERT the stacked value back to a float before calling test2,
so that test2 is passed the address of something that is really a double,
not a float as declared in the code. This is a BUG, and it has bitten us
badly in many big libraries where we have little stubs designed to allow
the calling of FORTRAN routines from C without having to observe FORTRAN
calling conventions, e.g.:

sub(f) float f; {
   sub_(&f);
}

This allows C calls like "sub(2.)" which would otherwise require the creation
of a temporary variable like "float temp=2; sub_(&temp)". I maintain this
is a BUG, because I specifically declare that what I am passing the address
of is a float, when in fact it's a double.

--Greg
-- 
{ucbvax!hplabs | decvax!noao | mcvax!seismo | ihnp4!seismo}
       		        !hao!woods

CSNET: woods@ncar.csnet  ARPA: woods%ncar@CSNET-RELAY.ARPA

throopw@dg_rtp.UUCP (11/22/86)

> woods@hao.UUCP (Greg Woods)
>   I've seen something similar, and I think your bug probably has to do with
> the same thing. On a VAX, and every machine I've worked on EXCEPT the ISI-68K,
> the first 32 bits of a double form a float.

The two problems are very likely unrelated, except in a most tenuous
sense.  The original bug in the ((double)~(unsigned)1) was a either a
problem in implementing the conversion from type unsigned to double, or
a problem in deciding what type a twiddle of an unsigned type was.  The
nature of the error (integer max becomes -1) made it very likely that
the original problem was an integer format problem, not a floating point
format problem.

> This is a BUG, and it has bitten us
> badly in many big libraries where we have little stubs designed to allow
> the calling of FORTRAN routines from C without having to observe FORTRAN
> calling conventions,

It is not clear that this is a bug under K&R semantics.  I think it
likely that ANSI would consider this a bug, since they already mandated
that taking the address of a character formal would "work right".  In
essence, it means that the compiler would be obliged to copy the
declared-float-but-really-double formal argument into an invisible float
local variable when the address of the formal is taken.

--
There was a radical defect somewhere, and I must search it out and cure it.
                                --- Mark Twain
-- 
Wayne Throop      <the-known-world>!mcnc!rti-sel!dg_rtp!throopw

mouse@mcgill-vision.UUCP (11/23/86)

In article <295@hao.UUCP>, woods@hao.UUCP (Greg Woods) writes:
> On a VAX, and every machine I've worked on EXCEPT the ISI-68K, the
> first 32 bits of a double form a float.
And it has led to some of the *sloppiest* code, just because some
machines will let you get away with it....
> Not true on the 68000.  Consider the following trivial C program:

> main() { float f=1.0; test1(f); exit(0); }
> test1(f) float f; { printf("test1: f=%f\n",f); test2(&f); }
> test2(f) float *f; { printf("test2: f=%f\n",*f);

> The reason is that according to the C standard, when f is passed to
> test1, it is converted to a double and placed on the stack. [...] so
> that test2 is passed the address of something that is really a
> double, not a float as declared in the code.  This is a BUG, [...]

Yes - in your code.  K&R, page 205:

	C converts all float actual parameters to double, so formal
	parameters declared float have their declaration adjusted to
	read double.

That is, the argument to test1 is not a float, it is a double (which
has been declared in a misleading manner).  Thus test1 is passing a
pointer to double, not to float.  But test2 is expecting a pointer to
float, not to double.  Of *course* it's losing!

> it DOESN'T CONVERT the stacked value back to a float before calling
> test2, so that test2 is passed the address of something that is
> really a double, not a float as declared in the code.

The code declares f to be a double, albeit in a confusing way.

I agree that having C behave this way is a misfeature.  I posted
something very similar to net.lang.c a while ago, and someone (I forget
who) pointed this detail of the C definition out to me then.

					der Mouse

USA: {ihnp4,decvax,akgua,utzoo,etc}!utcsri!mcgill-vision!mouse
     think!mosart!mcgill-vision!mouse
Europe: mcvax!decvax!utcsri!mcgill-vision!mouse
ARPAnet: think!mosart!mcgill-vision!mouse@harvard.harvard.edu

[USA NSA food: terrorist, cryptography, DES, drugs, CIA, secret, decode]

roger@celtics.UUCP (Roger Klorese) (11/25/86)

In-Reply-To: <562@mcgill-vision.UUCP>

Cc:

Bcc:


In article <562@mcgill-vision.UUCP> you write:
>In article <295@hao.UUCP>, woods@hao.UUCP (Greg Woods) writes:
>> On a VAX, and every machine I've worked on EXCEPT the ISI-68K, the
>> first 32 bits of a double form a float.
>And it has led to some of the *sloppiest* code, just because some
>machines will let you get away with it....
>> Not true on the 68000.  

Or on any other IEEE-conforming floating-point implementation.  The IEEE
formats call for eight bits of exponent on floats, and eleven on doubles.
Any piece of code depending on this silly coincidence on the VAX and other
very common but non-compliant architectures is askin' fer it.

-- 
===================================
"Speak for the company?!   Gee, I have a hard enough time speaking for myself!"

====================  Roger B.A. Klorese
|    ///==\\       |  Celerity Computing (Eastern Region)
|   ///            |  40 Speen St., Framingham, MA 01701  +1 617 872-1552
|   \\\            |  
|    \\\==//       |  celerity!rklorese@sdcsvax.ARPA (sdcsvax!celerity!rklorese)
====================  celtics!roger@seismo.CSS.GOV   (seismo!celtics!roger)

guy@sun.uucp (Guy Harris) (12/01/86)

> But apparently type is lost or ignored on the VAX, because it then
> does a cvtld (convert long to double).  This is clever-not-clever
> use of the VAX FP instruction set, which doesn't have an unsigned
> long data type.

Not on 4.3BSD, it doesn't!  It generates a "movd", but the constant it moves
is "-1.0", not "4.2949...e9".

It's probably due to the following bit of bizarre code in "makety" in
"trees.c" - that's part of the machine-independent code, so it will affect
other machines:

	if( p->in.op == ICON && p->tn.rval == NONAME){
		if( t==DOUBLE||t==FLOAT ){
			p->in.op = FCON;
			if( ISUNSIGNED(p->in.type) ){
				p->fpn.dval = /* (unsigned CONSZ) */ p->tn.lval;
				}
			else {
				p->fpn.dval = p->tn.lval;
				}

			p->in.type = p->fn.csiz = t;
			return( clocal(p) );
			}
		}

The commented-out cast is rather peculiar.  If you uncomment it, it looks
like it should work - and it does, at least where I tried it.

> The 68K calls a subroutine:
> 	jbsr	dpufloat
> which comprehends and translates unsigned longs, as dpfloat does
> for signed longs.  (Doing it in software has occasional advantages
> over doing it in hardware.)

*Our* 68Ks don't do this - they do the conversion at compile time, as they
should!  (Although our compiler currently generates code that does the same
wrong thing that the code generated by the VAX compiler does.)  It's not a
question of whether it's done in hardware or software; it's a question of
whether the compiler does the conversion correctly at compile time or not.
-- 
	Guy Harris
	{ihnp4, decvax, seismo, decwrl, ...}!sun!guy
	guy@sun.com (or guy@sun.arpa)