L’informatique peut provoquer des erreurs de calcul
Un bon moyen d’obtenir des résultats faux lors de calculs scientifiques, c’est de recopier dans un ouvrage mathématique la formule de définition de la grandeur à calculer, et de la transposer telle quelle dans le langage de programmation utilisé.
En effet les nombres des ordinateurs ne sont pas les nombres réels de l’ensemble $\mathbbR$, mais un sous-ensemble fini des entiers de l’ensemble $\mathbbN$, que l’on peut agencer astucieusement pour représenter des nombres fractionnaires avec un nombre fini de chiffres après la virgule.
Calcul itératif par un algorithme de Donald Knuth
Donald Knuth, qui fait référence à B.P. Welford, a publié un algorithme qui répond à notre attente et qui a été abondamment analysé et trouvé robuste.
L’idée est simple : si nous connaissons la moyenne des $n-1$ premières valeurs, plutôt que de multiplier cette moyenne par $n-1$, de lui ajouter la $n$\fupième valeur et de diviser le résultat par $n$, il est plus simple d’ajouter à cette moyenne la différence entre elle et cette $n$\fupième valeur, divisée par $n$, qui est en fait la contribution de cette nouvelle valeur à la nouvelle moyenne.
Pour la variance, nous additionnons à chaque étape non pas les carrés des valeurs selon la formule classique, mais le produit de l’écart entre la nouvelle valeur et la moyenne précédente par l’écart entre le nouvelle valeur et la nouvelle moyenne, ce qui donne des valeurs numériques plus faibles (en général).
Soit l’algorithme :
Algo : MoyenneVariance -- selon Donald E. Knuth (1998).
Donnée : Observations tableau
Soient : nbObservations <- longueur(Observations)
moyenne <- Observations[0]
S2 <- 0
pour i allant de 1 à nbObservations - 1 faire
tmp <- moyenne
moyenne <- moyenne + (Observations[i] - moyenne)/(i+1)
S2 <- S2 + (Observations[i] - tmp)
* (Observations[i] - moyenne)
fin pour
Variance <- S2 / (nbObservations - 1)
retourne moyenne, Variance
fin MoyenneVarianceet en TypeScript :
const Observations: number[] = [12, 16, 13.5, 8, 11.5, 18]
function MoyenneVariance(T: number[]): number[] {
const nbObservations: number = T.length;
let moyenne = T[0];
let S2 = 0;
for (let i: number = 1; i < nbObservations; i++) {
let tmp = moyenne;
moyenne = moyenne + ((T[i] - moyenne) / (i + 1));
S2 = S2 + ((T[i] - tmp) * (T[i] - moyenne));
}
let Variance = S2 / (nbObservations - 1);
return [moyenne, Variance];
}
console.log(MoyenneVariance(Observations));Ce programme lit les valeurs dans un tableau qui comporte un nombre par ligne et retourne un tableau constitué de la moyenne et de la variance.
Calcul avec un flux de données
Finalement il serait aussi commode de considérer que les données parviennent au programme par un flux d’origine quelconque, fichier, réseau, capteur ou appareil de mesure. Cela peut être commode si les données sont issues d’un appareil de mesure en continu.
La question :
Bonjour ChatGPT, je veux calculer la moyenne et la variance d’un flux de données, au fur et à mesure de leur lecture donnée par le fichier Data-2 ci-dessous :
13
11
45
78
22
12
58
32
23
55
24
56Voici le programme principal :
#!/usr/bin/env -S npx tsx
// ne pas oublier : $ npm install commander
// et : $ chmod a+x main.ts
import { Command } from "commander";
import * as readline from "readline"; //
import * as fs from "fs";
import { MoyenneVariance } from "./class_MoyenneVariance.js";
let moyenne_variance = new MoyenneVariance;
// --- Programme principal avec Commander ---
const program = new Command();
program
.name("calcul_moyenne_variance")
.description("Lire un flux de données et calculer moyenne et variance au fur et à mesure")
.argument("<flux>", "Fichier de données numériques")
.action(async (flux: string) => {
await lireLignes(flux);
});
program.parse(); // déclenche le parsing des arguments
// --- Fonction utilitaire ---
async function lireLignes(flux: string) {
let S2: number = 0;
let n: number = 0;
let moyenne: number = 0;
let tmp: number = moyenne;
let Variance: number = 0;
const rl = readline.createInterface({
input: fs.createReadStream(flux),
crlfDelay: Infinity, // gère correctement \n et \r\n
});
for await (const ligne of rl) {
moyenne_variance.MoyenneVariance(
Number(ligne),
S2,
n,
moyenne,
Variance
);
}
}
console.log(lireLignes(flux));et voici le programme de calcul :
export class MoyenneVariance {
public MoyenneVariance(xi,
S2,
n,
moyenne,
Variance) {
let tmp: number;
while (xi) {
n = n + 1;
tmp = moyenne;
moyenne = moyenne + (xi - moyenne) / n;
S2 = S2 + (xi - tmp) * (xi - moyenne);
Variance = S2 / (n - 1);
}
}
}Je pense que je ne détecte pas correctement la fin de fichier, parce que le programme tourne indéfiniment. Où est l’erreur ?
La réponse :
Excellent — ton intuition est très juste : ton programme tourne en boucle infinie à cause du while (xi) dans ta classe MoyenneVariance.
Voici la portion fautive :
while (xi) {
n = n + 1;
tmp = moyenne;
moyenne = moyenne + (xi - moyenne) / n;
S2 = S2 + (xi - tmp) * (xi - moyenne);
Variance = S2 / (n - 1);
}Le while (xi) signifie :
« tant que xi est une valeur "truthy" (différente de 0, undefined, null, etc.), répéter cette boucle. »
Or, xi ne change jamais dans cette boucle.
Donc si xi ≠ 0, la boucle ne se termine jamais → boucle infinie.
La solution :
Tu ne veux pas de boucle while ici — tu veux simplement mettre à jour les valeurs à chaque lecture d’un nouvel échantillon.
Donc, il faut remplacer cette méthode par quelque chose comme ceci :
export class MoyenneVariance {
private n: number = 0;
private moyenne: number = 0;
private S2: number = 0;
public ajouter(xi: number) {
this.n += 1;
const tmp = this.moyenne;
this.moyenne = this.moyenne + (xi - this.moyenne) / this.n;
this.S2 = this.S2 + (xi - tmp) * (xi - this.moyenne);
}
public getMoyenne(): number {
return this.moyenne;
}
public getVariance(): number {
return this.n > 1 ? this.S2 / (this.n - 1) : 0;
}
}Et dans ton programme principal :
Adapte la boucle pour utiliser cette méthode incrémentale :
async function lireLignes(flux: string) {
const rl = readline.createInterface({
input: fs.createReadStream(flux),
crlfDelay: Infinity,
});
for await (const ligne of rl) {
const x = Number(ligne);
moyenne_variance.ajouter(x);
console.log(
`x = ${x}, moyenne = ${moyenne_variance.getMoyenne().toFixed(2)}, variance = ${moyenne_variance.getVariance().toFixed(2)}`
);
}
console.log("\nRésultat final :");
console.log(`Moyenne = ${moyenne_variance.getMoyenne().toFixed(2)}`);
console.log(`Variance = ${moyenne_variance.getVariance().toFixed(2)}`);
}Et surtout, ne fais pas :
console.log(lireLignes(flux));car lireLignes() est asynchrone. Le console.log() affichera juste une Promise.
Il suffit de laisser Commander appeler la fonction via :
.action(async (flux) => await lireLignes(flux));.