Sum of float variables differing by 10^8

Hello,
I have always issues with calculation because I'm not frequently and professional user on Arduino...
In my last application I've to sum in a loop a very small value (between 0.000043333 to 0,0043333) to an increasing values like 110.4 (I'm interested only on first decimal in this case)

I'm going to implement something similari to the following code but I'm sure their Is something more elegant, any suggestion?

unsigned float a = 0;
unsigned float b = 110,3;

void setup() {
}

void loop() {
  a = a + 0,000034
  if (a >= 1) {
    b = (int)a + b;
    a = a - (int)a;
  }
}

Hello solki

Take a view to gain the knowledge:

See Notes and Warnings

Have a nice day and enjoy coding in C++.

You should scale your problem to integer numbers and use uint64_t or maybe uint32_t.
Floats on arduino do not have sufficient accuracy.

1 Like

Hello solki

You may add either Motorola 68881 or Motorola 68882 floating-point units to your hardware design.

1 Like

May I ask what the "small" values represent?
Often the problem is due to misrepresentation or inappropriate measurement.
If you measure the width of a hair in metres the value will be small. So choose suitable units.

1 Like

Thanks for trying to use code tags; I've fixed them for you. [code] and [/code] should have been on their own line.


Floating point numbers use the decimal dot, not the decimal comma.

1 Like

What are you trying to do? Where are these numbers coming from? And like @sterretje said, you need to use the decimal dot, not the decimal comma, so 110.3 and not 110,3.

What I would do? I would try something like:

int16_t whole = 110;
int32_t billionths = 300000000L;
float myFloat = 110.3;

void setup() {
}

void loop() {
  billionths = billionths + 34000L;
  if (billionths >= 1000000000L) {
    billionths = billionths - 1000000000L;
    whole = whole + 1;
  }
  myFloat = whole + (billionths / 1000000000.0);
}

But, I ask again: where are these numbers coming from?
Google "floating point disaster" for possibly relevant information.

110.400004333 would require more digits of precision than the 32bit floating point numbers used by AVR arduinos can represent.

The β€œ,” vs β€œ.” issue is β€œcute.” C actually has a β€œ,” operator, so this does not generate an error, but it does not do what you want!

1 Like

That is why I suggested a workaround, with one variable for whole numbers (110 in your example) and one variable for billionths (400004333 in your example).

@odometer yes, you did, but a solution to a problem is only useful if the OP understands the true nature of his problem, which @westfw kindly explained. Between the two of you, the OP has a workable answer AND the knowledge of why it is necessary.

Well, that's for sure the solution in case of mass production but I'm only building one piece.

this is a drops counter that should shows on a display the number of liters (after several days).
I measure a variable frequency while the drop dimension is the same.

Yes, that's the blocking point that brought me to rise my hand.

@odometer and @camsysca , thanks for suggestion, I think I'll go with the solution of two variables, one for the whole quantity and one for the billionths... the code of odometer is much more elegant than mine :sweat_smile:

@odometer ...just tested your draft sketch ... it's incredibly precise :smiling_face_with_three_hearts:
thanks a lot for the tip!

Wouldn't it simplify the code a lot if you worked in nanoliters in the code? You could always convert at the point of display.

What does an overflowed decimal look like? Zero? A negative-twos-compliment?

The whole point of this

is so that that never gets a chance to happen.

Ah.

modulus billionths

Good suggestion. An unsigned 32 bit integer can total nanoliter drops up to 4.3 L, or more precisely, 4,294,967,295 nanoliters, without round off error.

2 Likes

And if that is not enough, then even an Arduino Uno can do calculations with uint64_t.
Suppose the drops are in picoliter, then the maximum is 18,446,744,073,709,551,615 picoliter, which is 18 million liters.

All the divide, multiply, add, subtract operations work on a uint64_t, but it can not be printed, because Serial.println() does not support it.

Here is an example with uint64_t:

// Increment 64 bits counter as fast as possible.
// Press the button to show the value.
// Forum: https://forum.arduino.cc/t/sum-of-float-variables-differing-by-10-8/1253425/18
// This Wokwi project: https://wokwi.com/projects/396560196465457153

uint64_t counter;

unsigned long previousMillis;
const unsigned long interval = 1000;
volatile bool flag;

void setup() 
{
  Serial.begin(115200);
  Serial.println("Press button to show counter value");
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2),showNow,FALLING);
}

void loop() 
{
  counter++;
  
  if(flag)
  {
    byte buffer[21];
    buffer[20] = '\0';
    uint64_t copy = counter;
    for(int i=19; i>=0; i--)
    {
      int digit = copy % 10;
      copy /= 10;
      buffer[i] = (byte) (digit + '0');
    }
    Serial.println((char *)buffer);
    flag = false;
  }
}

void showNow()
{
  flag = true;
}

Try it in Wokwi:

If my calculation is correct, then it takes 2Β½ million years to reach to the end with a Arduino Uno.

Modulus operator produces a value that fits well in byte type variable.
If int digit is declared as byte digit, then there is no need for this cast: (byte)(digit + '0').

1 Like

A horrid ++counter *=3; will speed it up.

1 Like