Sur le traitement des nombres à virgule flottante de manière déterministe
La virgule flottante est déterministe. Eh bien, ça devrait l'être. C'est compliqué.
Il y a beaucoup de littérature sur les nombres à virgule flottante:
Et comment ils sont problématiques:
Pour résumé. Au moins, sur un seul thread, les mêmes opérations, avec les mêmes données, se déroulant dans le même ordre, devraient être déterministes. Ainsi, nous pouvons commencer par nous préoccuper des entrées et de la réorganisation.
Le temps est l’une des choses qui posent problème.
Tout d’abord, vous devez toujours calculer le même pas de temps. Je ne dis pas de ne pas mesurer le temps, je dis que vous ne passerez pas de temps à la simulation physique, car les variations dans le temps sont une source de bruit dans la simulation.
Pourquoi mesurez-vous le temps si vous ne le passez pas à la simulation physique? Vous souhaitez mesurer le temps écoulé pour savoir quand une étape de simulation doit être appelée et, en supposant que vous utilisiez le sommeil, combien de temps pour dormir.
Ainsi:
- Temps de mesure: oui
- Utiliser le temps en simulation: Non
Maintenant, réorganiser les instructions.
Le compilateur peut décider que f * a + b
c'est la même chose b + f * a
, mais que le résultat peut être différent. Il pourrait également compiler pour fmadd , ou décider de prendre plusieurs lignes comme celles-ci et de les écrire avec SIMD , ou une autre optimisation à laquelle je ne peux pas penser pour le moment. Et rappelez-vous que nous voulons que les mêmes opérations se déroulent dans le même ordre. Il est donc logique de vouloir contrôler quelles opérations ont lieu.
Et non, l'utilisation de double ne vous sauvera pas.
Vous devez vous préoccuper du compilateur et de sa configuration, en particulier pour synchroniser les nombres à virgule flottante sur le réseau. Vous devez obtenir que les versions acceptent de faire la même chose.
On pourrait soutenir que l'écriture d'assemblage serait idéale. De cette façon, vous décidez quelle opération faire. Cependant, cela pourrait poser un problème pour prendre en charge plusieurs plates-formes.
Ainsi:
Le cas des nombres à virgule fixe
En raison de la manière dont les flottants sont représentés en mémoire, les grandes valeurs vont perdre de la précision. Il va de soi que le fait de garder vos valeurs faibles (clamp) atténue le problème. Ainsi, pas de vitesses énormes et pas de grandes salles. Ce qui signifie également que vous pouvez utiliser la physique discrète car vous avez moins de risque de tunneling.
D'autre part, de petites erreurs vont s'accumuler. Donc, tronquer. Je veux dire, redimensionnez et convertissez en un type entier. De cette façon, vous savez que rien ne se construit. Il y aura des opérations que vous pouvez faire en restant avec le type entier. Lorsque vous devez revenir en virgule flottante, vous lancez et annulez la mise à l'échelle.
Notez que je dis l'échelle. L'idée est que 1 unité sera réellement représentée par une puissance de deux (16384 par exemple). Quoi que ce soit, faites-en une constante et utilisez-la. Vous l'utilisez essentiellement comme un nombre à virgule fixe. En fait, si vous pouvez utiliser beaucoup mieux les nombres à virgule fixe appropriés d'une bibliothèque fiable.
Je dis tronqué. À propos du problème d'arrondi, cela signifie que vous ne pouvez pas faire confiance au dernier bit de la valeur que vous avez obtenue après la distribution. Donc, avant la distribution, obtenez un peu plus que nécessaire et tronquez-le par la suite.
Ainsi:
- Gardez les valeurs petites: Oui
- Arrondissement prudent: oui
- Numéros de points fixes lorsque possible: Oui
Attends, pourquoi as-tu besoin de virgule flottante? Ne pourriez-vous pas travailler uniquement avec un type entier? Oh, c'est vrai. Trigonométrie et radication. Vous pouvez calculer des tables pour la trigonométrie et le rayonnement et les faire cuire dans votre source. Vous pouvez également implémenter les algorithmes utilisés pour les calculer avec un nombre à virgule flottante, à l'exception de l'utilisation de nombres à virgule fixe. Oui, vous devez équilibrer la mémoire, les performances et la précision. Pourtant, vous pouvez rester en dehors des nombres en virgule flottante et rester déterministe.
Saviez-vous qu'ils ont fait ce genre de choses pour la PlayStation d'origine? S'il vous plaît rencontrer mon chien, des correctifs .
En passant, je ne dis pas de ne pas utiliser de virgule flottante pour les graphiques. Juste pour la physique. Je veux dire, bien sûr, les positions dépendront de la physique. Cependant, comme vous le savez, un collisionneur ne doit pas nécessairement correspondre à un modèle. Nous ne voulons pas voir les résultats de la troncature des modèles.
Ainsi: UTILISEZ DES NUMÉROS DE POINTS FIXES.
Pour être clair, si vous pouvez utiliser un compilateur qui vous permet de spécifier le fonctionnement des points flottants et que cela vous suffit, vous pouvez le faire. Ce n'est pas toujours une option. De plus, nous le faisons pour le déterminisme. Les nombres à point fixe ne signifient pas qu’il n’ya pas d’erreurs, après tout, leur précision est limitée.
Je ne pense pas que "nombre de points fixe sont difficiles" est une bonne raison de ne pas les utiliser. Et si vous voulez une bonne raison de les utiliser, c'est le déterminisme, en particulier le déterminisme sur toutes les plateformes.
Voir également:
Addendum : Je suggère de garder la taille du monde petite. Cela dit, les deux OP et Jibb Smart soulignent le fait que s’éloigner des flotteurs d’origine ont moins de précision. Cela aura un effet sur la physique, un phénomène qui sera vu beaucoup plus tôt que les confins du monde. Les nombres de points fixes, ainsi, ont une précision fixe, ils seront également bons (ou mauvais, si vous préférez) partout. Ce qui est bon si nous voulons le déterminisme. Je tiens également à mentionner que la manière dont nous pratiquons habituellement la physique a la propriété d’amplifier de petites variations. Voir L'effet papillon - Physique déterministe dans The Incredible Machine and Maker .
Une autre façon de faire de la physique
Je pensais que la petite erreur de précision dans les nombres en virgule flottante s’expliquait parce que nous effectuons des itérations sur ces nombres. À chaque étape de la simulation, nous prenons les résultats de la dernière étape de la simulation et les remplaçons. Cumul d'erreurs après sommet d'erreurs. C'est ton effet papillon.
Je ne pense pas que nous verrons une seule construction utilisant un seul thread sur la même machine donner une sortie différente par la même entrée. Pourtant, sur une autre machine, cela pourrait ou une construction différente.
Il y a un argument pour tester là-bas. Si nous décidons exactement comment les choses devraient fonctionner et que nous pouvons tester sur du matériel cible, nous ne devrions pas proposer de versions ayant un comportement différent.
Cependant, il y a aussi un argument pour ne pas travailler ailleurs qui accumule tant d'erreurs. C'est peut-être une occasion de faire de la physique d'une manière différente.
Comme vous le savez peut-être, il existe une physique continue et discrète, les deux travaillent sur la progression de chaque objet sur le pas de temps. Cependant, la physique continue a le moyen de déterminer le moment de la collision au lieu de sonder différents instants possibles pour voir si une collision s'est produite.
Ainsi, je propose ce qui suit: utilisez les techniques de la physique continue pour déterminer quand la prochaine collision de chaque objet se produira, avec un pas de temps important, beaucoup plus important que celui d’une seule étape de simulation. Ensuite, prenez l’instant de collision le plus proche et déterminez où tout se trouvera à cet instant.
Oui, cela représente beaucoup de travail d’une seule étape de simulation. Cela signifie que la simulation ne démarrera pas instantanément ...
... Cependant, vous pouvez simuler les prochaines étapes de la simulation sans vérifier chaque fois la collision, car vous savez déjà quand la prochaine collision se produira (ou qu'aucune collision ne se produira dans le grand intervalle de temps). De plus, les erreurs accumulées dans cette simulation sont sans importance car une fois que la simulation a atteint le pas de temps important, nous ne faisons que placer les positions que nous avons calculées auparavant.
Nous pouvons maintenant utiliser le budget-temps que nous aurions utilisé pour vérifier les collisions à chaque étape de la simulation afin de calculer la collision suivante après celle trouvée. C’est-à-dire que nous pouvons simuler l’avance en utilisant le grand pas de temps. En supposant un monde de portée limitée (cela ne fonctionnera pas pour des jeux énormes), il devrait y avoir une file d’états futurs pour la simulation, puis chaque image que vous venez d’interpoler du dernier état à l’autre.
Je plaiderais pour une interpolation. Cependant, étant donné qu'il y a des accélérations, nous ne pouvons pas simplement tout interpoler de la même manière. Au lieu de cela, nous devons interpoler en prenant en compte l'accélération de chaque objet. D'ailleurs, nous pourrions simplement mettre à jour la position de la même manière que nous le faisons pour le grand pas de temps (ce qui signifie également qu'il est moins sujet aux erreurs parce que nous n'utiliserions pas deux implémentations différentes pour le même mouvement).
Remarque : Si nous faisons ces nombres à virgule flottante, cette approche ne résout pas le problème des objets se comportant différemment à mesure qu'ils s'éloignent de l'origine. Cependant, s'il est vrai que la précision est perdue au fur et à mesure que l'on s'éloigne de l'origine, cela reste déterministe. En fait, c’est la raison pour laquelle cela n’a même pas été abordé à l’origine.
Addenda
De OP en commentaire :
L'idée est que les joueurs pourront sauvegarder leurs machines dans un format quelconque (tel que xml ou json), de manière à enregistrer la position et la rotation de chaque morceau. Ce fichier xml ou json sera ensuite utilisé pour reproduire la machine sur l’ordinateur d’un autre joueur.
Donc, pas de format binaire, non? Cela signifie que nous devons également nous inquiéter de savoir si les nombres en virgule flottante récupérés correspondent à l'original. Voir: La précision du flotteur revisitée: portabilité du flotteur à neuf chiffres