DigitalNinja DigitalNinja - 29 days ago 21
C Question

Convert uint32 floating point representation to uint8

Without going into too many details, I have two embedded systems, neither of which can use the floating point library. In between them is a mobile application, where some calculations are performed. One calculation needs to keep the precision. This value is sent to the client via Bluetooth, as a byte array. When it's received I am then storing it as a uint32. This value is then shared again with the mobile application, where the precision is needed. There is no problem there because I am able to use Java's ByteBuffer class. The problem is I also need to share this value with a Z80 microprocessor as an uint8 because it is transmitted over a UART, and is used as an ADC count (0-255), so it loses the precision (but it's not needed on this end).

So I am doing this in my mobile app using Java:

int bits = Float.floatToIntBits(fAdcCount);
byte[] b = new byte[4];
b[0] = (byte)(bits & 0xff);
b[1] = (byte)((bits >> 8) & 0xff);
b[2] = (byte)((bits >> 16) & 0xff);
b[3] = (byte)((bits >> 24) & 0xff);


b
is then being sent in a BLE characteristic write to the BLE microcontroller. The BLE microcontroller then reads this buffer as a little-endian 32-bit word and stores it in a
uint32
. Looking at this
uint32
in the debugger shows the correct values, and like I said above I am able to put this
uint32
back into a byte array and send it to the mobile app and read it using Java's ByteBuffer class. This works fine, I get the correct floating point value.

The problem is I need the integer part of this floating point representation to send to a Z80 microprocessor over UART as a uint8 because it is used as an ADC count from 0-255.

So, how can I convert a floating point value that's been wrapped up into an uint32 (little endian byte order) to an uint8 losing the precision? I also know that the range is from 0-255, meaning there will never be anything greater than 255.0 to convert.

For example if I have this:

uint32 fValue = 0x43246ADD; // = 164.417...


How can I get this without using float?:

uint8 result = 164;

Answer

Some lightly tested code to get OP started. Uses lots of constants to allow customization and various degrees of error checking. OP has not addressed rounding: round to nearest, toward 0, or ??. Following truncates toward 0.

#include <stdint.h>
#define MANTISSA_BIT_WIDTH 23
#define BIASED_EXPO_MAX 255
#define EXPO_BIAS 127
#define SIGN_MASK 0x80000000

unsigned DN_float_to_uint8(uint32_t x) {
  if (x & SIGN_MASK) return 0; // negative
  int expo = (int) (x >> MANTISSA_BIT_WIDTH);
  if (expo == 0) return 0;  // sub-normal
  if (expo == BIASED_EXPO_MAX) return 255;  // Infinity, NaN
  expo -= EXPO_BIAS;
  if (expo > 7) return 255; // too big
  if (expo < 0) return 0; // wee number
  uint32_t mask = ((uint32_t)1 << MANTISSA_BIT_WIDTH) - 1;
  uint32_t mantissa = mask & x;
  mantissa |= mask + 1;
  mantissa >>= (MANTISSA_BIT_WIDTH - expo);
  return mantissa;
}

#include <stdio.h>
int main() {
  printf("%u\n", DN_float_to_uint8(0x43246a00)); // 164
  printf("%u\n", DN_float_to_uint8(0x437e0000)); // 254
  printf("%u\n", DN_float_to_uint8(0x437f0000)); // 255
  printf("%u\n", DN_float_to_uint8(0x43800000)); // 256
  printf("%u\n", DN_float_to_uint8(0x3f7fffff)); // 0.99999994
  printf("%u\n", DN_float_to_uint8(0x3f800000)); // 1
  printf("%u\n", DN_float_to_uint8(0x40000000)); // 2
  return 0;
}

Output

164
254
255
255
0
1
2

Useful IEEE 754 Converter


To round positive values to nearest (many ways to cope), ties away from zero, just look at the last bit to be shifted out.

  // mantissa >>= (MANTISSA_BIT_WIDTH - expo);
  // return mantissa;

  // shift as needed expect for 1 bit
  mantissa >>= (MANTISSA_BIT_WIDTH - expo - 1);
  // now shift last bit
  mantissa = (mantissa + 1) >> 1;
  // Handle special case
  if (mantissa >= 256) mantissa = 255;
  return mantissa;
Comments