'for' varies the results of pow(2,2) ?

The code:

#include <SoftwareSerial.h>
void setup() {
Serial.begin(9600);
}
void loop(){
int i = 0;
Serial.print("pow(2,");
Serial.print(i) ;
Serial.print("):") ;
Serial.println(int(pow(2,i))) ;
i = 1;
Serial.print("pow(2,");
Serial.print(i) ;
Serial.print("):") ;
Serial.println(int(pow(2,i))) ;
i = 2;
Serial.print("pow(2,");
Serial.print(i) ;
Serial.print("):") ;
Serial.println(int(pow(2,i))) ;
}

gives:

pow(2,0):1
pow(2,1):2
pow(2,2):4 OK

and the code:

#include <SoftwareSerial.h>

void setup() {
Serial.begin(9600);
}
void loop(){
for (int i = 0; i < 3; i++) {
Serial.print("pow(2,");
Serial.print(i) ;
Serial.print("):") ;
Serial.println(int(pow(2,i))) ;
}
}

gives:
pow(2,0):1
pow(2,1):2
pow(2,2):3 !!???

Why?

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248044758/4

funziona:
...
Why?

The second part is pretty easy to show. The avr-libc pow() function actually works in floating point arithmetic.

Print floating point results to see the effects of using floating point arithmetic.

void setup()
{
    Serial.begin(9600);
}
void loop()
{
    for (int i = 0; i < 3; i++) {
        Serial.print("pow(2,");
        Serial.print(i) ;
        Serial.print("):") ;
        Serial.println(pow(2,i),7) ;
    }
    delay(1000);
}

Output


[color=blue]
pow(2,0):1.0000000
pow(2,1):2.0000000
pow(2,2):3.9999995[/color]

Now, if you cast a floating point number to an integer, the fractional part is truncated, so the numbers printed out would be integers 1, 2, 3, as you see with the second sketch.

(This is a Very Big Deal.)

Now as for the first sketch: Here the smarty-pants optimizing compiler looks at your code very carefully: It sees that you are setting a variable to a constant value. You don't change the value before calling the pow() function, and the other argument is also a constant. The avr-gcc compiler, believe it or not, has a built-in pow() function (above and beyond the standard math library function) and the compiler can see that you are calling pow() with two constant integers. It even knows ways of calculating integer powers of integers (within a certain range) without using floating point arithmetic. It does the work for you at compile time and plugs the (exact) value 4 into the assembled code so that it doesn't even have to do any of the actual work at run time.

In my opinion, that's just phenomenal. My flabber is totally gasted.

Note that this sneaky, tricky stuff about the optimization is not any part of the standard C (or C++) language specification, and things might be handled differently with different compilers or different versions of the same compiler. Sometimes the degree of optimization can be controlled by command-line switches when the compiler is invoked. So you can't count on any particular compiler doing the constant integer power stuff exactly. (A little numerical pun here, get it? Can't count on it. No? Oh, well...) See Footnote.

Bottom line: The first sketch just happens to use integer arithmetic and gives exact values for the particular arguments that are used. The second sketch, using floating point arithmetic, is susceptible to floating point arithmetic errors, which just happen to show up the third time through the loop, but not for the first two times.

Regards,

Dave

Footnote:
When dealing with floating point arithmetic, you must always (yes always) be aware of the possibility of roundoff error.

In the second sketch, since the power is calculated several times in a loop with different arguments, the compiler does not (can not, without unrolling the loop) calculate the result at compile time. The code calls the standard math library function pow() to do the calculation at run time.

There is only one pow() function in the avr-libc library, and it has floating point arguments, so the compiler "promotes" the arguments in the sketch to floating point values and works on them with floating point arithmetic.

In the avr-libc library, the pow() function calculates "x = a to the power b" by a floating point calculation equivalent to the following mathematical definition:

x = exp(b*log(a))

Where exp() is the exponential function and log() is logarithm to the base e.

The function is implemented in assembly language in the file pow.S in the libm/fplib directory of avr-libc source tree. Roundoff error is always a possibility. But I said that already.

davekw7x:
you must always (yes always) be aware of the possibility of roundoff error

I would say inevitability of roundoff error.

I see.

Thank you very much for your feedback :slight_smile:

gardner:

davekw7x:
you must always (yes always) be aware of the possibility of roundoff error

I would say
inevitability of roundoff error.

I hate to repeat myself, but I would say possibility. I also said "always be aware of..." And I really meant the "always" part. That's why I repeated it (even though I hate to repeat myself). See Footnote.

Furthermore, I might even say that nothing (much) is really inevitable except, perhaps, uncertainty. I mean, you can't guarantee that there will be roundoff error all possible sequences of floating point calculations, can you?

It is very possible that calculations can involve only values that can be stored exactly as binary floating point numbers in a given system and that neither intermediate terms nor the final result will have roundoff error(s). But (and I hate to repeat myself) I wouldn't count on it.

Regards,

Dave

Footnote:
"When I use a word," said Humpty Dumpty in rather a scornful tone,
"It means just what I choose it to mean --- neither more nor less."

"The question is," said Alice, "whether you can make words mean so many different things."

"The question is," said Humpty Dumpty, "which is to be master --- that's all."

---Lewis Carroll
Through the Looking Glass

davekw7x:
It is very possible that calculations can involve only values that can be stored exactly as binary floating point numbers in a given system and that neither intermediate terms nor the final result will have roundoff error(s).

Sure. But the probability that a random calculation dreampt up by a naive beginner might have this property is vanishingly small. I took numerical methods too, and I know that any non-trivial numerical calculation that yields an exact solution using floating point is a pretty unusual edge case and one that would have to be carefully planned. And is usually one that would be better executed using integer or fixed-point approaches.

But (and I hate to repeat myself) I wouldn't count on it.

Like I said -- inevitable. By far the safest policy is to assume that there will be rounding errors or similar inaccuracy and get on with solving your problem. Holding out even the faintest hope that there will be a miraculous exact solution is not a realistic approach.