Skip to content

dottspina/cmedfloat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cmedfloat

IEEE 11073-20601 specifies formats and protocols for representing and exchanging health data between Personal Health Devices such as weighing scales, blood pressure monitors or blood glucose monitors.

Within Bluetooth, IEEE 11073-20601 SFLOAT and FLOAT types are known as medfloat16 and medfloat32, and used to represent some GATT characteristics of non-integer type like Blood Pressure Measurement or Non-Methane Volatile Organic Compounds Concentration. Although IEEE 11073-20601 is not a pubic standard, transcoding Bluetooth characteristics to IEEE 11073-20601 attributes is described in the Personal Health Devices Transcoding white paper.

libcmedfloat is a simple C11 library for transcoding IEEE 11073-20601 SFLOAT and FLOAT values and representations:

  • convert IEEE 754 floating point numbers to SFLOAT-Type and FLOAT-Type
  • encode and decode SFLOAT-Type and FLOAT-Type binary representations
  • compare SFLOAT-Type and FLOAT-Type values

Also includes basic command line tools (convert, encode, decode).

Status

I wrote this library while working on a Bluetooth Environmental Sensing Service implementation.

It's made public so others don't have to reinvent the wheel.

Apache-2.0 license, on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.

medfloat16 and medfloat32

SFLOAT (medfloat16) and FLOAT (medfloat32) values are specified by a mantissa and an exponent to base 10: $x = 10^E \times m$

Mantissa and exponent are both signed integers in two's-complement binary representation, so that their value ranges are of the well known form $[-2^{N-1} , 2^{N-1} -1]$.

Note that the same number accepts multiple representations: e.g. 2 can be represented in SFLOAT-Type as either 2e0, 20e-1, 200e-2 or 2000e-3.

This is on purpose: although the dot floats with the exponent, the latter is rather intended to be fixed and indicate the precision of the measurement, e.g. to represent degree Celsius measured by a sensor with a precision of one hundredth, a fixed exponent of value -2 would be used.

Some examples can also be found in Personal Health Device Implementation Guide, Mder FLOATs and SFLOATs.

Special medfloat32 and medfloat16 values with a zero-exponent are reserved for representing:

  • Positive and negative infinity: equivalent to IEEE 754 signed infinity, e.g. division by zero
  • NaN: equivalent to IEEE 754 NaN, e.g. zero divided by zero, but also used to indicate missing data due to the hardware’s inability to provide a valid measurement
  • NRes (Not at this resolution): no IEEE 754 equivalent, used to report that the value cannot be represented with the available range and resolution, possibly resulting from an overflow or underflow situation
  • RFU: no IEEE 754 equivalent, Reserved for Future Use
Special value SFLOAT-Type FLOAT-Type
Positive infinity 0x07FE 0x007FFFFE
Nan 0x07FF 0x007FFFFF
NRes 0x0800 0x00800000
RFU 0x0801 0x00800001
Negative infinity 0x0802 0x00800002

Although IEEE 754 floating points and IEEE 11073-20601 decimal numbers have very different topologies, from an implementation point of view we mostly have SFLOAT-Type $\subset$ IEEE 754 single-precision $\subset$ FLOAT-Type $\subset$ IEEE 754 double-precision.

SFLOAT-Type (medfloat16)

The SFLOAT-Type is defined as a 16-bit value with 12-bit mantissa and 4-bit exponent: $x \in$ [-2048e+7, -1e-8] $\cup$ {0} $\cup$ [+1e-8, 2047e+7].

Maximum positive value Minimum negative value Minval
IEEE 11073-20601 SFLOAT (medfloat16) 2047e+7 -2048e+7 1e-8
IEEE 754 single-precision (32-bit) 3.40e+38 -3.40e+38 1.18e−38

Encoding: <e3..e0m11..m8> <m7......m0>.

At most $\lceil 11 \times log{10}(2)\rceil = 4$ digits of precision, available when the 4 most significant digits of the decimal number are in the range [-2048, +2047].

FLOAT-Type (medfloat32)

The FLOAT-Type is defined as a 32-bit value with 24-bit mantissa and 8-bit exponent, with a range of values [-8388608e+127, -1e-128] $\cup$ {0} $\cup$ [+1e-128, 8388607e+127].

Format Maximum positive value Minimum negative value Minval
IEEE 11073-20601 FLOAT (medfloat32) 8388607e+127 -8388608e+127 1e-128
IEEE 754 double-precision (64-bit) 1.80e+308 -1.80e+308 2.23e-308

Encoding: <e7..e0> <m23......m16><m15......m8><m7......m0>.

At most $\lceil 23 \times log{10}(2)\rceil = 7$ digits of precision, available when the 7 most significant digits of the decimal number are in the range [-8388608, +8388607].

API

IEEE 11073-20601 data type Support
IEEE 11073-20601 SFLOAT (medfloat16) #include "cmedfloat/medfloat16.h"
IEEE 11073-20601 FLOAT (medfloat32) #include "cmedfloat/medfloat32.h"

IEEE 11073-20601 values are naturally represented by mantissa and exponent integers in two's-complement form.

Value Exponent Mantissa
SFLOAT-Type struct cmefloat_medfloat16 int8_t:4 int16_t:12
FLOAT-Type struct cmefloat_medfloat32 int8_t int32_t:24

The API is essentially by-value:

  • SFLOAT-Type and FLOAT-Type values are values
  • the size of the C structures representing mantissa and exponent, at most 32-bit, should not exceed the pointer size of micro-controllers commonly used in IoT sensors

Unit tests in the tests directory are a good source of API usage examples.

Convert IEEE 754 floating point numbers

Most often the sensor API/driver won't support the rather niche IEEE 11073-20601 formats: floating-point values will likely be represented using the more common IEEE 754 or IEEE 854 standards.

libcmedfloat convert IEEE 754 floating point numbers to SFLOAT-Type and FLOAT-Type values, with fixed exponent or floating point.

Floating-point conversion Fixed-point conversion
SFLOAT-Type cmedfloat_medfloat16_from_float() cmedfloat_medfloat16_from_float_fixed()
FLOAT-Type cmedfloat_medfloat32_from_double() cmedfloat_medfloat32_from_double_fixed()

The initial conversion is always floating: the exponent is adjusted to obtain the mantissa with the maximum number of significant digits (the mantissa with the greatest allowed absolute value).

For example, the decimal number 1 is converted to the SFLOAT-Type representation 1000e-3: 1000 is the greatest possible value we can choose that fits in the 12-bit mantissa, and -3 is the corresponding exponent.

struct cmedfloat_medfloat16 mfloat16 = cmedfloat_medfloat16_from_float(1);
assert(mfloat16.e == -3);
assert(mfloat16.m == 1000)

struct cmedfloat_medfloat32 mfloat32 = cmedfloat_medfloat32_from_double(1);
assert(mfloat16.e == -6);
assert(mfloat16.m == 1000000)

Additionally (using SFLOAT-Type in examples):

  • when not a power of 10, the mantissa is rounded: 0.0020445 equals to 2044.5e-6, rounded to 2045e-6
  • when the final representation is a special value, skip the zero-exponent, giving up one digit of precision: 2046 would be represented as 2046e0, but this is the value reserved for positive infinity, hence 2046 is actually converted to 205e1

Fixed point conversions indicate the precision of the sensor. For example, if 1 represents one degree Celsius, measured by a sensor with a precision of one hundredth, its SFLOAT-Type representation should be 100e-2 (1.00) instead of 1000e-3 (1.000).

For this, use the conversion API variants that permit to set the exponent:

struct cmedfloat_medfloat16 mfloat16 = cmedfloat_medfloat16_from_float_fixed(1, -2);
assert(mfloat16.e == -2);
assert(mfloat16.m == 100)

struct cmedfloat_medfloat32 mfloat32 = cmedfloat_medfloat32_from_double_fixed(1, -6);
assert(mfloat32.e == -6);
assert(mfloat32.m == 1000000)
mfloat32 = cmedfloat_medfloat32_from_double_fixed(1, -7);
assert(cmedfloat_medfloat32_isnres(mfloat32));

IEEE 754 special values are converted to their IEEE 11073-20601 equivalent:

struct cmedfloat_medfloat16 mfloat16 = cmedfloat_medfloat16_from_float(nanf("NaN"));
assert(cmedfloat_medfloat16_isnan(mfloat16));
assert(isnan(cmedfloat_medfloat16_to_float(mfloat16)));

struct cmedfloat_medfloat32 mfloat32 = cmedfloat_medfloat32_from_double(INFINITY);
assert(cmedfloat_medfloat32_isinf(mfloat32));
assert(cmedfloat_medfloat32_to_double(mfloat32) == INFINITY);

Values non representable are converted to IEEE 11073-20601 NRes: 23.5 with a precision of one hundredth would be written 2350e-2, that cannot be represented in SFLOAT-Type (2350 is greater than the allowed mantissa).

struct cmedfloat_medfloat16 mfloat16 = cmedfloat_medfloat16_from_float(23.5);
assert(mfloat16.e == -1);
assert(mfloat16.m == 235)

mfloat16 = cmedfloat_medfloat16_from_float_fixed(23.5, -2);
assert(cmedfloat_medfloat16_isnres(mfloat16));

Transcode IEEE 11073-20601 binary representations

libcmedfloat transcode SFLOAT-Type and FLOAT-Type values to and from byte arrays suitable for data exchange.

Encode to IEEE 11073-20601 data Decode to IEEE 11073-20601 value
SFLOAT-Type cmedfloat_medfloat16_encode() cmedfloat_medfloat16_decode()
FLOAT-Type cmedfloat_medfloat32_encode() cmedfloat_medfloat32_decode()

Encode SFLOAT and FLOAT values to IEEE 11073-20601 data:

uint8_t data[MEDFLOAT16_LEN];
struct cmedfloat_medfloat16 mfloat16;

mfloat16 = cmedfloat_medfloat16_from_float(2047.e7f);
cmedfloat_medfloat16_encode(mfloat16, data);
assert(data[0] == 0x77);
assert(data[1] == 0xFF);

uint8_t data[MEDFLOAT32_LEN];
cmedfloat_medfloat32_encode(MEDFLOAT32_NRES, data);
assert(data[0] == 0x00);
assert(data[1] == 0x80);
assert(data[2] == 0x00);
assert(data[3] == 0x00);

Decode IEEE 11073-20601 data to SFLOAT and FLOAT values:

#define UINT8_ARRAY(...) ((uint8_t[]){__VA_ARGS__})

struct cmedfloat_medfloat32 mfloat32;

mfloat32 = cmedfloat_medfloat32_decode(UINT8_ARRAY(0x00, 0xFF, 0xFB, 0x2E));
assert(mfloat32.e == 0);
assert(mfloat32.m == -1234);

Produced and expected byte arrays do not depend on endianness:

Size (bytes) Data
SFLOAT-Type 2 <e3..e0m11..m8> <m7......m0>
FLOAT-Type 4 <e7..e0> <m23......m16> <m15......m8> <m7......m0>

API clients may have to swap the byte order according to the communication protocol.

Compare IEEE 11073-20601 values

Directly comparable IEEE 11073-20601 representations include decimal numbers and signed infinity:

  • cmedfloat_medfloatNN_cmp(x, y) < 0 if x is less than y
  • cmedfloat_medfloatNN_cmp(x, y) > 0 if x is greater than y
  • cmedfloat_medfloatNN_cmp(x, y) == 0 if x equals to y
struct cmedfloat_medfloat16 mfloat16_pos1 = {.m=1};
struct cmedfloat_medfloat16 mfloat16_neg1 = {.m=-1};

assert(cmedfloat_medfloat16_cmp(mfloat16_pos1, mfloat16_neg1) > 0);
assert(cmedfloat_medfloat16_cmp(MEDFLOAT16_NEG_INF, mfloat16_neg1) < 0);

assert(cmedfloat_medfloat16_cmp(MEDFLOAT16_NAN, MEDFLOAT16_NAN) == 0);

Additionally, the sensor/driver API may produce NaN/NRes representations to indicate missing or non representable measurements. These can be compared only for equality, and cannot be less than or greater than any IEEE 11073-20601 value:

  • cmedfloat_medfloatNN_isequal(x, y): if x and y have either the same decimal value or the same representation (e.g. they're both NaN)
  • cmedfloat_medfloatNN_isless(x, y): if x and y are comparable, and x is less than y
  • cmedfloat_medfloatNN_isless_eq(x, y): if x and y are comparable, and x is less than or equal to y
  • cmedfloat_medfloatNN_isgreater(x, y): if x and y are comparable, and x is greater than y
  • cmedfloat_medfloatNN_isgreater_eq(x, y): if x and y are comparable, and x is greater than or equal to y
struct cmedfloat_medfloat32 mfloat32_pos1 = {.m=1};
struct cmedfloat_medfloat32 mfloat32_neg1 = {.m=-1};

assert(!cmedfloat_medfloat32_isless_eq(MEDFLOAT32_NAN, MEDFLOAT32_NAN));
assert(!cmedfloat_medfloat32_isgreater(mfloat32_pos1, MEDFLOAT32_NAN));

assert(cmedfloat_medfloat32_isgreater(mfloat32_pos1, mfloat32_neg1));
assert(cmedfloat_medfloat32_isless_eq(mfloat32_neg1, mfloat32_pos1));

assert(cmedfloat_medfloat32_isgreater(mfloat32_neg1, MEDFLOAT32_NEG_INF));

Comparisons involving RFU representations are undefined behavior.

Build, install, test

A few build options are available (all disabled by default):

  • BUILD_SHARED_LIBS: enable to build libcmedfloat as a shared library; default is to statically link everything
  • CMEDFLOAT_BUILD_CLI: enable to build command line tools (convert, encode and decode)
  • CMEDFLOAT_BUILD_DOC: enable to build API documentation (requires Doxygen)
  • CMEDFLOAT_BUILD_TESTS: enable to build unit tests (requires Unity submodule)

For example, to enable the CLI tools (from the cmedfloat root directory):

cmake -B build -DCMEDFLOAT_BUILD_CLI=YES
cmake --build build

Library and command line tools can be installed, e.g. cmake --install build --prefix ~/.local will install:

  • the header files to ~/.local/include/cmedfloat
  • the library file to ~/.local/lib
  • the command line tools to ~/.local/bin

MS Windows users

When the MSVC toolchain is detected, the option /W4 is used as an equivalent to -Werror -Wall (GCC/LLVM).

If this option is not recognized, try to comment out all calls to the cmedfloat_compile_options() function in CMakeLists.txt, cli/CMakeLists.txt and tests/CMakeLists.txt.

Command line interface

Convert and encode Decode
SFLOAT-Type medfloat16-encode medfloat16-decode
FLOAT-Type medfloat32-encode medfloat32-decode

Convert and encode floating-point literals

Convert a floating-point literal (e.g. 1e-3, 0.001, INFINITY, NAN) to its IEEE 11073-20601 representation, then encode to 16-bit or 32-bit packets.

$ medfloat16-encode -h
usage: medfloat16-encode [-p <n>] [-v] <x>

-v      verbose output
-p <n>  requested precision for fixed-point conversions
<x>     floating-point literal, e.g. 1e-3, 0.001, INFINITY, NAN

Special values:

$ medfloat16-encode nan
nan:     0x07ff

$ medfloat32-encode -inf
-inf    0x00800002

Normalized conversions (greatest mantissa):

$ medfloat16-encode 1
1000e-3   0xd3e8

$ medfloat32-encode 1
1000000e-6   0xfa0f4240

$ medfloat16-encode 20470000000
2047e7   0x77ff

$ medfloat32-encode 1e-128
1e-128   0x80000001

Fixed point conversions:

$ medfloat16-encode 8651234 -p 6
9e6   0x6009

medfloat32-encode 8651234 -p 3
8651e3   0x030021cb

$ medfloat16-encode 2.045 -p -2
205e-2   0xe0cd

$ medfloat32-encode 2.00865 -p -4
20087e-4   0xfc004e77

$ medfloat16-encode 1 -p -4
NRes    0x0800

Decode IEEE 11073-20601 data

Decode 16-bit or 32-bit packets to IEEE 11073-20601 representations.

Special values:

$ medfloat16-decode 0x07ff
0x07ff    nan

$ medfloat32-decode 0x00800002
0x00800002    -inf

Decimal numbers:

$ medfloat16-decode 0x1002
0x1002    2e1

$ medfloat32-decode 0xFE0000C8
0xfe0000c8    200e-2

Generate API documentation

Generate API documentation into cmedfloat/apidoc:

cmake -B build -DCMEDFLOAT_BUILD_DOC=YES
cmake --build build -t apidoc

Unit tests

Pull the Unity library as submodule:

git submodule init
git submodule update

Build and run unit tests:

cmake -B build -DCMEDFLOAT_BUILD_TESTS=YES
cmake --build build -t test

About

Transcode ISO/IEEE 11073-20601-2008 floating point numbers (medfloat16, medfloat32).

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published