Pages: [1]   Go Down
Author Topic: 1+1=11... et ça c'est beau !  (Read 1199 times)
0 Members and 1 Guest are viewing this topic.
Metz
Offline Offline
Newbie
*
Karma: 0
Posts: 17
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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  smiley-wink

Code:
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);
  }

}
« Last Edit: October 17, 2012, 09:14:54 am by AspiGeek » Logged

Rennes
Offline Offline
Sr. Member
****
Karma: 0
Posts: 273
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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


Metz
Offline Offline
Newbie
*
Karma: 0
Posts: 17
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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 :/

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.
Logged

Offline Offline
Full Member
***
Karma: 0
Posts: 152
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Chez moi :

Code:
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 :

Code:
0
1
2
4
8
16
32
Logged

France
Offline Offline
Faraday Member
**
Karma: 52
Posts: 5341
Arduino Hacker
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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à.
Logged

Des news, des tuto et plein de bonne chose sur http://skyduino.wordpress.com !

Metz
Offline Offline
Newbie
*
Karma: 0
Posts: 17
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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  smiley-eek

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.

« Last Edit: October 17, 2012, 12:03:51 pm by AspiGeek » Logged

France
Offline Offline
Faraday Member
**
Karma: 52
Posts: 5341
Arduino Hacker
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Après quelques tests (oui je suis curieux smiley-mr-green) voici ce que je constate :

- Sur PC (pour avoir une base de comparaison) :
Code:
#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 :
Quote
1
2
4
8
16
(Tout est normal)

- Sur AVR (avec avr-gcc 4.3.3 (WinAVR 20100110)) :
Code:
#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 :
Quote
1
2
3
7
15
(Houston, we've had a problem)

En utilisant des double :
Code:
#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 :
Quote
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()
« Last Edit: October 17, 2012, 12:24:01 pm by skywodd » Logged

Des news, des tuto et plein de bonne chose sur http://skyduino.wordpress.com !

Metz
Offline Offline
Newbie
*
Karma: 0
Posts: 17
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Quote
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  smiley-lol
Quote
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  smiley-red

Logged

Ales
Offline Offline
Faraday Member
**
Karma: 29
Posts: 3178
Do or DIY
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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


Metz
Offline Offline
Newbie
*
Karma: 0
Posts: 17
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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  smiley-wink
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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…
Logged

Metz
Offline Offline
Newbie
*
Karma: 0
Posts: 17
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

France
Offline Offline
Faraday Member
**
Karma: 52
Posts: 5341
Arduino Hacker
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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 ...
Logged

Des news, des tuto et plein de bonne chose sur http://skyduino.wordpress.com !

Offline Offline
Newbie
*
Karma: 0
Posts: 4
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Tentative d'explication du problème...

Il y a bien un problème de précision, la fonction 'pow' consiste en gros à calculer x*x*x...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 :

Code:
  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.
Logged

Pages: [1]   Go Up
Jump to: