Mathematical anomolies [Solved]

I've been trying to figure out if I'll have problems mixing float and unsigned long variables.
So I wrote this short sketch to test how big they can get before they cease to be numerically equal.

float varFloat=10000000;
unsigned long varUnsignedLong=10000000;

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

void loop() {
varFloat = varFloat + 1;
varUnsignedLong = varUnsignedLong + 1;
Serial.print("Float ");
Serial.println(varFloat,0);
Serial.print(" Long ");
Serial.println(varUnsignedLong);
delay(250);
}

I've changed the starting value for the two variables, and the amount they are incremented by, always doing the same to both float and unsigned long.

If I'm adding something like 10,000,000 then the two variables cease to be numerically equal at 2,150,000,000 which is obviously consisten with the 2,147,483,648 limit for 32bit numbers.

BUT ...

If I then change the initial values of the variables and/or the incrementing value to values that should work, the numbers output aren't how they should be.

Sometimes the float won't increment at all.
eg Initial values 2,100,000,000
increment = 1

Float 2100000000
 Long 2100000001
Float 2100000000
 Long 2100000002
Float 2100000000
 Long 2100000003
Float 2100000000
 Long 2100000004
Float 2100000000
 Long 2100000005
Float 2100000000
 Long 2100000006
Float 2100000000
 Long 2100000007

Sometimes it just produces entirely spurious values.
eg Initial values 2,100,000,000
increment = 100,000

Float 2100099968
 Long 2100100000
Float 2100199936
 Long 2100200000
Float 2100299904
 Long 2100300000
Float 2100399872
 Long 2100400000
Float 2100499840
 Long 2100500000
Float 2100599808
 Long 2100600000
Float 2100699776
 Long 2100700000

I can't figure out what's going on.

Any thougths?

idrisdraig:
I can't figure out what's going on.

Any thougths?

The variable type 'float' is accurate to "6 or 7 significant digits" (depends on the float functions you use).

Variable type "long" has accuracy for at "9 or 10 significant digits" (within its decimal range).

Each time you try to get more than 7 significant digits from a 'float' variable, there will be a problem with the accuracy in the last digits due to rounding errors.

So what's your problem in understanding?

jurs:
So what’s your problem in understanding?

Apparently not so much a lack of understanding as alack of knowledge. Hadn’t apreciated that, or it’s impact. Very helpful. Thanks.

That said, I don't understand how inaccuracies can be be provoked by a preceding sketch.

idrisdraig:
That said, I don’t understand how inaccuracies can be be provoked by a preceding sketch.

Which “preceding sketch” provokes which inaccuracies?
Example code?

The variable type “float” is always a bit inaccurate in its internal representation of decimal numbers, with almost all decimal numbers. The internal representation of ‘float’ is based on binary, not decimal. So almost all decimal numbers that you store into a ‘float’ are a bit inaccurate when trying to squeeze out more than 7 digits accuracy.

To show what can happen when using “float”, look at the output created by this sketch:

int i;
float f;

void setup() {
  Serial.begin(9600);
  Serial.println();
  for (int i=0;i<100;i++)
  {
    f=i;
    Serial.print(f,12);
    Serial.print('\t');
    f= f+0.1;
    Serial.print(f,12);
    Serial.print('\t');
    f= f-0.1;
    Serial.println(f,12);
  }  
}

void loop()
{}

I’m trying to show a float value with 13 significant digits (one before and 12 behind the decimal point).
The sketch shows:

  • the original float
  • the float after adding 0.1
  • the float after substracting 0.1
    Under normal circumstances you’d expect, that adding 0.1 and subtracting 0.1 will lead to the original number. And in many cases this even works in the visible representation with much more than 7 significant digits. In those cases, both inaccuracies cancel each other and the result is the same number as you started with.

But in other cases (in the example code watch for the lines 2 and 32), inaccuracies do not cancel each other, but add up, so the final result is different from the original number. Though: It is accurate up to 7 significant digits.

So if you need accurate calculations, you better not rely on ‘float’.
At least: Do not rely on more than 7 significant digits with ‘float’ calculations!

If you need exact calculations, use integer math whereever possible! The AVR LIBC not only provides 32 bit ‘long’ and ‘unsigned long’, but also ‘int64_t’ and ‘uint64_t’ for even higher accuracy.

Floating point numbers are like piles of sand;
every time you move them around,
you lose a little sand and pick up a little dirt. ”

— Brian Kernighan and P. J. Plauger.

jurs:
Which "preceding sketch" provokes which inaccuracies?
Example code?

The code above.
I load the sketch with initial and incremetal values that cause the float to exceede 2,150,000,000 after a few itterations, with errors resulting in the float after 2,150,000,000 has been exceeded.
If I then load what is effectively a new sketch (albeit the same code just with different and initial and incremetal values, though values which haven't previously caused errors) the incremented value of the float is still unreliable.

My guess is there is some sort of register or flag that can't be relied upon to be porperly set/reset/cleared when a new sketch is loaded.

idrisdraig:
The code above.
I load the sketch with initial and incremetal values that cause the float to exceede 2,150,000,000 after a few itterations, with errors resulting in the float after 2,150,000,000 has been exceeded.
If I then load what is effectively a new sketch (albeit the same code just with different and initial and incremetal values, though values which haven’t previously caused errors) the incremented value of the float is still unreliable.

My guess is there is some sort of register or flag that can’t be relied upon to be porperly set/reset/cleared when a new sketch is loaded.

Sorry, English is not my mother language and I still not understand your problem.

When incrementing a float by 1, this cannot work if the number has more than 7 significant digits before the decimal point.

Example code:

float f=5000000;  // 7 significant digits
// float f=50000000; // 8 significant digits

void setup() {
  Serial.begin(9600);
  Serial.println();
  for (int i=0;i<1000;i++)
  {
    Serial.println(f);
    f=f+1;
  }  
}

void loop()
{}

When using 5000000 as a starting point, you can increment by 1. You increment the 7th significant digit, that works.

But when using 50000000 as a starting point, you cannot increment by 1. You would need to increment the 8th significant digit, and that is beyond the resolution of ‘float’. So the number 50000000 is exactly the same float number as “50000000+1”, there is no difference. And therefore you can 1000 times add 1 to the number, and it always stays the same. If you increment a number by less than the float resolution, the increment doesn’t change the ‘float’ representation. Maximum is 7 significant digits. And not a single digit more.

AHHHHH!

Now I understand.
My maths is a little rusty and I think I'm probably getting confused over the exact meaning of "significant figures".
My Bad.
Problem solved.
Thanks.

Float significand is 23 bits, 0 to +8388607. Then the sign bit and 8 bit exponent make 32 bits.

That is 7 digits possible if the high order digit is 8 or less, 6 digits if it's 9.

Unsigned long, 32 bits, 0 to 4294967295.

That is 10 digits possible if the high order digit is 4 or less, 9 digits if it's 5 or more.

IMO it's better to not count on 1st digits to be less than 9.

The quirks you mention happen on every computer that uses standard IEEE single-precision arithmetic.

"What Every Programmer Should Know About Floating-Point Arithmetic:
or,
Why don’t my numbers add up?"

further discussion:

"What every computer programmer should know about floating point"

further discussion:
https://news.ycombinator.com/item?id=8321940

The Arduino compiler (avr-gcc) uses standard IEEE single-precision arithmetic.
Lots of people have talked about its various quirks,
but it continues to be better than most of the alternatives most of the time.

Alternatives to "float":

DavidCary:
"Speeding up AVR"
A Detailed Tutorial On Speeding Up AVR Division | Hackaday
AVRfix download | SourceForge.net
avr-gcc - GCC Wiki
http://en.wikibooks.org/wiki/Floating_Point/Fixed-Point_Numbers

Go here and under snippets and sketches, performance snippets has some mind benders.
http://playground.arduino.cc/Main/GeneralCodeLibrary