Union between a float and byte array[4] Not working as expected

Mi arduino Zero has an MCP79410 Realt Time Clock attached with EPROM and SRAM (battery powered).

I am suing both the EPROM and SRAM, using the library MCP79412 (GitHub - JChristensen/MCP79412RTC: Arduino library for the Microchip MCP79411/12 Real-Time Clock/Calendar).

In my sketch I want to save a float in the SRAM (battery backed), therefore I am sending this float through I2C bus, which forces me to split the float in its 4 bytes. I was dealing with pointer (*) and address (&) operators before discoivering that you can use UNIONS to see a float value as a set of 4 bytes.

My program is pretty big (100k / 6 files), but this is the relevant part of the code:

global variables definition:

// 3.11. TOTALIZERS kWh / kVarh -> (SRAM Battery powered)
// float (4 bytes = 32bits) -> -3.4028235E+38 / 3.4028235E+38 

union kWh1_Float_Byte {
   float floatkWh;
   byte  bytekWh[4];
} TOTAL1_kWh;

union kVarh1_Float_Byte {
   float floatkVarh;
   byte  bytekVarh[4];
} TOTAL1_kVarh;

Then in the SETUP section I am doing the following:

 // (10.) TOTALIZER INICIALIZATION FROM SRAM
  //       (0x20) + 4 Bytes  --> TOTAL_kWh
  //       (0x24) + 4 Bytes  --> TOTAL_kVarh
  //ramRead((byte)0x20, (byte*)&TOTAL1_kWh.bytekWh[0], 8);

  TOTAL1_kWh.bytekWh[0] = ramRead(0x20);
  TOTAL1_kWh.bytekWh[1] = ramRead(0x21);
  TOTAL1_kWh.bytekWh[2] = ramRead(0x22);
  TOTAL1_kWh.bytekWh[3] = ramRead(0x23);

  if (_DEBUG_) {
  uUSB.print(millis());
  uUSB.print("\tFrom SRAM: TOTAL_kWh: ");
  uUSB.println(TOTAL1_kWh.floatkWh);
  }

  // For Testing purposes: saving in SRAM
  //TOTAL1_kWh.floatkWh = 1234567891;
  TOTAL1_kWh.floatkWh = 12345678.912;
  //ramWrite((byte)0x20, (byte*)&TOTAL1_kWh.bytekWh[0], 8);
  ramWrite(0x20, TOTAL1_kWh.bytekWh[0]);
  ramWrite(0x21, TOTAL1_kWh.bytekWh[1]);
  ramWrite(0x22, TOTAL1_kWh.bytekWh[2]);
  ramWrite(0x23, TOTAL1_kWh.bytekWh[3]);

As you can see in the code I am trying to save a float value of "12345678.912" in SRAM, and later after repowering my Arduino, I am recovering the value from SRAM, but I am getting a value "TOTAL_kWh: 12345679.00"

There is some rounding issue or overflow effect that should not occur.

I have however success when I save a smaller floats like "123456.7", in this case I can after powering off get the correct number.

Does somebody has experience with this kind of issue?

You are using more significant digits than a float supports.

void setup() {
  Serial.begin(115200);
  float test = 12345678.912;
  Serial.print(F("Value is "));
  Serial.println(test);
}
void loop() {}
Value is 12345679.00

A float can provide, at most, about 7 significant digits of precision, not the 11 you're trying to shoe-horn in there. For AVR-based Arduinos, double is identical to float, so that does not help you either.

Regards,
Ray L.

since you have an arduino Zero --> if you go to a double, then on the M0 you'll have 8 bytes and you can represent numbers with more precision.

example:

union kWh1_Float_Byte {
  float floatkWh;
  byte  bytekWh[4];
} fTOTAL1_kWh;

union kWh1_Double_Byte {
  double doublekWh;
  byte  bytekWh[8];
} dTOTAL1_kWh;

void setup()
{
  Serial.begin(115200);
  fTOTAL1_kWh.floatkWh = 12345678.912;
  dTOTAL1_kWh.doublekWh = 12345678.912;

  Serial.print("size float=");
  Serial.print(sizeof(fTOTAL1_kWh));
  Serial.print("\t0x");
  Serial.print(fTOTAL1_kWh.bytekWh[0], HEX);
  Serial.print(fTOTAL1_kWh.bytekWh[1], HEX);
  Serial.print(fTOTAL1_kWh.bytekWh[2], HEX);
  Serial.print(fTOTAL1_kWh.bytekWh[3], HEX);
  Serial.print("\t\t or for big endian 0x");
  Serial.print(fTOTAL1_kWh.bytekWh[3], HEX);
  Serial.print(fTOTAL1_kWh.bytekWh[2], HEX);
  Serial.print(fTOTAL1_kWh.bytekWh[1], HEX);
  Serial.println(fTOTAL1_kWh.bytekWh[0], HEX);

  Serial.print("size double=");
  Serial.print(sizeof(dTOTAL1_kWh));
  Serial.print("\t0x");
  Serial.print(dTOTAL1_kWh.bytekWh[0], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[1], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[2], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[3], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[4], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[5], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[6], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[7], HEX);
  Serial.print("\t or for big endian 0x");
  Serial.print(dTOTAL1_kWh.bytekWh[7], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[6], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[5], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[4], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[3], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[2], HEX);
  Serial.print(dTOTAL1_kWh.bytekWh[1], HEX);
  Serial.println(dTOTAL1_kWh.bytekWh[0], HEX);
}


void loop() {}

that will give you

size float=4 0x4F613C4B or for big endian 0x4B3C614F
size double=8 0xA01A2FDD298C6741 or for big endian 0x41678C29DD2F1AA0

and if you want to verify that the output is correct, you can go to this web site and enter the big endian version of what's printed into the float or double fields and convert. you'll see that the first one is rounded while the second one is not

I think

union kWh1_Double_Byte {
  double floatkWh;
  byte  bytekWh[4];
} dTOTAL1_kWh;

should be

union kWh1_Double_Byte {
  double floatkWh;
  byte  bytekWh[8];
} dTOTAL1_kWh;

Sorry was modifying the code indeed as you were typing...

thanks for so many replies and help!. changing from "float" to "double" in my SAMD21 architecture (Arduino zero), and changing size from 4 to 8 bytes, did help, and I can store now much bigger numbers.

However I am now very confused, according the Arduino float reference: float - Arduino Reference

Floats are 4 bytes/32 bits, and its range is between 3.4028235E+38 (highest) and -3.4028235E+38 (lowest)

These are pretty big numbers (10E38), with many significant digits, how is it possible that I can not reach more than 10 digits, before the decimal values?

kinematik:
Floats are 4 bytes/32 bits, and its range is between 3.4028235E+38 (highest) and -3.4028235E+38 (lowest)

These are pretty big numbers (10E38), with many significant digits,

No. You only get around 7 significant digits, regardless of the magnitude.

You can store a number that big, but only with 6 or 7 significant digits of precision. SO you could store 7.54765 * 10^36 no problem. But you only get accuracy to the nearest 10^31. I guess another way to say that is that you can store numbers as small as 10^-38 or as big as 10^38 but not every number in between can be represented.

This is why floating point numbers are not the best choice when accuracy matters. The only reason for floating point is when you have numbers that span a very large range. When accuracy matters, stick to fixed point math.

Delta_G:
This is why floating point numbers are not the best choice when accuracy matters. The only reason for floating point is when you have numbers that span a very large range.

My personal rule is that floats are only for statistical averages and artifacts, things that can only be computed approximately (eg: cosines), or measurements of physical quantities where there is an implied error tolerance. One must take the view that a floating point quantity is never accurate - it is always to be treated as a fuzzy number. Any time when a thing needs to be accurate to the digit - time, currency, anything that people pay money for - integer types must be used.

A fine distinction in pay there: a "kilogram" is a physical quantity when you are weighing it, but it becomes a financial one when people are paying by the kilo.

This rule of mine is borne by bitter experience working on a system where someone decided "actually, we'd like to bill in quarter-hour increments" and some fool decided to address this by making Session Time floating-point throughout the application. The correct solution would to have been to use an integer "number of quarter-hours" or "number of minutes" across the entire app.