Problème compteur incrémentale et reste division euclidienne

Signaler
-
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
-
Bonjour,

Avec le code suivant :

using System;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            double a = 0;
            int b = 0;
            for(int i=0; i<=33;i++)
            { 
                Console.WriteLine("double : "+a);
                a = (a + 0.1) % 1.1;
                Console.WriteLine("entier : "+b);
                b = (b + 1) % 11;
                Console.WriteLine(Environment.NewLine);
                Thread.Sleep(200);
            }
            Console.ReadLine();

        }
    }
}


Nous sommes bien d'accord que les résultat on un coefficient 10 d'écart mais voilà la sortie n'est pas ça.

Voici ce que j'ai :

double : 0
entier : 0


double : 0,1
entier : 1


double : 0,2
entier : 2


double : 0,3
entier : 3


double : 0,4
entier : 4


double : 0,5
entier : 5


double : 0,6
entier : 6


double : 0,7
entier : 7


double : 0,8
entier : 8


double : 0,9
entier : 9


double : 1
entier : 10


double : 1,1
entier : 0


double : 0,0999999999999999
entier : 1


double : 0,2
entier : 2


double : 0,3
entier : 3


double : 0,4
entier : 4


double : 0,5
entier : 5


double : 0,6
entier : 6


double : 0,7
entier : 7


double : 0,8
entier : 8


double : 0,9
entier : 9


double : 1
entier : 10


double : 1,1
entier : 0


double : 0,0999999999999999
entier : 1


double : 0,2
entier : 2


double : 0,3
entier : 3


double : 0,4
entier : 4


double : 0,5
entier : 5


double : 0,6
entier : 6


double : 0,7
entier : 7


double : 0,8
entier : 8


double : 0,9
entier : 9


double : 1
entier : 10


double : 1,1
entier : 0


Si vous avez une idée de où cela peut venir.

Bon courage

Configuration: Windows / Firefox 74.0 / Visual Studio Community 2019
16.4.2

7 réponses

Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020
659
bonjour, ce serait plus clair si tu montrait le résultat que tu attends, qui est probablement différent du résultat que tu obtiens.
Comme je l'ai dit les résultats devraient avoir un coef 10 d'écart. Si b = 1, a = 0.1 et sinon le résultat de a doit s'incrémenté de 0 à 1 par pas de 0.1. Je précise que le résultats b est correct.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554
Bonjour

et merci à Chris94 d'avoir corrigé ta coloration syntaxique, tu as essayé de t'en servir et c'est bien, mais dans ce petit tuto il est décrit comme bien l'utiliser https://codes-sources.commentcamarche.net/faq/11288-les-balises-de-code

Venons à ta question
Nous sommes bien d'accord que les résultat on un coefficient 10 d'écart mais voilà la sortie n'est pas ça.

Sauf erreur de ma part, si....

double : 0
entier : 0 //0 * 10 est bien égal à zéro


double : 0,1
entier : 1 //0,1 * 10 est bien égal à 1


double : 0,2
entier : 2 //0,2 * 10 est bien égal à 2


double : 0,3
entier : 3 //0,3 * 10 est bien égal à 3

//etc...




Il n'y a que pour le dernier
double : 1,1 // ce résultat est incohérent, puisqu'on divise par 1.1, il ne devrait pas pouvoir y avoir un reste de 1.1
entier : 0

C'est ça ton problème?


Exact. Je devrai avoir 0 aussi pour le double mais dès fois j'ai 0,09999999 à la place de 0.1 et dès fois j'ai 1.1 au lieu de 0. Ce qui pose problème et qui n'est pas correcte mathématiquement ce qui est embêtant pour de l'informatique.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554
L'informatique, n'est jamais juste.
C'est toujours un choix au plus proche possible.

Dans la vraie vie, on peut écrire une infinité de nombres parce « qu’il suffit d’ajouter un chiffre » pour qu’il se concrétise.

L’informatique vient de l’électronique et là ça se complique, un nombre à une taille définit de bits qui est directement lié à la technologie.
Et on fait avec.

Du coup, les nombres à virgule flottantes sont utilisés pour représenter un nombre décimal
Ici https://forums.commentcamarche.net/forum/affich-35846831-erreur-de-calcul#3
J’ai proposé une explication un peu généraliste.
DalFab et Reiverax ont par la suite donné des precisions.

Tu peux aussi lire le paragraphe « l’éternel problème de la virgule qui part en couille » http://sametmax.com/les-nombres-en-python/

Le 0.0999999999 est directement une manifestation de cela.
Il y a fort à parier que le 1.1 aussi, je vais faire quelques tests de mon coté

Merci de ta réponse. Pour résoudre le problème je vais pour avoir a faire : a = b/10, comme ça j'aurais le bon résultat.
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020
659
pour moi, l'ordinateur est tout-à-fait capable de faire des calculs précis, il est même incapable de faire des approximations.
en choisissant d'utiliser "double" pour le nombre 1,1, le programmeur choisi, peut-être sans s'en rendre compte, de transformer le nombre 1,1 en un autre nombre, légèrement différent. le programmeur fait ce choix sans doute par facilité, et c'est ce choix qui conduit à un résultat correct, mais différent de ce que le programmeur attend.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554 >
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020

Oui et non, souvent c'est par ignorance.
Il y a bien sûr moyen de faire précis, soit en utilisant un type encore plus définit que double (decimal par exemple).
Soit en utilisant un type de nombre à virgule fixe, mais ça faut se l'écrire soit même.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554
Bon alors en décomposant le calcul, je n'arrive pas à voir à quel moment le petit epsilon qui nous embête se glisse.

            double a = 0;
            int b = 0;
            for (int i = 0; i <= 33; i++)
            {
                Console.Write("'a': {0}\t\t",a);
                a += 0.1;
                Console.Write("'a' + 0.1: {0}\t\t",a);
                a %= 1.1;
                Console.WriteLine("'a' % 1.1: {0}",a);

                Console.WriteLine("entier : " + b);
                b = (b + 1) % 11;
                Console.WriteLine(Environment.NewLine);
            }
            Console.ReadLine();


Par contre, si j'applique un simple arrondi à
a += 0.1
, il n'y a plus d'erreur.
            double a = 0;
            int b = 0;
            for (int i = 0; i <= 33; i++)
            {
                Console.Write("'a': {0}\t\t",a);
                a = Math.Round(a + 0.1, 1);
                Console.Write("'a' + 0.1: {0}\t\t",a);
                a %= 1.1;
                Console.WriteLine("'a' % 1.1: {0}",a);

                Console.WriteLine("entier : " + b);
                b = (b + 1) % 11;
                Console.WriteLine(Environment.NewLine);
            }
            Console.ReadLine();


Je ne sais pas exactement comment est codé l'arrondi sur un double, mais il à un effet de "remise à zéro" de la mantisse et de l'exposant quand une multitude d'opérations finit par la mettre un peu à mal.

Attention cependant, l'arrondi informatique n'est pas l'arrondi mathématique Français.
C'est l'arrondi monétaire international.
            Console.WriteLine("Arrondi de 1.5: {0}", Math.Round(1.5));
            Console.WriteLine("Arrondi de 2.5: {0}", Math.Round(2.5));

Ces 2 arrondis donnent 2.
Dans le cas de 1.5, l'acheteur va payer 2, il perd donc 0.5. Dans le cas de 2.5, il payera 2, c'est le vendeur qui perd 0.5.
Cette règle permet statistiquement de répartir gain et perte équitablement entre acheteur et vendeur.
Elle s'applique quelque soit le nombre de décimales,
1.111111111111115 => 1.11111111111112
1.111111111111125 => 1.11111111111112
On l'appelle aussi l'arrondi au pair le plus proche.

Normalement dans le calcul actuel, il n'y a pas de raison de tomber sur un cas comme cela mais, mieux va le savoir.
Hé bien, je ne pensais pas que ce post déchaînerait autant de passion. Je précise que le programme est juste un exemple simple pour bien montrer le problème. Et venant de l'informatique embarquée je n'avais jamais eu ce problème mais là visual studio ne voulait pas que j'utilise un float avec la division euclidienne et ma proposé le double. Mais je vais regarder le décimal qui peut être intéressant. Merci à tous pour vos réponses. Et n'hésitez pas à en donner d'autres, les problèmes ont toujours de multiples solutions.

P.S : C'est toujours anonymous mais le correcteur orthographique en a décidé autrement.
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020
659
je pense qu'en informatique embarquée, personne ne fera jamais un modulo avec un nombre non entier. un peu parce que c'est inutile, beaucoup parce que le résultat est imprévisible, comme tu l'observes.
et, surtout, personne ne s’inquiétera de la différence entre 0,09999999 et 0.1: pourquoi t'en inquiètes-tu, que vas-tu faire avec ces résultats?
>
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020

Comme je l'ai précisé en informatique embarquée je n'ai jamais eu ce problème. Et un modulo fait directement avec un nombre décimal m'évite de faire un calcul en plus. Si je fait le calcule directe le résultat est bon. Le problème c'est que à chaque boucle les erreurs s'ajoutent et rapidement j'ai des résultats qui ne correspondent pas à ce que j'attends. Et donc si par exemple je veux créer un signal triangle à une fréquence de 100Hz je vais vite avoir une dérive l'amplitude. Et surtout si je veux 0,1 je ne veux pas 0,09999999. Car après au lieu d'avoir 0 j'ai 1.1 ce qui est la très différents
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020
659 > anonymous
je pense que tu aurais eu exactement le même résultat si tu avais ainsi incorrectement programmé en informatique embarquée.
si tu veux 0,1, n'utilise pas de double dans des langages tels que le C, à moins de comprendre et contrôler l'impact de l'utilisation de double.
et, surtout, n'utilise pas de modulo avec des nombres (non entiers) dont tu ne maîtrises pas la valeur.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554
Avec le float, c'est pire, y'a moins de bits, donc moins de précision.
Faire le calcul avec un entier et diviser le résultat par 10 pour l'affichage (ou la fourniture) reste le meilleur moyen de ne jamais avoir d'erreur de virgule flottante.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554 >
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020

et d'ailleurs, il me semble me souvenir que le pilote automatique d'appolo a lui aussi subit un dépassement de bit au moment de l'alunissage.
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020
659 >
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020

ce qui n'a rien à voir avec une incompréhension à propos d'un arrondi.
comme quelqu'un qui s'étonnerait que 0,33*3 ne fait pas 1
Alors le float dispose moins de nombre après la virgule certe mais 0 à 1.1 il n'y a qu'un chiffre après la virgule. Deuxièmement ça n'a strictement rien avoir avec un dépassement mémoire. Ton exemple est hors propos. Et si tu as remarqué le code est fait de tel sorte pour qu'il n'y ai jamais de dépassement vu que a ne dépasse jamais 1, enfin si le calcule était juste. Si j'avais fait des opérations mathématiques directement en analogique avec des AOP j'accepterais l'erreur. Surtout que comme je l'ai dit c'est la première fois que ça m'arrive.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554 > anonymous
Alors le float dispose moins de nombre après la virgule certe mais 0 à 1.1 il n'y a qu'un chiffre après la virgule

c'est incorrect comme raisonnement, d'une parce que le principe même du nombre à virgule flottante c'est de toujours avoir le même nombre de chiffre avant et après la virgule, et d'autre part parce que ça n'est pas codé en base 10 mais en base 2.
D'ailleurs, Dalfab dans le premier lien que je t'ai donné (à se demander si tu es allé voir?) décrit très bien le problème de 0.1 qui n'est pas fini en base 2

Ton exemple est hors propos
c'est pour cela que j'ai ajouté
C'est pas le même problème, mais c'est la même raison sous jacente, derrière l'informatique y'a de l'électronique
et aussi pour donner 2 exemples de bugs connus dans l'informatique embarquée, mais ce ne sont pas les seuls (et loin de là).
Messages postés
11512
Date d'inscription
lundi 9 juin 2008
Statut
Contributeur
Dernière intervention
10 juillet 2020
659 > anonymous
c'est la première fois que cela t'arrive: petit à petit, tu découvres la réalité.
Messages postés
14475
Date d'inscription
mardi 11 mars 2003
Statut
Contributeur
Dernière intervention
10 juillet 2020
554
La nuit portant conseil, et hier je planchais sur une autre question en même temps.
J'ai négligé un point important de ton symptôme, Console.Write(line) applique un arrondi.

Pour voir la valeur vraie des variables, pas le choix, il faut espionner en pas à pas.
En repartant de ce code
            for (int i = 0; i <= 33; i++)
            {
                Console.Write("'a': {0}\t\t", a);
                a = a + 0.1;
                Console.Write("'a' + 0.1: {0}\t\t", a);
                a %= 1.1;
                Console.WriteLine("'a' % 1.1: {0}", a);

                Console.WriteLine("entier : " + b);
                b = (b + 1) % 11;
                Console.WriteLine(Environment.NewLine);
            }

J'ai mis un point d'arrêt et 2 espions (il est possible que cet espion arrondissent aussi, mais on y voit quand même plus de choses

Je t'ai pris 3 captures d'écran

3 ème itération (i == 2) ça commence à se voir


10 ème itérations, y' un 8 à l'avant dernière décimale


11 ème itération, bien que très proche de 1.1 est bien strictement inférieur, le reste est donc proche de 1.1, et arrondi à 1.1 par Console.Write(line)

Quand j'étais petit, la mer Morte n'était que malade.
George Burns