Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019 
<== Date ==> <== Thread ==>

Subject: Re: Conversion problem from double to 32 bit unsigned int (in ao)
From: Andrew Johnson <anj@aps.anl.gov>
To: tech-talk@aps.anl.gov
Date: Thu, 11 Apr 2013 15:37:35 -0500
Hi Dirk,

On 2013-04-10 Dirk Zimoch wrote:
> Today I found a strange problem. I have a device that has 32 bit
> unsigned int output registers. I want to scale an analog value (e.g.
> -10.0 .. +10.0) to the raw range 0x00000000 ... 0xffffffff. The
> special_linconv() function of the ao record sets ESLO and EOFF correctly
> (ESLO=20/4294967295=4.65661287416E-09, EOFF=-10.0). All negative values
> work but any positive value gives wrong results. Even worse, the results
> are different on different machines: 0x80000000 on linux-x86, 0x7fffffff
> on vxworks-ppc604 for all positive values.
> 
> What happens? In the convert() function the analog (double) value is
> converted to epicsInt32 (type of the RVAL field). However if the double
> value is too large for the integer, strange and obviously implementation
> dependent things happen. I cannot find anything about this case in K&R.

Benjamin quoted the wrong part of K&R §A6.3 because we're not dealing with 
unsigned types here at all.  The first sentence of that section ends "if the 
resulting value cannot be represented in the integral type, the behavior is 
undefined", which explains why you're getting different results on different 
machines.

The problem is that with your ESLO and EOFF values the negative engineering 
values -10.0 .. <0 convert to the RVAL range 0 .. 0x7fffffff but 0x7fffffff is 
the largest possible value of an epicsInt32, so there's no range left for zero 
and the positive values to map to (you want it to convert 0.0 to 0x80000000 
but as a signed number that's the other end of the number range).

You need to offset everything so that the double-to-int32 conversion converts 
0.0 => 0x0 which means setting EOFF = 0.0 and using the ROFF field to bring 
the 0.0 value back to the right place afterwards.  For a UINT32 output 
register your device support should thus initialize ROFF to 0x80000000.

Now strictly speaking even this triggers Undefined Behavior because the record 
relies on a signed integer overflow in the subtraction of ROFF to convert the 
positive engineering values into the right part of the number range, but in 
practice I believe this still works on all our platforms.  Compiler optimizers 
are relying on more and more obscure rules that cause UB to break previously 
working code so this might break one day, but gcc does have a flag -fwrapv to 
tell it to allow signed overflows.


> The same thing can happen at other locations, too.
> Example:
> 
> record (mbbo, "DZ:mbbo") {
>      field (FLNK, "DZ:calcout")
> }
> record (calcout, "DZ:calcout") {
>      field (INPA, "DZ:mbbo")
>      field (CALC, "A*65536")
>      field (OUT,  "DZ:longout PP")
> }
> record (longout, "DZ:longout") {
> }
> 
> Writing 0xffff to the mbbo results in 0x8000000 on my 32 bit Linux PC.

longout.VAL is epicsInt32 which can't hold the value 4294836225, so you're 
getting an overflow when doing the conversion.

> field (CALC, "A<<16") on the other hand gives the expected result
> 0xffff0000.

You're probably relying on UB there again.  In C99 and C++0x you are not 
allowed to shift a 1-bit into or past the sign bit when shifting a signed 
integer left-wards.  Apparently the rules changed for those standards though, 
I'm not sure what the earlier standards actually said in that case.

There's a guy called John Reghr, a professor at the University of Utah, who 
writes about undefined behaviors in C and C++ code quite a lot; I recommend 
reading his stuff.  He ran an interesting competition recently to write a 
simple ascii-to-long numeric converter that must not exhibit UB, so no 
overflows allowed but it must detect numbers that won't fit into the long.

- Andrew
-- 
It is difficult to get a man to understand something, when his salary
depends upon his not understanding it. -- Upton Sinclair


Replies:
Re: Conversion problem from double to 32 bit unsigned int (in ao) Benjamin Franksen
References:
Conversion problem from double to 32 bit unsigned int (in ao) Dirk Zimoch

Navigate by Date:
Prev: Re: Conversion problem from double to 32 bit unsigned int (in ao) Benjamin Franksen
Next: Re: Conversion problem from double to 32 bit unsigned int (in ao) Benjamin Franksen
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019 
Navigate by Thread:
Prev: Re: Conversion problem from double to 32 bit unsigned int (in ao) Benjamin Franksen
Next: Re: Conversion problem from double to 32 bit unsigned int (in ao) Benjamin Franksen
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019 
ANJ, 20 Apr 2015 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·