Losing float precision

Hi!

In the code that I'm working:

• There is a constant, called K.

• A distance to walk, called dist.

• An amount of steps, called steps.

The code finds how many steps is needed to walk the distance, using the constant.

float k = 1256.29; //Steps required to walk a distance unit
float dist = 100;
unsigned long steps = dist*k;
float cPos = 0; //Target position
float realPos = 0; //Real position


void setup() {
Serial.begin(115200);

cPos = steps * (1/k);

for(unsigned long i = 0; i < steps; i++)
{
  realPos += (1/k);

  }
  Serial.println(cPos);
  Serial.println(realPos);
}

When the code runs, the calculated distance (cPos) hits 100.0, but for some reason, the realPos hits 99.93. I know it is very close, but I need it a little bit more precise.

Thanks!!!!

You have defined k in terms of fractional steps, but the variable steps is declared as an unsigned long. That is, the variable steps will always hold an integer number of steps, no fractions.

Besides, maybe I do not really understand the problem. In the sentence "I know it is very close, but I need it a little bit more precise," the word "it" occurs twice but I don't really understand what is being referenced.

I'm sorry. I'm From Brazil, and I still learning English! Thank you for all!

Don't use floats. I kept ralPos a float as it's a bit easier. Just to give you the idea:

float realPos = 0; //Real position

unsigned long stepSize = 796; // micro meters
unsigned long stepsPerMeter = 1257;

void setup()
{
  Serial.begin(9600);

  for (unsigned long i = 0; i < stepsPerMeter; i++)
  {
    realPos += stepSize;
  }

  Serial.print(realPos / 10000, 6); Serial.println(" centimeters");
  Serial.print(realPos / 1000000, 6); Serial.println(" meters");

}

void loop()
{
}

Result:

100.057197 centimeters
1.000572 meters

You're getting drift. 1/k doesn't convert perfectly into a binary real number, so it is approximated as well as it can be in 4 bytes of data. But when you add it repeatedly, many, many, many times, that very small difference gets multiplied. By the time you have added it 125629 times, it's that far off.

This is not an Arduino specific problem. Running that calculation in a C program on your PC will give you the same results. The difference is that on your PC, you can simply declare your float variables as double variables, and your precision gets much better. On the Arduino, doubles are just turned into floats, and you're stuck with the precision you get.

This program looks like it was designed to exhibit this problem. To eliminate the drift, you are supposed to do the actual formula calculation inside the for loop. Like so:

  for(unsigned long i = 0; i < steps; i++)
  {
    realPos = i / k;
    // Do useful stuff here with realPos
  }

In most cases, you actually use realPos in the loop, and this will give you accurate data in the loop. And, at the end of the loop, when i is equal to steps, the result will be the same as cPos. But then it's the exact same calculation, so it should be.

With an Arduino DUE, this code:

float k = 1256.29; //Steps required to walk a distance unit
float dist = 100;
uint32_t steps = dist*k;
float cPos = 0; //Target position
double realPos = 0; //Real position

void setup() {
Serial.begin(250000);

cPos = steps * (1/k);

for(unsigned long i = 0; i < steps; i++)
{
  realPos += (1/k);

  }
  Serial.println(cPos);
  Serial.println(realPos);
}
void loop() {
  
}

Outputs:

100.00
100.00

This

double realPos = 0; //Real position


for(unsigned long i = 0; i < steps; i++)
{
  realPos += (1/k);

  }

is a terrible way to add up lots of small numbers.

double realPos = 0; //Real position


for(unsigned long i = 0; i < steps; i++)
{
  realPos += (1/k);

  }

Hello? This is a really inefficient way to do a multiplication!

All the for loop does is this:

  realpos = steps/k;

I wonder if the compiler was smart enough to figure that out and optimize it anyway.

Pete

It's the same as OP's code; only difference is the use of a double instead of a float. And hence better results on a Due.

Only useful if OP uses a Due or similar :wink: