1+1=11... et ça c'est beau !

Hello,

Alors je viens de perdre pas mal d'heures sur un drôle de truc, si vous aimez les énigmes c'est pour vous !
Le code ci-dessous fonctionne, quand i=2 resultat vaut 4...jusqu'à là c'est normal, mais si vous initialisez l'index de la boucle à 0 au lieu de 1 alors quand i=2 résultat vaut 3...
Pow retourne un double qui vaut 3.999. Dans mon programme la boucle passe par plusieurs puissances mais seul le 2 au carré pose problème.
Je fais un bisous à celui qui trouve l'explication apparemment le i++ est louche :wink:

void setup()
{
  
  Serial.begin(9600); 
  for(byte i=1; i<3 ; i++) // pour rentrer dans une autre dimension il suffit de remplacer i=1 par i=0 ^^
  {
    int resultat = pow(2,i);
    Serial.println(resultat);
  } 

}

Dans le cas présent j'opterais plutôt pour int résultat = 1 << i;

Merci patg_

En effet c'est plus propre que mon round() pour contourner le problème, je m'en veux de ne pas y avoir pensé avant ^^

Par contre pour mon problème initial, c'est vraiment à devenir fou, en fait qu'importe l'initialisation de la boucle, tant qu'on boucle deux fois pow() retourne les bonnes valeurs, au delà de 2 boucles une erreur apparaît faisant passer tous résultats à l'entier inférieur lors du passage float en int :confused:

J'avoue que j'aimerai bien comprendre ce qui se passe car je sèche totalement... merci par avance à l'âme charitable qui saura m'éclairer.

Salut,
tu dis toi même que résultat est un double et tu le défini en int non ?

Chez moi :

void setup()
{
  
  Serial.begin(9600); 
  for(int i=0; i<6 ; i++) // pour rentrer dans une autre dimension il suffit de remplacer i=1 par i=0 ^^
  {
    double resultat = pow(2,i);
    Serial.print("Pour un exponent de ");
    Serial.print(i);
    Serial.print(" resultat = ");
    Serial.println(resultat);
  } 

delay(10);
}


void loop(){
  
  
}

Donne :

0
1
2
4
8
16
32

Bonjour,

Le passage d'un double à un int induit un cast avec perte de précision.
Le résultat n'est pas arrondi, il est tronqué (la partie décimal disparait), 3.99999 devient 3, 2.1111 devient 2, etc ...
A mon avis le problème vient tout simplement de là.

Merci de ta réponse Trigger.

Ne te fie pas au print, si tu cast resultat en int tu verras que le problème existe toujours et que le double ne tombe pas sur un entier.
J'ai besoin de récupérer un int car il me sert d'index dans un tableau, un index de tableau typé double n'est pas autorisé en C.

J'ai beau retourné le problème dans tous les sens je ne comprends pas comment c'est lié au nombre de boucles :astonished:

Edit : Bonjour Skywodd pourtant dans mon premier exemple le cast fonctionne !!!
Sinon je suis d'accord que le int arrondi à l'entier inférieur mais ce n'est pas le problème ici.

A savoir, juste 2 tours de boucle pow(2,i) renvoie les bonnes valeurs. Plus de 2 tours de boucles tous les résultats ne sont plus des entiers.

Après quelques tests (oui je suis curieux :grin:) voici ce que je constate :

  • Sur PC (pour avoir une base de comparaison) :
#include <math.h>
#include <stdio.h>

int main(void) {
	int i;
	
	for(i = 0; i < 5; ++i) {
		int resultat = pow(2, i);
		printf("%d\n", resultat);
	}

	return 0;
}

Résultat :

1
2
4
8
16

(Tout est normal)

  • Sur AVR (avec avr-gcc 4.3.3 (WinAVR 20100110)) :
#include <math.h>

void setup() {
  Serial.begin(9600);
  for(int i = 0; i < 5; ++i) {
    int resultat = pow(2, i);
    Serial.println(resultat);
  } 
}

void loop() {

}

Résultat :

1
2
3
7
15

(Houston, we've had a problem)

En utilisant des double :

#include <math.h>

void setup() {
  Serial.begin(9600);
  for(int i = 0; i < 5; ++i) {
    double resultat = pow(2, i);
    Serial.println((double)resultat);
    Serial.println((int)resultat);
  } 
}

void loop() {

}

Résultat :

1.00
1
2.00
2
4.00
3
8.00
7
16.00
15

A partir de 4.00 tout part en brioche ...
Ça sent le bug avec le cast double -> int ou au niveau du Serial.println()

Moi aussi Skywodd je pensais que ça partait en sucette à partir de pow(2,2)...

Mais c'est pire que ça... c'est vraiment lié au nombre de boucle par exemple ce code marchera car 2 boucles même si initialisé à 6

for(int i=6; i<8 ; i++){ code }

dès qu'on dépasse 2 boucles tous les résultats sont faux même les deux premiers XD

for(int i=6; i<9 ; i++){ code }

Je laisse tomber... suivant si on rajoute des prints dans mon tout premier code pourtant bon jusqu'à présent j'obtiens aussi des résultats différents...

Je déclare forfait :blush:

Et comment expliquer que pour i=0 ça marche et pas avec i=1 ?

Je ne marque pas le sujet résolu, je laisse courir le topic peut être qu'un jour une personne pourra apporter davantage d'informations sur l'origine du problème.
En attendant la solution rappelée par patg_ est de loin la meilleure et la plus propre...et surtout pas de problème de résultat :wink:

Petite question : te linkes-tu à libm, sur l'AVR, quand tu as le souci ? Si non, essaie avec. Des fois, les calculs flottants se comportent très étrangement, sans elle…

Merci de ta réponse Kernald.
Oui cette piste a aussi été testée, je n'ai pas eu le reflexe dans un premier temps mais skywodd l'avait fait quelques posts au dessus.

Kernald:
Petite question : te linkes-tu à libm, sur l'AVR, quand tu as le souci ? Si non, essaie avec. Des fois, les calculs flottants se comportent très étrangement, sans elle…

L'ide arduino ajoute le link de libm par défaut.
Ce "bug" (si s'en est réellement un) mériterai une analyse plus approfondit ...

Tentative d'explication du problème...

Il y a bien un problème de précision, la fonction 'pow' consiste en gros à calculer xxx...x*x avec le bon nombre de x (la fonction pow le fait de manière plus intelligente mais mathématiquement c'est ça).
Dans le cas de ta boucle, à chaque itération le calcul ce fait avec juste un x de plus, donc il est possible de garder la valeur de la boucle précédente et de juste la multiplier par x à chaque fois. Ce qui donne une simple multiplication à chaque itérations au lieu d'un appel à fonction très couteuse. Ce qui donne comme boucle :

  float temp = 1;
  for(byte i=1; i<3 ; i++)
  {
    temp *= 2.0;
    int resultat = temp;
    Serial.println(resultat);
  }

Ok, et le rapport avec la choucroute ? C'est tout simple, les multiplications ne forment plus une seule opération comme dans le cas de pow et donc les garanties de précision ne sont plus les même. Dans le cadre du standard IEEE 754, pour de petites boucles il n'y a pas de problèmes. Les arrondis sont fais de telle manière que ça marche.

Le problème est donc double (ici c'est une supposition)

  • l'optimiseur de GCC fais la transformation car il doit penser être dans le cadre du standard (c'est le cas de la grande majorité des plateformes classiques)
  • la lib math software de l'AVR ne doit pas respecter complètement le standard car les règle d'arrondis sont complexe et donc coûteuse en instructions.
    Donc à chaque tour de boucle tu accumule un peu plus d'erreur et donc avec une seule itération sa va, mais avec deux ça ne marche plus...

PS: je précise que c'est une supposition. J'ai vu des compilateur faire cette optimisation sur UltraSPARC notamment, je ne sais pas si GCC en est capable, et je ne connais pas le comportement de la lib math software.
Ça explique bien ce que tu observe mais il faudrais regarder le code assembleur généré pour être sur.