Dans le prolongement des autres réponses qui montrent comment on obtient un résultat différent avec les extrêmes des petits et des grands nombres, voici un exemple où la virgule flottante avec des nombres normaux réalistes donne une réponse différente.
Dans ce cas, au lieu d'utiliser des nombres aux limites extrêmes de la précision, je fais simplement beaucoup d'additions. La différence se situe entre (((...(((a+b)+c)+d)+e)...
o ...(((a+b)+(c+d))+((e+f)+(g+h)))+...
J'utilise ici python, mais vous obtiendrez probablement les mêmes résultats si vous écrivez en C#. Créez d'abord une liste d'un million de valeurs, toutes égales à 0,1. Additionnez-les en partant de la gauche et vous verrez que les erreurs d'arrondi deviennent significatives :
>>> numbers = [0.1]*1000000
>>> sum(numbers)
100000.00000133288
Ajoutez-les à nouveau, mais cette fois-ci par paires (il existe des méthodes beaucoup plus efficaces qui utilisent moins de stockage intermédiaire, mais je me suis contenté d'une mise en œuvre simple) :
>>> def pair_sum(numbers):
if len(numbers)==1:
return numbers[0]
if len(numbers)%2:
numbers.append(0)
return pair_sum([a+b for a,b in zip(numbers[::2], numbers[1::2])])
>>> pair_sum(numbers)
100000.0
Cette fois, les erreurs d'arrondi sont minimisées.
Editer pour être complet, voici une implémentation plus efficace mais moins facile à suivre d'une somme par paire. Elle donne la même réponse que la méthode pair_sum()
ci-dessus :
def pair_sum(seq):
tmp = []
for i,v in enumerate(seq):
if i&1:
tmp[-1] = tmp[-1] + v
i = i + 1
n = i & -i
while n > 2:
t = tmp.pop(-1)
tmp[-1] = tmp[-1] + t
n >>= 1
else:
tmp.append(v)
while len(tmp) > 1:
t = tmp.pop(-1)
tmp[-1] = tmp[-1] + t
return tmp[0]
Et voici le simple pair_sum écrit en C# :
using System;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static double pair_sum(double[] numbers)
{
if (numbers.Length==1)
{
return numbers[0];
}
var new_numbers = new double[(numbers.Length + 1) / 2];
for (var i = 0; i < numbers.Length - 1; i += 2) {
new_numbers[i / 2] = numbers[i] + numbers[i + 1];
}
if (numbers.Length%2 != 0)
{
new_numbers[new_numbers.Length - 1] = numbers[numbers.Length-1];
}
return pair_sum(new_numbers);
}
static void Main(string[] args)
{
var numbers = new double[1000000];
for (var i = 0; i < numbers.Length; i++) numbers[i] = 0.1;
Console.WriteLine(numbers.Sum());
Console.WriteLine(pair_sum(numbers));
}
}
}
avec sortie :
100000.000001333
100000
32 votes
Probablement des valeurs en virgule flottante...
7 votes
À titre d'information, ce problème n'est pas spécifique à C#. Les mathématiques à virgule flottante telles qu'elles sont mises en œuvre dans le matériel ne sont pas exactement associatives. En effet, il ne s'agit pas seulement de matériel, mais de toute implémentation en virgule flottante conforme à la norme IEEE 754. Si vous avez besoin de l'associativité pour des nombres réels, vous devez inventer votre propre format (et probablement embaucher un mathématicien dans le processus)
7 votes
@slebetman Tout système qui utilise un nombre fixe de chiffres significatifs n'est pas associatif. Si nous utilisons un seul chiffre significatif par exemple, en arrondissant après chaque opération, nous avons : (1 + 0,5) + 0,5 == 1,5 (arrondi à 2) + 0,5 == 2,5 (arrondi à 3) == 3, tandis que 1 + (0,5 + 0,5) == 1 + 1 == 2. IEEE 754 utilise simplement des chiffres binaires au lieu de chiffres décimaux avec un nombre fixe de chiffres significatifs (la mantisse).
0 votes
@slebetman : L'arithmétique rationnelle ferait l'affaire, pas besoin de mathématicien.
1 votes
@Mehrdad : le commentaire de xanatos ci-dessus montre pourquoi un mathématicien est nécessaire. Il s'avère que ce n'est pas possible, mais en tant qu'ingénieur, je ne le savais pas.
0 votes
@slebetman : Je pense que vous avez manqué le fait qu'il parlait d'un nombre fixe de chiffres alors que je ne le faisais pas. (L'arithmétique rationnelle signifie que vous représentez chaque nombre comme un rapport de deux entiers, qui peuvent être de précision arbitraire). Vous n'avez vraiment pas besoin d'un mathématicien pour cela...
0 votes
@Mehrdad Vous auriez besoin d'une arithmétique rationnelle avec des chiffres infinis (donc l'arithmétique rationnelle du "monde réel").
0 votes
@xanatos : Pas tout à fait -- c'est seulement si vous voulez aller au-delà de l'arithmétique de base (
+ - * /
), qui serait alors généralement une caractéristique de la bibliothèque plutôt qu'une caractéristique de la langue. Pour l'arithmétique de base dans le langage, ce n'est pas le cas. En fait, l'ensemble du point La représentation des nombres sous forme de rationnels permet de ne pas avoir à stocker un nombre infini de chiffres pour l'arithmétique de base, puisque les rationnels sont fermés dans le cadre de l'arithmétique de base. (Il s'agit d'une observation non triviale ; si elle n'est pas évidente à première vue, c'est normal).3 votes
@LuuVinhPhúc : Comment une question en C# peut-elle être un duplicata d'une question en C... ?
1 votes
Lire ceci Les mathématiques en virgule flottante sont-elles défectueuses ? Ce que tout informaticien doit savoir sur l'arithmétique à virgule flottante L'addition en virgule flottante n'est pas associative
3 votes
@Mehrdad cela est lié à la propriété de l'arithmétique à virgule flottante, qui ne dépend pas de la langue.
4 votes
@LuuVinhPhúc : ...quoi ? Ce n'est pas parce que la réponse (et la raison) est la même que la question est un doublon ! En outre, l'OP n'a même pas parlé de virgule flottante. Comment diable sa question pourrait-elle être un doublon d'une question sur la virgule flottante ? J'aimerais pouvoir rétrograder les commentaires...
2 votes
@Mehrdad : Si la réponse et la raison sont les mêmes, il s'agit d'un doublon, même si la question est formulée différemment. Le concept de doublons est destiné à ce que les questions formulées différemment mais ayant la même réponse renvoient à une seule et même réponse.
2 votes
@slebetman : Je pense que vous ne comprenez pas ce que signifie fermer en tant que "duplicate" ? La description sous la raison de la fermeture dit littéralement, "Cette question a déjà été posée ". La question à laquelle vous avez renvoyé est la suivante pas Il s'agit simplement d'une "formulation différente" de cette question. Il s'agit d'une question totalement différente, portant sur une langue très différente, qui a simplement la même réponse. Si le répondre est un doublon, il ne s'ensuit pas logiquement que "la question a déjà été posée".
2 votes
@Mehrdad : Il s'agit simplement d'un problème A/B. Cette question (pourquoi les additions en virgule flottante ne sont pas associatives) a déjà été posée.
1 votes
@Mehrdad : il a été discuté à plusieurs reprises sur Meta que si les réponses sont dupliquées, la question l'est aussi. C'est d'ailleurs tout l'intérêt des doublons : faire en sorte que plusieurs questions différentes renvoient à la même réponse. Le fait d'avoir plusieurs questions signifie que la réponse sera plus facile à trouver, parce que différentes personnes formulent la question différemment ou que la question apparaît dans différents contextes (par exemple, dans cet exemple, C et C#). Le fait d'avoir une seule réponse signifie que l'effort est concentré en un seul endroit et n'est pas dispersé dans le système.
1 votes
@Mehrdad Par chiffres infinis, je voulais dire chiffres non fixes... Si vous avez 8 bits pour le dénominateur, donc 0-255 (non signé), alors
1/2 + 1/255
ne peut pas être représenté exactement (le nouveau dénominateur serait 510, ce qui est en dehors de l'intervalle)... Il faut donc l'arrondir d'une manière ou d'une autre. Je ne suis pas sûr que cet arrondi (selon la manière dont il est effectué) puisse/veuille le rendre non associatif.0 votes
@JörgWMittag : Je vois. Auriez-vous un lien vers le Meta post ?
0 votes
@xanatos : Si par "infini" vous voulez dire "sans limite", alors je dois vous demander : avez-vous lire mon 2ème commentaire ? J'essayais d'expliquer à slebetman exactement la même chose que vous essayez maintenant de me faire comprendre lol.
2 votes
@Mehrdad Non, je ne l'avais pas fait... Nous disons la même chose...