## Any neat sign extension tricks for 10-bit to 16-bit?

11 posts / 0 new
Author
Message

Hi all,

I'd like to know neat tricks for converting 10-bit offset binary value with 3-bit scaling value to 16-bit signed integer.

Imagine you receive 13 actual bits from a device, of which 10 is the mantissa or value for a 10-bit DAC, and other 3 bits are the controls for a 7-step analog attenuator after the DAC. It means you can have a 16-bit range with 10-bit resolution. 10-bit offset binary means value 0 is most negative -512, 0x1FF is -1, 0x200 is midpoint 0, 0x201 is +1 and 0x3FF is most positive 511, and so to convert that to signed integer you can just XOR the MSB 0x200, after which 0x200=-512, 0x3FF=-1, 0x00=0, 0x01=+1, and 0x1FF=+511.

To convert that into linear 16-bit integer, I now use this code:

```int16_t convert(uint16_t indata)
{
// indata format
// 3 MSBs is the shift value 1..7, 0 never encountered
// 10 bits DAC value
// 3 LSBs unused bits, random even

uint16_t shiftval;
int16_t value;

// first set 10-bit offset binary value to MSB
value=(indata<<3)&0xFFC0;

// convert offset binary to signed integer, toggle MSB
value=value^0x8000;

// since shiftval bits  originally means 1=minimum range and 7=maximum range, we need to invert it for right shift
shiftval=7-(indata>>13);

// do the attennuation by 0 to 6 bits
value=value>>shiftval;
}

```

Now I'd like to be more efficient, so is there a way to do the sign extension without first copying to MSB and then doing right shifts with negated shift value? Can I just XOR it with some other value based on BIT9 and then do left shifts with (indata>>13)-1 ?

Would this work?

```int16_t convert(uint16_t indata)
{
// indata format
// 3 MSBs is the shift value 1..7, 0 never encountered
// 10 bits DAC value
// 3 LSBs unused bits, random even

uint16_t shiftval;
int16_t value;

// first set 10-bit offset binary value to LSB
value=(indata>>3)&0x3FF;

// convert offset binary to signed integer

if (value&0x200) // positive
{
value=value^0x200;
}
else // negative
{
value=value^0xFE00;
}

// shiftval bits  originally means 1=minimum range and 7=maximum range
shiftval=(indata>>13)-1;

// do the gain by 0 to 6 bits
value=value<<shiftval;
}

```

To sign extend 13 to 16 I do this: if(n & 0x1000) n |= 0x7000;

Imagecraft compiler user

Or even better: if (n & 0x1000) n |= 0xE000;

That actually sets the 3 topmost bits.

Einstein was right: "Two things are unlimited: the universe and the human stupidity. But i'm not quite sure about the former..."

Last Edited: Wed. Feb 16, 2011 - 12:15 PM

!?

Four legs good, two legs bad, three legs stable.

(I noticed this was more than four years ago. Got here searching for sign extension.) (At the risc of stating the obvious:)
To go from an offset representation to plain, subtract the offset:
value = ((indata>>3) & 0x3FF) - 0x200;
If bit 9 was on before, it is off after; if off before, bits 15-9 will be set after.
Would work with any offset, e.g., 300 - obviousness lying in the eye of the beholder.

...obviousness lying in the eye of the beholder

Z8 -- if really interested in this, there was a very spirited and extensive discussion on this in the main forum a while back.  Purists point their noses to the sky when simple and effective solutions such as yours are recommended.  [Sheesh, portability be damned--we are doing sign extension for an AVR8 after all.]

https://www.avrfreaks.net/forum/s...

Let me see if I can find the thread.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

I guess we ended up in something like...

inline int16_t foo ( uint16_t arg )
{
return ( arg ^ 0x200 ) - 0x200;
}

oloftangrot wrote:

I guess we ended up in something like...

inline int16_t foo ( uint16_t arg )
{
return ( arg ^ 0x200 ) - 0x200;
}

Here we go again... LOL--I reviewed the thread again when I dug it up.  I still fail to see why that solution is as good as the (IMO) straightforward and shorter and faster

```    int main(void)
{
int result;

if (result & 0x0200) result |= 0xfc00;
PORTD = result;
PORTD = result>>8;
}

46:	80 91 78 00 	lds	r24, 0x0078
4a:	90 91 79 00 	lds	r25, 0x0079
if (result & 0x0200) result |= 0xfc00;
4e:	91 fd       	sbrc	r25, 1
50:	9c 6f       	ori	r25, 0xFC	; 252
PORTD = result;
52:	8b b9       	out	0x0b, r24	; 11
PORTD = result>>8;
54:	89 2f       	mov	r24, r25
...

```

Two cycles; two words.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

I am beaten by the pig by one lousy clock cycle. Victory and honor is all yours theush.

Now y'all have got me going again...

-- http://en.wikipedia.org/wiki/Sig... Short Wikipedia article just says "pad with one bits".

-- https://graphics.stanford.edu/~s... A Stanford student included a method that is interesting, letting the C compiler extend a bitfield to full width.  I don't think that was explicitly brought up in the long thread.  Interesting enough to post here...

In C, sign extension from a constant bit-width is trivial, since bit fields may be specified in structs or unions. For example, to convert from 5 bits to an full integer:

```int x; // convert this from using 5 bits to a full int
int r; // resulting sign extended number goes here
struct {signed int x:5;} s;
r = s.x = x;
```

CodeVision seems to "do the job", but many cycles/code words.  GCC/Studio6 code word count isn't bad.  (still much more than two words, two cycles)

```int main(void)
{
int result;

PORTD = result;
PORTB = result>>8;
}
```
```int main(void)
{
int result;
46:	80 91 78 00 	lds	r24, 0x0078
4a:	90 91 79 00 	lds	r25, 0x0079

PORTD = result;
4e:	8b b9       	out	0x0b, r24	; 11
#include <avr/io.h>                    // Include AVR device-specific IO definitions
int main(void)
{
int result;
50:	26 e0       	ldi	r18, 0x06	; 6
52:	88 0f       	add	r24, r24
54:	99 1f       	adc	r25, r25
56:	2a 95       	dec	r18
58:	e1 f7       	brne	.-8      	; 0x52 <main+0xc>
5a:	36 e0       	ldi	r19, 0x06	; 6
5c:	95 95       	asr	r25
5e:	87 95       	ror	r24
60:	3a 95       	dec	r19
62:	e1 f7       	brne	.-8      	; 0x5c <main+0x16>

PORTD = result;
PORTB = result>>8;
64:	95 b9       	out	0x05, r25	; 5
}
```

-- http://stackoverflow.com/questio... This stackoverflow thread is pertinent/interesting.  One answer is very like my preferred method:

```int signExtension(int instr) {
int value = (0x0000FFFF & instr);
value += 0xFFFF0000;
}
return value;
}```

with a difference:  the adjustment constant is added to the base value.  (that thread is on a 32-bit "int" system)

Another post there lets the compiler do the work, but the shift method(s) are painful in size/speed:

```Anyway sign extending is much easier than the proposals. Just make sure you are using signed variables and then use 2 shifts.

long value;   // 32 bit storage
value=0xffff; // 16 bit 2's complement -1, value is now 0x0000ffff
value = ((value << 16) >> 16); // value is now 0xffffffff

If the variable is signed then the C compiler translates >> to Arithmetic Shift Right which preserves sign. This behaviour is platform independent.```

-- https://www.owasp.org/index.php/... I don't know what OWASP is, but the discussion is interesting (IMO) wrt this topic:

If one extends a signed number incorrectly, if negative numbers are used, an incorrect extension may result.

Consequences

• Integrity: If one attempts to sign extend a negative variable with an unsigned extension algorithm, it will produce an incorrect result.

Hmmm-- +1 for the pedants, eh?

-- http://aggregate.org/MAGIC/#Sign... University of Kentucky poster mentions already listed approaches:

Sign Extension

Although many instruction sets provide single machine instructions that implement sign extension of 2's-complement integers, I've been sent a number of tricks for sign extension. I've included them here because sign extension instructions generally work only on the data sizes directly understood by the processor, whereas these methods work on any bit precisions.

The most obvious method assumes that you have a signed right shift: to extend an a-bit number x to b bits, shift left by b-a, then signed shift that value right by b-a bits. I believe this has been widely known and used for many years -- I know I didn't invent it, but used it decades ago.

Jean-Charles Meyrignac suggested a shiftless variation that basically does a 1-bit add to flip high bits: if n is 2 to the a, simply compute (((x | -n) + (n/2)) ^ (n/2)). This version has been posted here for some time....

However, in August 2010, Joe Zbiciak sent me a little email with a much cleaner shiftless sign extension: ((x ^ n) - n) where n is the value of the top bit position in the number to be extended. Thus, to sign-extend an 8-bit value x, compute ((x ^ 128) - 128). It really couldn't be much simpler or more obvious... at least once you've been told how to do it. ;-)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.