3

I have the following problem that I couldn't find a solution. I have a double variable that has a value of 2.1 and I want to get the 4 bytes that represent it. For this I use the following code

var valueFloat = new Float32List (1);
valueFloat [0] = double.parse ("2.1");
var listOfBytes = valueFloat.buffer.asUint8List ();

And I get the list of bytes, but I see that in converting from double to Float32 the value to be 2.099999 ...

The problem is that I have to send the bytes via MODBUS TCP and the value that ends up being configured on the device is 2,099 instead of 2.1

I tried all the forms I found by googling but they all pass double to float. Is there a way to get the bytes directly from the double?

Thank you in advance for the help you can give me!

  • Also see: [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – jamesdlin Jul 01 '20 at 18:16

2 Answers2

4

What you are doing is correct.

The problem is that 2.1 does not exist as a 32-bit float value (nor as a 64-bit float, aka. double, for that matter).

The 2.1 double value actually has the value 2.100000000000000088817841970012523233890533447265625. That is the nearest double value to the mathematical value 2.1, so that's what you get when you write 2.1 as a double.

When that value is stored into a (32-bit) float, the nearest float value is picked. The two closest representable float values are:

  • 2.099999904632568359375
  • 2.1000001430511474609375

The former has the smaller distance to the double "2.1" (~0.000000095 vs ~0.000000143), so that's the float used to represent the double value.

Even if you could write floats directly, the smaller number is also closer to the mathematical number 2.1, so it's still the one you get.

What likely happens is that at the other end, the MODBUS server reads out your float, converts it back to a double, which it can without loss. That double is not the double for "2.1", and therefore it's treated as being smaller than 2.1. When they then do .toFixed(3) (or whatever they do to get three decimals), that's what you end up with.

If you had picked the larger of the floats, then it too would different from 2.1, but at least it has the advantage that it rounds to 2.100 with three fractional digits.

If you know that, maybe you can choose a float value which rounds as you want, instead of one that is closer to the actual double:

/// Returns a float value (as a double) that approximates the double.
double roundableFloat(double doubleValue, int fractionalDigits) {
  var target = doubleValue.toStringAsFixed(fractionalDigits);
  var buffer = Float32List(1);
  buffer[0] = doubleValue;
  var rounded = buffer[0];
  var result = rounded.toStringAsFixed(fractionalDigits);
  if (result != target) {
    // Assume same endianness for floats and integers.
    // Not always true on ARM, so more cleverness might be needed.
    buffer.buffer.asUint32List()[0] += rounded < doubleValue ? 1 : -1;
    rounded = buffer[0];
  }
  return rounded;
}

Somewhat cumbersome and inefficient, but if you can't fix the server, it might help.

lrn
  • 64,680
  • 7
  • 105
  • 121
0

Thanks for your detailed answer, it was very helpful to finish deciding to implement changes in the MODBUS server to round the data and avoid user compression problems. Regards!