Convert uint8_t to sint8_t

What is the best way to convert "uint8_t" to "sint8_t" in portable C.

What is the code that I came up with ....

#include <stdint.h> sint8_t DESER_SINT8(uint8_t x) ( return (sint8_t)((x >= (1u << 8u)) ? -(UINT8_MAX - x) : x); ) 

Is there a better / easier way to do this? Maybe a way without using a conditional?

Edit: Thanks guys. So, to summarize, what I learned already ...

  • sint8_t really called int8_t
  • 128 is expressed as 1 << 7 , not 1 << 8
  • 2s supplements "cancels one"

:)

So here is the updated version of my source code:

 #include <stdint.h> int8_t DESER_INT8(uint8_t x) ( return ((x >= (1 << 7)) ? -(UINT8_MAX - x + 1) : x); ) 
+4
source share
7 answers

1u << 8u 0x100u , which is greater than any uint8_t value, so the condition is never fulfilled. Your "conversion" procedure is actually simple:

 return x; 

what really makes sense.

You need to more clearly define what you want for the conversion. C99 defines a conversion from unsigned to signed integer types as follows ( §6.3.1.3 "Integer and unsigned integers" )

When a value with an integer type is converted to another integer type other than _Bool , if this value can be represented by a new type, it is unchanged.

...

Otherwise, the new type will be signed and the value cannot be represented in it; either the result is a specific implementation or the signal determined by the implementation is raised.

Thus, uint8_t values ​​between 0 and 127 are preserved, and the behavior for values ​​greater than 127 is undefined. Many (but not all) implementations simply interpret unsigned values ​​as a two-component representation of a signed integer. Perhaps you are really asking how to guarantee this behavior across platforms?

If so, you can use:

 return x < 128 ? x : x - 256; 

The value x - 256 is an int , the guaranteed value of x , interpreted as an 8-bit integer with two additions. The implicit conversion to int8_t then saves this value.

All this suggests that sint8_t means int8_t , since sint8_t not a standard type. If this is not the case, then all bets are disabled because the correctness of the proposed conversion depends on the guarantee that int8_t has a representation with two additions ( §7.18.1.1 "Integers of exact width" ).

If sint8_t is not some kind of weird type for a particular platform, it can use some other representation, like one-complement, which has a different set of representable values, so the transformation described above is determined by the implementation (therefore, not portable ) for certain inputs.


EDIT

Alf argued that it was "stupid," and that it would never be necessary for any production system. I disagree, but this is admittedly a corner case of a corner case. His argument is not entirely without virtues.

His claim that it is “ineffective” and therefore should be avoided, however, is unfounded. A reasonable optimizing compiler optimizes this on platforms where it is not needed. Using GCC on x86_64, for example:

 #include <stdint.h> int8_t alf(uint8_t x) { return x; } int8_t steve(uint8_t x) { return x < 128 ? x : x - 256; } int8_t david(uint8_t x) { return (x ^ 0x80) - 0x80; } 

compiled with -Os -fomit-frame-pointer, gives the following:

 _alf: 0000000000000000 movsbl %dil,%eax 0000000000000004 ret _steve: 0000000000000005 movsbl %dil,%eax 0000000000000009 ret _david: 000000000000000a movsbl %dil,%eax 000000000000000e ret 

Please note that after optimization, all three implementations are identical. Clang / LLVM gives exactly the same result. Similarly, if we create for ARM instead of x86:

 _alf: 00000000 b240 sxtb r0, r0 00000002 4770 bx lr _steve: 00000004 b240 sxtb r0, r0 00000006 4770 bx lr _david: 00000008 b240 sxtb r0, r0 0000000a 4770 bx lr 

Protecting your implementation from corner cases, when it does not require the costs of a “normal” case, is never “stupid”.

To the argument that this adds unnecessary complexity, I say: it’s harder to write a comment to explain the conversion and why it is there, or your successor trainee tries to debug the problem after 10 years, when the new compiler breaks the fluke that you were silent depending from all this time? Is it so hard to maintain?

 // The C99 standard does not guarantee the behavior of conversion // from uint8_t to int8_t when the value to be converted is larger // than 127. This function implements a conversion that is // guaranteed to wrap as though the unsigned value were simply // reinterpreted as a twos-complement value. With most compilers // on most systems, it will be optimized away entirely. int8_t safeConvert(uint8_t x) { return x < 128 ? x : x - 256; } 

When all is said and done, I agree that this is vague from above, but I also think that we should try to answer the question at face value. Of course, the best solution for standard C would be to associate unsigned to signed conversion behavior when the signed type is an integer with two intN_t without padding (thus defining behavior for all intN_t types).

+12
source

Converting uint8_t to int8_t substantially changes the order of the two half-ranges. High figures become low. This can be done using XOR.

 x ^ 0x80 

However, all numbers are still positive. This is not good. We need to enter the correct sign and restore the correct value.

 return ( x ^ 0x80 ) - 0x80; 

There you go!

+5
source

I do not know if this is of practical importance, but here a different approach came:

 uint8_t input; int8_t output; *(uint8_t *)&output = input; 

Note that:

  • int8_t is required to be a complement.
  • The corresponding signed and unsigned types must have the same representation for the overlapping part of their ranges, so that the pointer type can be accessed by a value in the range of both the signed and unsigned types.
  • This leaves only one bit, which should be the complement sign bit.

The only way to see that this argument may not be valid is if CHAR_BIT>8 and 8-bit integer types are extended integer types with trap traps that somehow determine whether the value is signed or not. However, the following similar code using char types obviously cannot fail:

 unsigned char input; signed char output; *(unsigned char *)output = input; 

because char types cannot have padding / trap bits.

A potential option would be:

 return ((union { uint8_t u; int8_t s; }){ input }).s; 

or for char types:

 return ((union { unsigned char u; signed char s; }){ input }).s; 

Edit: As Steve Jessop pointed out in another answer, int8_t and uint8_t should not have padding bits if they exist, so their existence implies CHAR_BIT==8 . Therefore, I am sure that this approach is valid. With that said, I would never use uint8_t and always explicitly use unsigned char if the implementation implements uint8_t as an extended integer type of equal size, because char types have special privileges with respect to alias rules and punning type, which make them more desirable.

+2
source

assuming sint8_t and uint8_t compatible with the destination, this works

 sint8_t DESER_SINT8(uint8_t x) { return x; } 
0
source

Uhm, ... I think you tried to return x if x can be represented in sint8 or abs (SINT8_MAX - x), if not, right?

In this case, here that works (you had a tiny bug, I think):

 #define HIGHBIT(X) ((X) & (1 << (sizeof(X) * 8 - 1))) char utos8(unsigned char ux) { return HIGHBIT(ux) ? -ux : ux; } 

Please note that with this code you can convert from any unsigned to a signed type , and the HIGHTBIT macro to a function.

Hope this helps.

0
source

If you want to avoid a branch, you can always do something crazy:

 int selector= 127 - x; // 0 or positive if x <=127, negative otherwise int selector>>= 8; // arithmetic rotate to get -1 or 0 int wrapped_value= x - 256; return (x&~selector)|(wrapped_value&selector); // if selector is 0, use x, otherwise, use the wrapped value. 
0
source

Assuming you sint8_t really int8_t from <stdint.h> , then it guaranteed two forms of padding and guaranteed no padding bits.

Assuming further that you want the opposite (implicit) conversion to work and give the original value.

Then, given the v value of type uint8_t , all you have to do is ...

  int8_t( v ) 

What is it.

In the C standard, AFAIK does not guarantee this conversion, but only the opposite conversion. However, there is no known system or compiler where it will not work (given that you have these types).

Forget about all the manual beat games. Or, to check if you are doing this correctly, convert back by simply setting the value to uint8_t and check if you get the original value for all cases. In particular, the formula you use gives - ((2 ^ n-1) -x) = 1 + x-2 ^ n, and the correct transformation for saving values ​​is x-2 ^ n.

Cheers and hth.,

- alf

-1
source

All Articles