UART to signed int

WBahn

Joined Mar 31, 2012
32,854
Is it entirely predictable how this line will behave when uart_hi_byte > 128?
Code:
   int16_t value = 256 * uart_hi_byte + uart_lo_byte;
That's actually a good point, however the problem isn't with uart_hi_byte being greater than 128, since uart_hi_byte is an unsigned data type. But, multiplying it by a constant means that the compiler will make the result of the expression an uint8_t, with the associated truncation of the upper bits, before implicitly casting it to an int16_t. So it should actually be:

Code:
   int16_t value = 256 * (int16_t) uart_hi_byte + (int16_t) uart_lo_byte;
The second one (before the lo byte) is unneeded, but I like to be consistent.

Now the question is what happens when the product of two positive numbers exceeds the range of the positive values that can be represented by that data type. I thought that the standard required that the result wrap around like you would expect, but I haven't been able to find that in the standard. Instead, I found the following:

6.3.1.3 Signed and unsigned integers

1) When a value with integer type is converted to another integer type other than _Bool, if
the value can be represented by the new type, it is unchanged.
2) Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or
subtracting one more than the maximum value that can be represented in the new type
until the value is in the range of the new type.
3) Otherwise, the new type is signed and the value cannot be represented in it; the result is
implementation-defined.

That third clause makes this unsafe.

This does seem to be begging for the use of a union. The problem is that now you have the issue of endianness, which is going to lead to portability issues (which you may not care about).

By combining the two byte values arithmetically, we avoid the endianness issue. Then we can use a union to access the bit representation as a signed value.

Code:
int32_t Sixteenbit(uint8_t uart_hi_byte, uint8_t uart_lo_byte)
{
   union
   {
      int16_t value_s;
      uint16_t value_u;
   } byte2int;

   byte2int.value_u = 256 * (uint16_t) uart_hi_byte + (uint16_t) uart_lo_byte;

   return (int32_t) byte2int.value_s;
}
I haven't tried any of this, so I might have a goof here or there.

One thing I'm not clear about is how the compiler instructs the processor to do the intermediate calculations before outputting the int16_t.
Oh, that is a very murky subject. High-end modern compilers each have a little sorcerer whose essence was digitized and infused into them, just before the sorcerer was ground up and mixed in the the magic dust used to infuse modern processors themselves.

They look for patterns that allow them to exploit tricks and it is highly compiler-dependent and also target-processor-dependent.

Most compilers will recognize multiplication and division by integer powers of two

As it has 32-bit registers, I would expect it to do the calculation in 32-bit then lose the top 16 bits to give the answer. (Am I right? Is it compiler-dependent?)
If the number is positive (in 32-bit) then does it really output a negative 16-bit integer? Because it really is a numeric overflow.
VERY compiler and processor dependent. But, if the compiler is properly written, it will produce machine code that uses the hardware's instruction set to implement the behavior required by the standard. Beyond that, anything is fair game.

For example, something like

n = m/42;

where m is an integer data type, will usually not result in a division operation being used even if the processor supports it. This is because. for most processors, division is extremely expensive relative to multiplication. So, instead, this will be changed to

n = m * (1/21) / 2;

The 1/21 is then represented as a fixed point fractional value, but integer division is used with the desired result then being shifted to put the radix point at the end of the register. The representation for 1/21 is carefully constructed so that NO value of m can result in the rounding at the end violating the rounding rules.
 

Thread Starter

Ian0

Joined Aug 7, 2020
13,132
n = m/42;
I know about that from writing assembler. Never divide by a constant.
I always used the UMULL or SMULL instruction and multiplied by (2^32)/n, then took the most significant of the two output registers (effectively dividing the result by 2^32).
I think that the compiler does it rather cleverer, so that it can get away with a standard 32-bit multiply more often than not.
 
Top