The Sleeper



  • Today's find comes from a piece of code that had been used in different applications running on different processors:

    int16 ix; 

    int16 sample;

    ix = (sample < 0) /* 0 <= ix < 2048 */
          ? (~((uint16) sample) >> 4)  /* 1's complement for negative values */
          : (sample >> 4);
     

    What's up here? The original programmer had intended to negate the sample, but for some reason he did not trust (or know about) the unary - operator and went for bit operation instead. As the C compiler is allowed to promote short integers to full int during calculations, this is guaranteed to fail sooner or later. It happened when porting to new hardware after more than 8 years in production. 



  • The original programmer was pretty clear that 'sample' is supposed to be interpreted as ones' complement.  Are you compiling this on a two's complement machine?  Then negating a number will produce an answer that's off by one.  Consider 0xFFF0.  The original code interprets this as -15, then computes 15>>4 == 0. If you simply negate this you will be interpreting it as -16 and then computing 16>>4 == 1.

    Also, beware of the 0x8000 case.  The original code treats this as -32767 and then computes 32767>>4 == 2047.  But if you negate this number on a two's complement machine you'll overflow.  Then you'll right shift 0x8000 which can produce 0x0800 or 0xF800 depending on how you do it, giving you 2048 or -2048 which are both out of range.




  • You have a point there. The input is 2's complement and the code runs on 2's complement processor, but it may be that it originally ran on 1's complement processor. It certainly hasn't done so in years. Then again, if the hardware was native 1's complement, why didn't the original programmer just use the usual negation anyway? I can't imagine a situation where it makes sense to do processing in native format and then force the result to 1's complement. To get around the 0x8000 issue, I believe that saturating to -32767 would be good enough and not give the off by one results current code gives.



  • Needs more context, I think.  The variable name "sample" suggests something coming in from external hardware, which might be sending its output in ones' complement, or perhaps read from a data file in a standard format that specifies ones' complement.  If so, it may make sense to keep the data internally in that format -- especially since conversion from ones' complement to two's complement is lossy (you lose the distinction between +0 and -0).  This might still be a WTF, but far all I know it might just as well make perfect sense under the circumstances.



  • OK, I'll explain more. The variable 'sample' a audio sample in Q15 fixed point format. Theoretically the format is left-aligned 13-bit integer, but everywhere I know this just means that the last 3 bits are sometimes discarded. The samples can come from a variety of sources. I can not be sure of all of them, but the ones I know just happily output whatever the native integer type is. Because the signal is compressed even more after this step (this is a part of linear->A-law conversion), the end result is mostly bit exact between 1's and 2's complement negation at this point.


Log in to reply