1

I have encountered a strange behavior today. I can't understand why the sequence is altered after 26.1000. I get a number 0.0001 less than the desired value (26.2124 instead of 26.2125). I need precision and can't get it.

Here is the code:

double _pos = 0;

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

void loop() {
  _pos += (0.1125);
  Serial.println(_pos,4);
  while (_pos >26.5) delay(1);
}

and here is the result

0.1125
0.2250
0.3375
0.4500
0.5625
0.6750
0.7875
0.9000
1.0125
1.1250
1.2375
1.3500
1.4625
1.5750
1.6875
1.8000
1.9125
2.0250
2.1375
2.2500
2.3625
2.4750
2.5875
2.7000
2.8125
2.9250
3.0375
3.1500
3.2625
3.3750
3.4875
3.6000
3.7125
3.8250
3.9375
4.0500
4.1625
4.2750
4.3875
4.5000
4.6125
4.7250
4.8375
4.9500
5.0625
5.1750
5.2875
5.4000
5.5125
5.6250
5.7375
5.8500
5.9625
6.0750
6.1875
6.3000
6.4125
6.5250
6.6375
6.7500
6.8625
6.9750
7.0875
7.2000
7.3125
7.4250
7.5375
7.6500
7.7625
7.8750
7.9875
8.1000
8.2125
8.3250
8.4375
8.5500
8.6625
8.7750
8.8875
9.0000
9.1125
9.2250
9.3375
9.4500
9.5625
9.6750
9.7875
9.9000
10.0125
10.1250
10.2375
10.3500
10.4625
10.5750
10.6875
10.8000
10.9125
11.0250
11.1375
11.2500
11.3625
11.4750
11.5875
11.7000
11.8125
11.9250
12.0375
12.1500
12.2625
12.3750
12.4875
12.6000
12.7125
12.8250
12.9375
13.0500
13.1625
13.2750
13.3875
13.5000
13.6125
13.7250
13.8375
13.9500
14.0625
14.1750
14.2875
14.4000
14.5125
14.6250
14.7375
14.8500
14.9625
15.0750
15.1875
15.3000
15.4125
15.5250
15.6375
15.7500
15.8625
15.9750
16.0875
16.2000
16.3125
16.4250
16.5375
16.6500
16.7625
16.8750
16.9875
17.1000
17.2125
17.3250
17.4375
17.5500
17.6625
17.7750
17.8875
18.0000
18.1125
18.2250
18.3375
18.4500
18.5625
18.6750
18.7875
18.9000
19.0125
19.1250
19.2375
19.3500
19.4625
19.5750
19.6875
19.8000
19.9125
20.0250
20.1375
20.2500
20.3625
20.4750
20.5875
20.7000
20.8125
20.9250
21.0375
21.1500
21.2625
21.3750
21.4875
21.6000
21.7125
21.8250
21.9375
22.0500
22.1625
22.2750
22.3875
22.5000
22.6125
22.7250
22.8375
22.9500
23.0625
23.1750
23.2875
23.4000
23.5125
23.6250
23.7375
23.8500
23.9625
24.0750
24.1875
24.3000
24.4125
24.5250
24.6375
24.7500
24.8625
24.9750
25.0875
25.2000
25.3125
25.4250
25.5375
25.6500
25.7625
25.8750
25.9875
26.1000
26.2124
26.3249
26.4374
26.5499
  • If you want precision it's often better to work in fixed point, that way you can be sure of the significance of the least significant bits. – ratchet freak Nov 13 '17 at 9:31
1

I do not see any specific difference after 26.2124.

However, in your case you are using 6 digits (e.g. 26.5499). To use an unsigned long/uint32_t you can store easily 6 digits, but only using whole numbers.

However, by adding 1125 every time and dividing the result by 10,000.0 for printing you get exact results.

unsigned long _pos = 0;

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

void loop() {
  _pos += 1125;
  Serial.println((float) _pos / 10000.0,4);
  while (_pos > 265000) delay(1);
}
  • Thanks, I used this approach, the only problem is that numbers can be negative too (something I did not clarify on the original question). – FedeKrum Nov 15 '17 at 8:13
  • Than remove unsigned, so you can have negative numbers. – Michel Keijzers Nov 15 '17 at 9:48
3

The 8 bit Arduinos don't have precision, or anything even close to precision when it comes to working with decimal values.

There is no such thing as double on an 8-bit Arduino - it is only an alias for float. By using double you aren't gaining any more precision than using float.

Even double isn't perfectly precise. It's more precise than float (on systems that support double), but still far from precise.

People that care about specific levels of precision code using variables of that precision. That's called using fixed point mathematics, and it's what DSPs work in. It's fast, efficient, and precise up to the point that you define it to be.

Fixed point mathematics works by operating in multiples of a pre-defined fraction. That fraction is your precision, or granularity and is the smallest value that can be represented. However, working with large numbers and fine granularity results in very large variables to store all that data - which is why for most things people use float: because it compresses the data down into a very small space - and like a highly compressed MP3 it loses a lot of the data.

2

The number 0.1125 would written in binary as 0.0001110011001100..., with an infinite repetition of “1100”. As the float data type has only 24 bits of precision, it can obviously not represent such a number. Instead, when you write 0.1125 in code, you get the closest representable number, namely 0.11249999701976776123046875, which is 0.00011100110011001100110011 in binary. Note that AVR-based Arduinos do not really support double precision: you get a float32 whether you declare a float or a double.

Furthermore, when you keep adding this number to itself, quite often the sum is not a representable number. Thus, in addition to the initial rounding error in the representation of 0.1125, you get an additional rounding error with the addition. These errors accumulate and you end up with a value that is significantly off what you expect.

You can avoid accumulating errors by working with integers, and converting o float only when needed:

void loop()
{
    static long count;
    count++;
    Serial.println(count * 0.1125, 4);
    while (count > 235) delay(1);
}

Note that you still get two rounding errors: in the representation of 0.1125 and in the multiplication. But at least you are not accumulating a large number of them.

0

I can't understand why the sequence is altered after 26.2124.

because floats / doubles are not precise representation of their respective values.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.