8
votes

Pourquoi y a-t-il une différence d'arrondi entre mon exemple normal de récursions et de récursions de la queue?

en jouant avec un exemple de récursion de la queue, j'ai remarqué une faible divergence entre les résultats d'un appel récursif normal et un appel récursif de queue: xxx

juste hors de curiosité, peut-on expliquer à moi pourquoi ou où l'arrondi se passe. Je suppose que parce que le compilateur Scala traduit la version récursive de la queue à une boucle, le paramètre ACC est attribué à chaque itération de la boucle et que la petite erreur d'arrondi glisse là-bas.


1 commentaires

Dans un langage de programmation approprié, comme Scala est, attribuant un double résultat à une variable double n'introduit pas une erreur d'arrondi.


3 Réponses :


9
votes

Vos deux fonctions ne font pas les opérations dans le même ordre.

in c: xxx

impressions: xxx

(j'ai utilisé une plate-forme sur laquelle le point flottant en C fonctionne de manière prévisible)

une version de votre fonction calcule 30. * 29 * ... et l'autre Calcules 2. * 3 * ... . Il est normal que ces deux résultats soient légèrement différents: les opérations à virgule flottante ne sont pas associatives. Mais veuillez noter qu'il n'y a rien d'insondable sur les résultats. Une de vos fonctions calcule exactement l'expression de double précision IEEE 754 30. * 29 * ... et l'autre calcule exactement 2. * 3 * ... . Ils travaillent tous deux comme conçu.

Si je devais deviner, je m'attendrais à ce que 2. * 3 * ... est plus précis (plus proche du résultat obtenu avec des nombres réels ), mais peu importe: les deux chiffres sont très proches et très proches du résultat réel.


3 commentaires

+1. Chose étrange, mais je viens d'essayer la même chose en C #. Les deux implémentations renvoient exactement des résultats égaux.


Merci pour la grande comparaison avec C. J'ai suscité votre réponse, mais j'ai marqué le nouveau gars aussi correct puisqu'il a moins de représentant et est également correct. J'espère que ça va.


@JacobusR Great Idée. Pour la comparaison avec c, la cause réelle était que je n'ai pas eu de compilateur Scala à portée de main, mais si elle contribue à dissiper le mythe de l'imprévisibilité du point flottant, tant mieux :)



15
votes

Le résultat est différent car les deux versions font les multiplications dans un ordre différent, conduisant ainsi à différents arrondis.

L'appel récursif normal conduit à une expression n * ([n-1] * ([n-2] * (...))) , car vous calculez d'abord la valeur du fait (n-1) puis multipliez-le avec N, tandis que la queue récursive conduit à ((N * [n-1]) * [N-2]) * ... parce que vous avez d'abord multiplier par n puis itérer sur N-1.

Essayez de réécrire une des versions afin qu'elle itère l'inverse de l'autre sens et que vous devriez théoriquement, obtenir la même réponse.


0 commentaires

6
votes

La différence ne concerne pas le fait que Scala allume la récursion de la queue en boucle. Le résultat serait le même sans cette optimisation. La récursion n'agit également pas différemment en ce qui concerne les erreurs d'arrondi que les boucles.

La différence est l'ordre dans lequel les chiffres sont multipliés. Votre première solution recouvre jusqu'au bout jusqu'à 1 avant de commencer à multiplier les chiffres. Donc, il finira par calculer n * ((n-1) * (... * (2 * 1))) . La version récursive de la queue commence à se multiplier tout de suite, il finit donc à calculer n * (n-1) * ... * 2 * 1 . .

Bien sûr, nous dirions que ces deux sont identiques parce que la multiplication est associative, mais ce n'est pas vrai pour l'arithmétique à virgule flottante. Utilisation de points flottants (x * y) * z peut très bien être différent de x * (y * z) car les erreurs d'arrondi se propagent différemment. Donc cela explique votre comportement.

Notez que vous verrez la même différence lors de l'utilisation d'une boucle pour la boucle qui compte de 1 à n versus un qui compte de N à 1 pour mettre en œuvre la factorielle.


0 commentaires