MBF (Microsoft Binary Format) to 32-bit float

Has anyone ever come across Microsoft Binary Format (MBF) and knows how to convert it to float that can be used? This is for a project with an Arduino Nano Every using the ModbusRTUMaster library. The slave device has a 32bit float in MBF stored in 2 consecutive 16-bit registers, it's a temperature reading from a sensor. I can read the registers, but I don't know how to convert MBF to IEEE-754 32bit float.

Any help, hint is greatly appreciated.

Some useful ideas in this thread.

numbers in 32 bits (4 bytes), with a 23-bit mantissa, 1-bit sign, and an 8-bit exponent

The format looks exactly the same as arduino 32bit float. Is there any need to convert anything at all?
Maybe MBF can be used directly as a float ?

Thank you both for the fast replies. It's certainly not IEEE-754. Here are some details from the manufacturer about the stored value:

float = 32-bit single precision floating point value stored in two subsequent 16-bit registers. Register n, [EEEE EEEE SMMM MMMM] – exponent (offset 129), sign and mantisa Register n+1, [MMMM MMMM MMMM MMMM] – mantisa examples:
+100.25 is coded as 8748h, 8000h
-12.5 is coded as 84C8h, 0000h
+3.1415 is coded as 8249h, 0E56h

The exponent would need to be converted to the proper signed number, after which the sign bit and exponent would need to be moved. Looks like the maitissa could stay where it is.

0ffset is 128, according to the Wikipedia article. As above, sign bits need to be corrected.

The manufacturer states offset as 129; unless the documentation is wrong.

I suspect the manufacturer's documentation, but of course you will need to check everything with known inputs and outputs, in both formats.

Again, the Wikipedia article gives explicit examples for some known decimal inputs, for example, this sure looks like an offset of 128 to me:

"1":
32-bit format: 81h, 00h, 00h, 00h

I will check but the documentation is from Feb. 2024.

Then the mistake was recently made, and not yet corrected.

Check the citation on Wikipedia; it could very well be 129.

Let us know what you find out!

MBF:

and Arduino

arduino_float

It looks like a sign bit should be moved to last byte and exponent shift one bit

Looks like the bias is 129 for MBF, and 127 for IEEE-754, a difference of 2.
Need to test for the special case of an exponent of 00h representing 0.0, and be careful of going out of range.

< edit >
Does appear to work by shifting the sign 8 bits to the left, subtracting 2 from the exponent and placing it in the proper location, with the mantissa unchanged. Have not looked at how to code the special cases of an exponent of 00h for zero, and FFh that IEEE-754 uses for NaN and infinity.

Also need to be careful of byte order, all the Arduino boards I am familiar with are little-endian.

I appreciate the effort.

Could you explain this?
I also noted this subtraction, but I don't understand the cause...

The MBF and IEEE-754 floating point formats both store the exponent as an unsigned 8-bit number, while the actual exponent is a signed number. To convert from the signed to unsigned number, MBF adds 129, while IEEE-754 adds 127. Converting from MBF to IEEE-754 requires that you subtract 129, to get the signed exponent value, then add 127 for the IEEE-754 representation, which is a net effect of subtracting 2.

There is going to be a slight difference in the range of numbers that each format can represent, since an MBF exponent of 1 cannot be converted to IEEE-754, and an MBF exponent of FFh would be FDh in IEEE-754, making it impossible to have a conversion that produced FEh. Note that 00h has special meanings in both formats, and FFh has special uses in IEEE-754.

1 Like

Expanding on my idea in that other thread

struct MicrosoftBinaryFormat {
  unsigned mantissC23:8;
  unsigned mantissB23:8;
  unsigned mantissA23:7;
  int sign:1;
  unsigned exponent8:8;

  MicrosoftBinaryFormat(uint16_t r0 = 0, uint16_t r1 = 0) {
    mantissC23 = r1;
    r1 >>= 8;
    mantissB23 = r1;
    mantissA23 = r0;
    r0 >>= 7;
    sign = r0;
    r0 >>= 1;
    exponent8 = r0;
  }
  bool isZero() {
    return !sign && !exponent8 &&
        !mantissA23 && !mantissB23 && !mantissC23;
  }
  int16_t exponent() const {
    return exponent8 - 0x81;
  }
  uint32_t mantissa() const {
    uint32_t ret = mantissA23;
    ret <<= 8;
    ret |= mantissB23;
    ret <<= 8;
    ret |= mantissC23;
    return ret;
  }
}__attribute__((packed));

struct Float32LE {
  unsigned mantissC23:8;
  unsigned mantissB23:8;
  unsigned mantissA23:7;
  unsigned exponentB8:1;
  unsigned exponentA8:7;
  int sign:1;

  void setSpecial(int nanZeroInf) {
    if (nanZeroInf) {
      exponentA8 = -1;
      exponentB8 = -1;
    } else {
      sign = 0;
      exponentA8 = 0;
      exponentB8 = 0;
    }
    if (nanZeroInf < 0) {
      sign = -1;
      mantissA23 = -1;
      mantissB23 = -1;
      mantissC23 = -1;
    } else {
      mantissA23 = 0;
      mantissB23 = 0;
      mantissC23 = 0;
    }
  }
  void setExponent(int16_t x) {
    x += 0x7F;
    if (x >= 1 << 8) {
      setSpecial(1);
    } else if (x < 0) {
      setSpecial(0);
    } else {
      exponentB8 = x;
      x >>= 1;
      exponentA8 = x;
    }
  }
  void setMantissa(uint32_t m) {
    mantissC23 = m;
    m >>= 8;
    mantissB23 = m;
    m >>= 8;
    mantissA23 = m;
  }
  void set(int sign, int16_t exponent, uint32_t mantissa) {
    setMantissa(mantissa);
    setExponent(exponent);  // may reset mantissa
    this->sign = sign;
  }
  float toFloat() const {
    return *reinterpret_cast<const float*>(this);
  }
}__attribute__((packed));

float mbf(uint16_t r0, uint16_t r1 = 0) {
  auto m = MicrosoftBinaryFormat(r0, r1);
  Float32LE f32;
  if (m.isZero()) {
    f32.setSpecial(0);
  } else {
    f32.set(m.sign, m.exponent(), m.mantissa());
  }
  return f32.toFloat();
}

void setup() {
  Serial.begin(115200);
  Serial.println(mbf(0x8748, 0x8000), 4);
  Serial.println(mbf(0x84C8), 4);
  Serial.println(mbf(0x8249, 0x0E56), 4);
  Serial.println(mbf(0), 4);
}

void loop() {}

You'll want to confirm that zero is encoded in MBF as all-zero. As for the bias of the exponent, if you find the temperature readings are off by exactly some power of two, you can tweak the magic 0x81 in the MicrosoftBinaryFormat::exponent function.

That is fantastic! It is certainly beyond my programming skills and very much appreciated!

It can as a subnormal number. You may get round-off errors, though.

Then again, not all IEEE-754 subnormal numbers can be converted back to MBF, since MBF doesn't support subnormal numbers. Most subnormal numbers will simply underflow to zero when converted to MBF.