🪙2025.11.04
simulation
probabilités
Dans le cours de mathématiques, on utilise le Paradoxe de Saint-Peterbourg comme exemple de calcul de fonction de masse et d’espérance d’une variable aléatoire.
Le paradoxe tourne autour d’un jeu qui se déroule ainsi:
- le joueur mise une certaine somme.
- il fait ensuite une série de jets de pile ou face.
- le jeu s’arrête au premier “pile”.
- le joueur gagne alors 2n euros, où n est le nombre de “faces” obtenues avant d’avoir un “pile”.
Donc si, par exemple, j’ai la série: Face-Face-Pile, le jeu s’arrête après trois lancers et je reçois 22 = 4 euros.
Le paradoxe réside dans le fait que, théoriquement, l’espérance des gains obtenus est infinie. Pourtant, peu de joueurs seraient prêts à miser une grosse somme, vu que les chances de gagner plus qu’une poignée d’euros sont extrêmement faibles.
Notre cours de mathématique ne s’intéresse pas trop au cas infini. On ajoute donc une règle supplémentaire: si au bout de 10 lancers on n’a obtenu que des faces, le joueur gagne 210 = 1024 euros et la partie s’arrête aussi. Les gains ne sont plus infinis, mais le “paradoxe” reste présent. L’espérance du gain dans cette configuration est de 6 euros, mais les chances pour un joueur de gagner de l’argent en misant 6 euros sont faibles (12.5%, pour être précis).
On peut calculer toutes ces valeurs, mais dès qu’on fait des probabilités, j’aime bien simuler les choses, pour obtenir une bonne intuition de ce qui se passe. En plus, comme on enseigne à des futurs développeurs, approcher une solution via une simulation est une compétence qui semble intéressante à mettre en avant.
Le simulateur permet de voir comment l’espérance change avec le nombre maximal de lancers autorisé, et comment la précision de notre estimation de l’espérance s’améliore avec le nombre de parties simulées.
Pour que tout ça soit accessible sur une page web, on va faire un peu de Javascript. Commençons donc par la simulation d’une partie:
function jeuSaintPetersbourg(max_n) {
let n = 1;
while (Math.random() < 0.5) {
if (n === max_n) return 2**n;
n++;
}
return 2**(n-1);
}On utilise pour “pile ou face” un test
Math.random() < 0.5, et on considère ici que
true représente face, et false
représente pile. La fonction renvoie le gain: si on atteint le
maximum de lancers, on renvoie 2n, sinon on
s’arrête dès qu’on tombe sur pile et on renvoie 2n − 1.
Pour estimer une statistique via une simulation, il faut pouvoir relancer l’expérience beaucoup de fois. On va donc faire ça, en collectant la distribution des résultats:
function lancerSimulation(){
// ... récupération d'inputs
const resultats = {};
for (let i = 0; i <= maxLancers; i++ ){
resultats[2**i] = 0
}
let total = 0;
for (let i = 0; i < numJeux; i++) {
const gains = jeuSaintPetersbourg(maxLancers);
resultats[gains]++;
total += gains;
}
const moyenne = (total / numJeux).toFixed(2);
// ... affichage des résultats
}maxLancers et numJeux sont
récupérés dans des <input>. On initialise le
tableau des résultats avec tous les gains possibles, puis on
lance numJeux parties, et on collecte les gains
obtenus. On calcule notre estimation de l’espérance en faisant
la moyenne des gains. L’affichage se fait via un diagramme en
barres fait dans un <canvas>.
Il est clair que miser beaucoup sur une partie n’est pas très avantageux. Mais si on peut miser un peu sur beaucoup de parties, par contre, la dynamique change. C’est la deuxième partie de la simulation: que ce passe-t-il si on peut rejouer?
On va maintenant calculer le total des gains si on joue une série de parties, chacune avec une mise fixe:
function totalSerie(numJeux, prix) {
let total = 0;
for(let i = 0; i < numJeux; i++){
res = jeuSaintPetersbourg(10);
total += res - prix;
}
return total;
}Et on va cette fois-ci dans notre simulation regarder une variable aléatoire binaire: est-ce qu’on gagne de l’argent à la fin? (bon, en vrai je l’ai faite ternaire parce que j’ai séparé le cas où on récupérait exactement notre mise).
function lancerSerieJeux(){
// ... récupération d'inputs
let partiesGagnees = 0;
let partiesPerdues = 0;
let partiesEgales = 0;
for( let i = 0; i < numSimulations; i++ ){
gains = totalSerie(numJeux, prix);
if(gains > 0){
partiesGagnees += 1;
}
else if( gains < 0){
partiesPerdues += 1;
}
else{
partiesEgales += 1;
}
}
// ... affichage des résultats
}En jouant avec le simulateur, on peut ainsi facilement voir que si on mise une fois 1000 euros, on va vraisemblablement les perdre (0.1% de victoires). Si par contre je joue 200 parties à 5 euros, j’ai plus d’une chance sur deux de sortir vainqueur de l’échange. Enfin, sauf si on compte le temps perdu à jeter la pièce.