Problème compteur incrémentale et reste division euclidienne

Fermé
Anonymous - Modifié le 4 avril 2020 à 16:59
 Utilisateur anonyme - 5 avril 2020 à 09:35
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
A voir également:

7 réponses

yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024 Ambassadeur 1 552
4 avril 2020 à 16:45
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.
0
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.
0
Utilisateur anonyme
4 avril 2020 à 17:07
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?


0
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.
0
Utilisateur anonyme
4 avril 2020 à 17:40
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é

0
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.
0
yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024 1 552
4 avril 2020 à 18:29
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.
0
Utilisateur anonyme > yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024
4 avril 2020 à 18:44
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.
0
Utilisateur anonyme
4 avril 2020 à 18:58
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.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
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.
0
yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024 1 552
4 avril 2020 à 19:49
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?
0
anonymous > yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024
Modifié le 4 avril 2020 à 20:42
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
0
yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024 1 552 > anonymous
4 avril 2020 à 21:54
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.
0
Utilisateur anonyme
4 avril 2020 à 19:31
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.
0
Utilisateur anonyme
4 avril 2020 à 19:41
PS, juste pour répondre à
Et venant de l'informatique embarquée je n'avais jamais eu ce problème

https://www.developpez.com/actu/222280/Un-bogue-informatique-avait-contraint-le-Boeing-787-a-etre-redemarre-tous-les-248-jours-pour-eviter-une-interruption-totale-du-systeme-electrique/

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 ;)
0
Utilisateur anonyme > Utilisateur anonyme
4 avril 2020 à 19:46
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.
0
yg_be Messages postés 23346 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 24 novembre 2024 1 552 > Utilisateur anonyme
4 avril 2020 à 19:52
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
0
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.
0
Utilisateur anonyme > anonymous
4 avril 2020 à 21:26
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à).
0
Utilisateur anonyme
Modifié le 5 avril 2020 à 09:36
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)

0