Introduction
This article will show how to convert a float
value into an integer according to IEEE 754 rules.
I will show two ways.
One is faster than the other one, particularly on the unpack
function.
Background
Sometimes, you need to send a float
value over a protocol (serial or network) but the protocol you are using does not send/receive float
value; it means this protocol supports only integer (signed or unsigned). This is very common during the developing of Microcontroller project.
In my personal experience, I've established a serial communication between two different microcontroller families (STM32F3 and STM32F7) using the Modbus rules.
If this scenario happens, you need to find a way to convert a float
into an integer (from sender point of view), then convert the integer into its float
value (receiver point of view).
A very common way to do this is using the IEEE 754 conversion.
The code was based on 32 bit but can easily be expanded to 64 bit. This could be necessary if you need to have more precision.
On the web, there is a lot of literature about this kind of approach, e.g., https://en.wikipedia.org/wiki/IEEE_754.
Using the Code
This code is quite portable because it has been written using very standard C code (I'm using it on Microcontroller, Windows O.S, Linux and Embedded world as well).
I've created two files (header and code) and two functions.
Here is their prototype and explanation.
This function returns an unsigned long
value that is a representation of the input float
value.
I have called it pack
.
uint32_t pack754_32 ( float f );
The unpaack
function returns float
values joined to the unsigned long
value pass to it.
float unpack754_32( uint32_t floatingToIntValue );
If the input value does not have a valid IEEE 754 representation, an undefined value has returned. I have called it unpack
.
In this example, to have platform portability, I've used the defined type uint32_t
(stand for unsigned long
).
FIRST WAY (faster)
uint32_t pack754_32( float f )
{
uint32_t *pfloatingToIntValue;
pfloatingToIntValue = &f;
return (*pfloatingToIntValue);
}
float unpack754_32( uint32_t floatingToIntValue )
{
float *pf, f;
pf = &(floatingToIntValue);
f = *pf;
return f;
}
Second Way (More Academic)
By using the union and its implicit conversion, this is another way to convert float
into unsigned long
value and vice-versa. This method will use BIT WISE functionalities The union will pack/unpack the value f
into its representation:
typedef union UnFloatingPointIEEE754
{
struct
{
unsigned int mantissa : 23;
unsigned int exponent : 8;
unsigned int sign : 1;
} raw;
float f;
} UFloatingPointIEEE754;
The Bit
operation extracts the bit in order to create the desired value (exponent and mantissa), according to IEEE 754 method.
I've used a #define
instead of a function or inline function, because this is a faster way.
#define NTH_BIT(b, n) ((b >> n) & 0x1)
#define BYTE_TO_BIN(b) (( b & 0x80 ) ) |\
(( b & 0x40 ) ) |\
(( b & 0x20 ) ) |\
(( b & 0x10 ) ) |\
(( b & 0x08 ) ) |\
(( b & 0x04 ) ) |\
(( b & 0x02 ) ) |\
( b & 0x01 )
#define MANTISSA_TO_BIN(b) (( b & 0x400000 ) ) |\
(( b & 0x200000 ) ) |\
(( b & 0x100000 ) ) |\
(( b & 0x80000 ) ) |\
(( b & 0x40000 ) ) |\
(( b & 0x20000 ) ) |\
(( b & 0x10000 ) ) |\
(( b & 0x8000 ) ) |\
(( b & 0x4000 ) ) |\
(( b & 0x2000 ) ) |\
(( b & 0x1000 ) ) |\
(( b & 0x800 ) ) |\
(( b & 0x400 ) ) |\
(( b & 0x200 ) ) |\
(( b & 0x100 ) ) |\
(( b & 0x80 ) ) |\
(( b & 0x40 ) ) |\
(( b & 0x20 ) ) |\
(( b & 0x10 ) ) |\
(( b & 0x08 ) ) |\
(( b & 0x04 ) ) |\
(( b & 0x02 ) ) |\
( b & 0x01 )
Finally, here is the definition of pack
/unpack
functions.
Those ones use the previous define to return the desired value.
The pack
function uses the implicity conversion of the union. By assigning the value of the union, the sign, exponent and mantissa will auto-fitted.
uint32_t pack754_32 ( float f )
{
UFloatingPointIEEE754 ieee754;
uint32_t floatingToIntValue = 0;
ieee754.f = f;
floatingToIntValue = (((NTH_BIT(ieee754.raw.sign, 0) << 8) |
(BYTE_TO_BIN(ieee754.raw.exponent))) << 23 ) | MANTISSA_TO_BIN(ieee754.raw.mantissa);
return floatingToIntValue;
}
The unpack
function will use the bit wise operations to create the ad-hoc unsigned int
value, according to IEEE754 standard.
float unpack754_32( uint32_t floatingToIntValue )
{
UFloatingPointIEEE754 ieee754; unsigned int mantissa = 0;
unsigned int exponent = 0 ;
unsigned int sign = 0;
sign = NTH_BIT(floatingToIntValue, 31);
for( int ix=0; ix<8; ix++)
exponent = (exponent | (NTH_BIT(floatingToIntValue, (30-ix))))<<1;
exponent = exponent>>1;
for( int ix=0; ix<23; ix++)
mantissa = (mantissa | (NTH_BIT(floatingToIntValue, (22-ix))))<<1;
mantissa = mantissa >> 1;
ieee754.raw.sign = sign;
ieee754.raw.exponent = exponent;
ieee754.raw.mantissa = mantissa;
return ieee754.f;
}
How to Use It
I have also provided a very simple test function that packs and unpacks some values.
void TestPackUnpack ( void )
{
uint32_t n;
float f;
n = 0x3FB4FDF4; f= 1.414
f = unpack754_32(n);
n = pack754_32(1.414);
f = unpack754_32(n);
n = pack754_32(-1.259921);
f = unpack754_32(n);
n = pack754_32(0.58);
f = unpack754_32(n);
n = pack754_32(-0.588);
f = unpack754_32(n);
n = pack754_32(2);
f = unpack754_32(n);
n = pack754_32(-3);
f = unpack754_32(n);
}
Points of Interest
I think this article will highlight some important functionality related to the C Language, such as implicit conversion, bit wise operator and so on.
The IEEE 754 conversion method can be used also to convert integer. In this way, if my protocol doesn't support float
or floating point values, I can always use those methods to share information over the protocol.