Non, ce n’est pas une autre question “Pourquoi est (1 / 3.0) * 3! = 1” .
J’ai beaucoup lu sur les points flottants ces derniers temps. Plus précisément, comment un même calcul peut donner des résultats différents sur différentes architectures ou parameters d’optimisation.
Ceci est un problème pour les jeux vidéo qui stockent des replays ou qui sont en réseau peer-to-peer (par opposition à server-client), qui dépendent de tous les clients générant exactement les mêmes résultats à chaque exécution du programme. le calcul en virgule flottante peut conduire à un état de jeu radicalement différent sur différentes machines (ou même sur la même machine! )
Cela se produit même parmi les processeurs qui “suivent” IEEE-754 , principalement parce que certains processeurs (à savoir x86) utilisent la précision double étendue . C’est-à-dire qu’ils utilisent des registres de 80 bits pour effectuer tous les calculs, puis tronquent à 64 ou 32 bits, ce qui conduit à des résultats d’arrondi différents de ceux des machines utilisant 64 ou 32 bits pour les calculs.
J’ai vu plusieurs solutions à ce problème en ligne, mais toutes pour C ++, pas C #:
double
calculs double
utilisent 64 bits IEEE-754) en utilisant _controlfp_s
(Windows), _FPU_SETCW
(Linux?) Ou fpsetprec
(BSD). float
et les double
. decimal
fonctionnerait à cette fin, mais serait beaucoup plus lent, et aucune des fonctions de la bibliothèque System.Math
ne le prend en charge. Donc, est-ce même un problème en C #? Que se passe-t-il si je ne souhaite que supporter Windows (pas Mono)?
Si c’est le cas, existe-t-il un moyen de forcer mon programme à fonctionner à une double précision normale?
Si ce n’est pas le cas, existe-t-il des bibliothèques permettant de maintenir la cohérence des calculs en virgule flottante?
Je ne connais aucun moyen de rendre les points flottants normaux déterministes dans .net. JITter est autorisé à créer un code qui se comporte différemment sur différentes plates-formes (ou entre différentes versions de .net). Donc, il n’est pas possible d’utiliser des float
normaux dans un code .net déterministe.
Les solutions de contournement que j’ai considérées:
Je viens de commencer une implémentation logicielle de calcul 32 bits en virgule flottante. Il peut faire environ 70 millions d’additions / multiplications par seconde sur mon i3 de 2,66 GHz. https://github.com/CodesInChaos/SoftFloat . Evidemment c’est toujours très incomplet et buggy.
La spécification C # (§ 4.1.6 Types de virgule flottante) permet spécifiquement d’effectuer des calculs en virgule flottante avec une précision supérieure à celle du résultat. Donc, non, je ne pense pas que vous pouvez rendre ces calculs déterministes directement dans .Net. D’autres ont suggéré diverses solutions de contournement pour pouvoir les essayer.
La page suivante peut être utile dans le cas où vous avez besoin d’une portabilité absolue de telles opérations. Il aborde les logiciels permettant de tester les implémentations de la norme IEEE 754, notamment les logiciels permettant d’émuler les opérations à virgule flottante. La plupart des informations sont probablement spécifiques à C ou C ++.
http://www.math.utah.edu/~beebe/software/ieee/
Une note sur le point fixe
Les nombres binarys à virgule fixe peuvent également servir de substitut à la virgule flottante, comme en témoignent les quatre opérations arithmétiques de base:
Les nombres binarys à virgule fixe peuvent être implémentés sur tout type de données entier tel que int, long et BigInteger, ainsi que les types non-compatibles CLS uint et ulong.
Comme suggéré dans une autre réponse, vous pouvez utiliser des tables de consultation, où chaque élément de la table est un nombre à virgule fixe, pour vous aider à implémenter des fonctions complexes telles que sinus, cosinus, racine carrée, etc. Si la table de consultation est moins granulaire que le nombre à virgule fixe, il est conseillé de arrondir l’entrée en ajoutant la moitié de la granularité de la table de recherche à l’entrée:
// Assume each number has a 12 bit fractional part. (1/4096) // Each entry in the lookup table corresponds to a fixed point number // with an 8-bit fractional part (1/256) input+=(1<<3); // Add 2^3 for rounding purposes input>>=4; // Shift right by 4 (to get 8-bit fractional part) // --- clamp or ressortingct input here -- // Look up value. return lookupTable[input];
Est-ce un problème pour C #?
Oui. Différentes architectures sont les moindres de vos soucis, différentes images-frameworks, etc. peuvent entraîner des écarts dus à des inexactitudes dans les représentations flottantes – même si elles sont identiques (par exemple, même architecture, sauf un GPU plus lent sur une machine).
Puis-je utiliser System.Decimal?
Il n’y a aucune raison pour laquelle vous ne pouvez pas, mais c’est un chien lent.
Est-il possible de forcer mon programme à fonctionner en double précision?
Oui. Hébergez vous-même le runtime CLR ; et comstackr tous les appels / indicateurs nessecary (qui modifient le comportement de l’arithmétique en virgule flottante) dans l’application C ++ avant d’appeler CorBindToRuntimeEx.
Existe-t-il des bibliothèques permettant de maintenir la cohérence des calculs en virgule flottante?
Pas que je sache de.
Y a-t-il une autre façon de résoudre ce problème?
J’ai déjà abordé ce problème, l’idée est d’utiliser QNumbers . Ils sont une forme de réels qui sont à virgule fixe; mais pas point fixe en base 10 (décimal) – plutôt base 2 (binary); de ce fait, les primitives mathématiques sur elles (add, sub, mul, div) sont beaucoup plus rapides que les points fixes de base-10 naïfs; surtout si n
est le même pour les deux valeurs (ce qui dans votre cas serait le même). De plus, du fait de leur intégrité, ils ont des résultats bien définis sur chaque plate-forme.
Gardez à l’esprit que le framerate peut toujours les affecter, mais il n’est pas aussi mauvais et il est facile de le corriger en utilisant des points de synchronisation.
Puis-je utiliser plus de fonctions mathématiques avec QNumbers?
Oui, faites un tour décimal pour faire cela. De plus, vous devriez vraiment utiliser des tables de consultation pour les fonctions sortingg (sin, cos); comme ceux-ci peuvent donner des résultats différents sur différentes plates-formes – et si vous les codez correctement, ils peuvent utiliser directement QNumbers.
Selon cette entrée de blog MSDN un peu ancienne, JIT n’utilisera pas SSE / SSE2 pour les virgules flottantes, mais x87. Pour cette raison, comme vous l’avez mentionné, vous devez vous préoccuper des modes et des indicateurs, et en C #, il est impossible de contrôler. L’utilisation des opérations à virgule flottante normales ne garantit donc pas le même résultat sur toutes les machines de votre programme.
Pour obtenir une reproductibilité précise de la double précision, vous devrez faire une émulation logicielle à virgule flottante (ou à virgule fixe). Je ne connais pas les bibliothèques C # pour ce faire.
Selon les opérations dont vous avez besoin, vous pourrez peut-être vous en sortir avec une seule précision. Voici l’idée:
Le gros problème avec x87 est que les calculs peuvent être effectués avec une précision de 53 bits ou 64 bits, en fonction de l’indicateur de précision et de la mémoire du registre. Mais pour de nombreuses opérations, effectuer l’opération en haute précision et arrondir à une précision inférieure garantira la réponse correcte, ce qui implique que la réponse sera garantie sur tous les systèmes. Que vous obteniez la précision supplémentaire n’aura aucune importance, puisque vous avez suffisamment de précision pour garantir la bonne réponse dans les deux cas.
Opérations qui devraient fonctionner dans ce schéma: addition, soustraction, multiplication, division, sqrt. Des choses comme le péché, l’exp, etc. ne fonctionneront pas (les résultats correspondent généralement mais il n’y a aucune garantie). “Quand le double arrondi est-il inoffensif?” Référence ACM (reg. Req. Payée)
J’espère que cela t’aides!
Comme déjà indiqué par d’autres réponses: Oui, c’est un problème en C # – même en restant sous Windows.
En ce qui concerne une solution: vous pouvez réduire (et avec un certain effort / performance) éviter complètement le problème si vous utilisez la classe BigInteger
et redimensionnez tous les calculs à une précision définie en utilisant un dénominateur commun pour tout calcul / stockage de ces nombres .
Comme demandé par OP – concernant les performances:
System.Decimal
représente le nombre avec 1 bit pour un signe et 96 bits Integer et une “échelle” (représentant où le point décimal est). Pour tous les calculs, vous devez utiliser cette structure de données et ne pouvez utiliser aucune instruction à virgule flottante intégrée à la CPU.
La “solution” de BigInteger
fait quelque chose de similaire – seulement que vous pouvez définir la quantité de chiffres dont vous avez besoin / que vous voulez … peut-être que vous ne voulez que 80 bits ou 240 bits de précision.
La lenteur vient toujours de devoir simuler toutes les opérations sur ces nombres via des instructions uniquement entières sans utiliser les instructions intégrées à la CPU / FPU, ce qui conduit à beaucoup plus d’instructions par opération mathématique.
Pour réduire les performances, il existe plusieurs stratégies – comme QNumbers (voir la réponse de Jonathan Dickinson – Les mathématiques à virgule flottante sont-elles cohérentes en C #? Peut-il être? ) Et / ou la mise en cache (par exemple les calculs sortingg …)
Eh bien, voici ma première tentative pour y parvenir :
(Je crois que vous pouvez simplement comstackr en .dll 32 bits, puis l’utiliser avec x86 ou AnyCpu [ou ne cibler probablement que x86 sur un système 64 bits; voir commentaire ci-dessous].)
Puis, en supposant que cela fonctionne, si vous voulez utiliser Mono, j’imagine que vous devriez être capable de répliquer la bibliothèque sur d’autres plates-formes x86 de manière similaire (pas COM bien sûr, bien que peut-être avec wine? nous y allons cependant …).
En supposant que vous puissiez le faire fonctionner, vous devriez être capable de configurer des fonctions personnalisées capables d’effectuer plusieurs opérations en même temps pour résoudre les problèmes de performances, et vous obtiendrez des résultats cohérents sur plusieurs plates-formes avec une quantité minimale du code écrit en C ++, et en laissant le rest de votre code en C #.
Je ne suis pas un développeur de jeux, même si j’ai beaucoup d’expérience avec des problèmes informatiques difficiles, alors je ferai de mon mieux.
La stratégie que j’adopterais est essentiellement celle-ci:
En bref: vous devez trouver un équilibre. Si vous passez 30ms de rendu (~ 33fps) et seulement 1ms de détection de collision (ou insérez une autre opération très sensible) – même si vous sortinglez le temps qu’il faut pour effectuer l’arithmétique critique, l’impact sur votre fréquence d’images est vous tombez de 33.3fps à 30.3fps.
Je vous suggère de tout profiler, comptez combien de temps est passé pour chacun des calculs sensiblement coûteux, puis répétez les mesures avec une ou plusieurs méthodes de résolution de ce problème et voyez l’impact.
En vérifiant les liens dans les autres réponses, vous avez la certitude de ne jamais savoir si un virgule flottante est “correctement” implémentée ou si vous obtenez toujours une certaine précision pour un calcul donné. (1) tronquer tous les calculs à un minimum commun (par exemple, si différentes implémentations vous donneront une précision de 32 à 80 bits, en tronquant toujours chaque opération à 30 ou 31 bits), (2) (cas limites de add, soustraire, multiplier, diviser, sqrt, cosinus, etc.) et si l’implémentation calcule des valeurs correspondant à la table, alors il ne faut pas faire de réglages.
Votre question est assez difficile et technique O_o. Cependant, je peux avoir une idée.
Vous savez certainement que le processeur effectue des ajustements après toute opération flottante. Et CPU offre plusieurs instructions différentes qui font des opérations d’arrondi différentes.
Donc, pour une expression, votre compilateur choisira un ensemble d’instructions qui vous mènera à un résultat. Mais tout autre stream de travail d’instruction, même s’il a l’intention de calculer la même expression, peut fournir un autre résultat.
Les «erreurs» d’un ajustement d’arrondi augmenteront à chaque nouvelle instruction.
A titre d’exemple, on peut dire qu’au niveau de l’assemblage: a * b * c n’est pas équivalent à a * c * b.
Je ne suis pas tout à fait sûr de cela, vous devrez demander à quelqu’un qui connaît l’architecture du processeur beaucoup plus que moi: p
Cependant, pour répondre à votre question: en C ou C ++, vous pouvez résoudre votre problème car vous avez un certain contrôle sur le code machine généré par votre compilateur, mais dans .NET, vous n’en avez aucun. Donc, tant que votre code machine peut être différent, vous ne serez jamais sûr du résultat exact.
Je suis curieux de savoir comment cela peut être un problème car la variation semble très minime, mais si vous avez besoin d’un fonctionnement vraiment précis, la seule solution à mon avis sera d’augmenter la taille de vos registres flottants. Utilisez la double précision ou même le double long si vous le pouvez (pas sûr que ce soit possible avec l’interface de ligne de commande).
J’espère que j’ai été assez clair, je ne suis pas parfait en anglais (… du tout: s)