Les mathématiques en virgule flottante sont-elles cohérentes en C #? Peut-il être?


155

Non, ce n'est pas une autre question "Pourquoi est (1 / 3.0) * 3! = 1" .

J'ai beaucoup lu sur les virgules flottantes ces derniers temps; plus précisément, comment le même calcul peut donner des résultats différents sur différentes architectures ou paramètres d'optimisation.

C'est un problème pour les jeux vidéo qui stockent des rediffusions, ou qui sont en réseau peer-to-peer (par opposition au serveur-client), qui reposent sur tous les clients générant exactement les mêmes résultats à chaque fois qu'ils exécutent le programme - une petite différence dans un. 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 (notamment x86) utilisent une double précision étendue . Autrement dit, 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 qui utilisent 64 ou 32 bits pour les calculs.

J'ai vu plusieurs solutions à ce problème en ligne, mais toutes pour C ++, pas C #:

  • Désactivez le mode double précision étendue (afin que tous les doublecalculs utilisent IEEE-754 64 bits) à l'aide de _controlfp_s(Windows), _FPU_SETCW(Linux?) Ou fpsetprec(BSD).
  • Exécutez toujours le même compilateur avec les mêmes paramètres d'optimisation et exigez que tous les utilisateurs aient la même architecture de processeur (pas de jeu multiplateforme). Parce que mon "compilateur" est en fait le JIT, qui peut s'optimiser différemment chaque fois que le programme est exécuté , je ne pense pas que cela soit possible.
  • Utilisez l'arithmétique à virgule fixe, et évitez floatet doubletout à fait. decimalfonctionnerait dans ce but, mais serait beaucoup plus lent, et aucune des System.Mathfonctions de la bibliothèque ne le prend en charge.

Alors, est-ce même un problème en C #? Que faire si je compte uniquement prendre en charge Windows (et non Mono)?

Si tel est le cas, existe-t-il un moyen de forcer mon programme à s'exécuter en double précision normale?

Sinon, existe-t-il des bibliothèques qui permettraient de maintenir la cohérence des calculs en virgule flottante?


J'ai vu cette question , mais chaque réponse répète le problème sans solution ou dit «ignorez-le», ce qui n'est pas une option. J'ai posé une question similaire sur gamedev , mais (à cause du public) la plupart des réponses semblent être orientées vers C ++.
BlueRaja - Danny Pflughoeft

1
pas une réponse, mais je suis sûr que dans la plupart des domaines, vous pouvez concevoir votre système de telle sorte que tout état partagé soit déterministe, et il n'y a pas de dégradation significative des performances à cause de cela
driushkin

1
@Peter connaissez-vous une émulation de virgule flottante rapide pour .net?
CodesInChaos

1
Java souffre-t-il de ce problème?
Josh

3
@Josh: Java a le strictfpmot - clé, qui force tous les calculs à être effectués dans la taille indiquée ( floatou double) plutôt que dans une taille étendue. Cependant, Java a encore de nombreux problèmes avec le support IEE-754. Très (très, très) peu de langages de programmation prennent bien en charge IEE-754.
porges

Réponses:


52

Je ne connais aucun moyen de rendre les virgules flottantes normales déterministes dans .net. Le JITter est autorisé à créer du code qui se comporte différemment sur différentes plates-formes (ou entre différentes versions de .net). Il floatn'est donc pas possible d' utiliser des s normaux dans un code .net déterministe.

Les solutions de contournement que j'ai envisagées:

  1. Implémentez FixedPoint32 en C #. Bien que ce ne soit pas trop difficile (j'ai une implémentation à moitié terminée), la très petite plage de valeurs rend son utilisation ennuyeuse. Il faut être prudent en tout temps pour ne pas déborder, ni perdre trop de précision. En fin de compte, je n'ai pas trouvé cela plus facile que d'utiliser directement des entiers.
  2. Implémentez FixedPoint64 en C #. J'ai trouvé cela assez difficile à faire. Pour certaines opérations, des entiers intermédiaires de 128 bits seraient utiles. Mais .net n'offre pas un tel type.
  3. Implémentez un flottant 32 bits personnalisé. L'absence d'un BitScanReverse intrinsèque provoque quelques ennuis lors de sa mise en œuvre. Mais actuellement, je pense que c'est la voie la plus prometteuse.
  4. Utilisez du code natif pour les opérations mathématiques. Entraîne la surcharge d'un appel de délégué sur chaque opération mathématique.

Je viens de commencer une implémentation logicielle de mathématiques en virgule flottante 32 bits. Il peut faire environ 70 millions d'ajouts / multiplications par seconde sur mon i3 à 2,66 GHz. https://github.com/CodesInChaos/SoftFloat . De toute évidence, il est encore très incomplet et bogué.


2
il y a un entier de taille "illimitée" disponible BigInteger mais pas aussi rapide que natif int ou long, donc .NET offre un tel type (créé pour F # je crois mais peut être utilisé en C #)
Rune FS

Une autre option est le wrapper GNU MP pour .NET . C'est un wrapper autour de la bibliothèque GNU Multiple Precision qui prend en charge les entiers de précision "infinis", les rationnels (fractions) et les nombres à virgule flottante.
Cole Johnson

2
Si vous comptez faire l'une de ces decimalchoses, autant essayer d' abord, car c'est beaucoup plus simple à faire. Ce n'est que s'il est trop lent pour la tâche à accomplir que d'autres approches mériteraient d'être envisagées.
Roman Starkov

J'ai découvert un cas particulier où les virgules flottantes sont déterministes. L'explication que j'ai obtenue est la suivante: pour la multiplication / division, si l'un des nombres FP est la puissance de deux nombres (2 ^ x), significatif / mantisse ne changera pas pendant le calcul. Seul l'exposant changera (le point se déplacera). L'arrondi ne se produira donc jamais. Le résultat sera déterministe.
zigzag le

Exemple: Un nombre comme 2 ^ 32 est représenté par (exposant: 32, mantisse: 1). Si nous multiplions cela avec un autre float (exp, man), le résultat est (exp + 32, man * 1). Pour la division, le résultat est (expo - 32, man * 1). Multiplier la mantisse par 1 ne change pas la mantisse, donc peu importe le nombre de bits dont elle dispose.
zigzag le

28

La spécification C # (§4.1.6 Types à 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 puissiez faire ces calculs déterministes directement dans .Net. D'autres ont suggéré diverses solutions de contournement, vous pouvez donc les essayer.


9
Je viens de réaliser que la spécification C # n'a pas vraiment d'importance si l'on distribue des assemblys compilés. Cela n'a d'importance que si l'on veut la compatibilité des sources. Ce qui compte vraiment, c'est la spécification CLR. Mais je suis à peu près sûr que ses garanties sont tout aussi faibles que les garanties C #.
CodesInChaos

Est-ce que le cast à doublechaque fois après une opération ne supprimerait pas les bits indésirables, donnant des résultats cohérents?
IllidanS4 veut que Monica revienne le

2
@ IllidanS4 Je ne pense pas que cela garantirait des résultats cohérents.
svick

14

La page suivante peut être utile dans le cas où vous avez besoin d'une portabilité absolue de telles opérations. Il traite des logiciels pour tester les implémentations de la norme IEEE 754, y compris des logiciels pour émuler les opérations en virgule flottante. Cependant, la plupart des informations sont probablement spécifiques au C ou au C ++.

http://www.math.utah.edu/~beebe/software/ieee/

Une note sur le point fixe

Les nombres binaires à virgule fixe peuvent également fonctionner comme un substitut à virgule flottante, comme le montrent les quatre opérations arithmétiques de base:

  • L'addition et la soustraction sont triviales. Ils fonctionnent de la même manière que les entiers. Ajoutez ou soustrayez!
  • Pour multiplier deux nombres à virgule fixe, multipliez les deux nombres puis décalez à droite le nombre défini de bits fractionnaires.
  • Pour diviser deux nombres à virgule fixe, décalez le dividende vers la gauche du nombre défini de bits fractionnaires, puis divisez par le diviseur.
  • Le chapitre quatre de cet article contient des conseils supplémentaires sur la mise en œuvre des nombres à virgule fixe binaires.

Les nombres à virgule fixe binaires peuvent être implémentés sur n'importe quel type de données entier tel que int, long et BigInteger, et les types non conformes à CLS uint et ulong.

Comme suggéré dans une autre réponse, vous pouvez utiliser des tables de recherche, où chaque élément de la table est un nombre à virgule fixe binaire, pour aider à implémenter des fonctions complexes telles que sinus, cosinus, racine carrée, etc. Si la table de recherche est moins granulaire que le nombre à virgule fixe, il est suggéré d'arrondir l'entrée en ajoutant la moitié de la granularité de la table de consultation à 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 restrict input here --
// Look up value.
return lookupTable[input];

5
Vous devez télécharger ceci sur un site de projet de code open source, comme sourceforge ou github. Cela rend plus facile à trouver, plus facile à contribuer, plus facile à mettre sur votre CV etc. Aussi, quelques astuces de code source (n'hésitez pas à ignorer): Utilisez constplutôt que staticpour les constantes, afin que le compilateur puisse les optimiser; préférez les fonctions membres aux fonctions statiques (afin que nous puissions appeler, par exemple myDouble.LeadingZeros()au lieu de IntDouble.LeadingZeros(myDouble)); essayez d'éviter les noms de variables à une seule lettre ( MultiplyAnyLengthpar exemple, a 9, ce qui le rend très difficile à suivre)
BlueRaja - Danny Pflughoeft

Soyez prudent en utilisant uncheckedet types non conformes CLS comme ulong, uint, etc. à des fins de vitesse - parce qu'ils sont si rarement utilisés, le JIT ne les optimiser pas aussi agressive, donc les utiliser peut effectivement être plus lent que d' utiliser des types normaux comme longet int. En outre, C # a une surcharge d'opérateurs , dont ce projet bénéficierait grandement. Enfin, y a-t-il des tests unitaires associés? Outre ces petites choses, travail incroyable Peter, c'est ridiculement impressionnant!
BlueRaja - Danny Pflughoeft

Merci pour les commentaires. J'effectue des tests unitaires sur le code. Ils sont plutôt étendus, cependant, beaucoup trop étendus pour être publiés pour l'instant. J'écris même des routines d'aide aux tests unitaires pour faciliter l'écriture de plusieurs tests. Je n'utilise pas d'opérateurs surchargés pour l'instant car j'ai l'intention de traduire le code en Java lorsque j'aurai terminé.
Peter O.

2
Le plus drôle, c'est que lorsque j'ai publié sur votre blog, je n'ai pas remarqué que ce blog était le vôtre. Je venais de décider d'essayer google + et dans son étincelle C #, il suggérait cette entrée de blog. Alors j'ai pensé "Quelle coïncidence remarquable pour nous deux de commencer à écrire une telle chose en même temps". Mais bien sûr, nous avons eu le même déclencheur :)
CodesInChaos

1
Pourquoi se soucier de porter ceci sur Java? Java a déjà garanti des calculs en virgule flottante déterministes via strictfp.
Antimoine

9

Est-ce un problème pour C #?

Oui. Différentes architectures sont le moindre de vos soucis, des fréquences d'images différentes, etc. peuvent entraîner des écarts dus à des inexactitudes dans les représentations flottantes - même s'il s'agit des mêmes inexactitudes (par exemple, même architecture, sauf un GPU plus lent sur une machine).

Puis-je utiliser System.Decimal?

Il n'y a aucune raison que vous ne puissiez pas, mais c'est un chien lent.

Existe-t-il un moyen de forcer mon programme à s'exécuter en double précision?

Oui. Hébergez vous-même le moteur d'exécution CLR ; et compilez tous les appels / indicateurs nécessaires (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 qui permettraient de maintenir la cohérence des calculs en virgule flottante?

Pas que je sache de.

Y a-t-il un autre moyen 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 en virgule fixe; mais pas de point fixe en base-10 (décimal) - plutôt en base-2 (binaire); à cause de cela, les primitives mathématiques sur eux (add, sub, mul, div) sont beaucoup plus rapides que les points fixes naïfs de base 10; surtout si nc'est la même chose pour les deux valeurs (ce qui serait dans votre cas). De plus, parce qu'ils font partie intégrante, ils ont des résultats bien définis sur chaque plateforme.

Gardez à l'esprit que le framerate peut toujours les affecter, mais il n'est pas aussi mauvais et est facilement corrigé à l'aide de points de synchronisation.

Puis-je utiliser plus de fonctions mathématiques avec QNumbers?

Oui, aller-retour d'une décimale pour ce faire. De plus, vous devriez vraiment utiliser des tables de recherche pour les fonctions trig (sin, cos); car ceux-ci peuvent vraiment donner des résultats différents sur différentes plates-formes - et si vous les codez correctement, ils peuvent utiliser directement QNumbers.


3
Je ne sais pas de quoi vous parlez avec les fréquences d'images en cause. De toute évidence, vous voudriez avoir un taux de mise à jour fixe (voir par exemple ici ) - que ce soit ou non le même que le taux de rafraîchissement d'affichage n'est pas pertinent. Tant que les inexactitudes sont les mêmes sur toutes les machines, nous sommes bons. Je ne comprends pas du tout votre troisième réponse.
BlueRaja - Danny Pflughoeft

@BlueRaja: La réponse "Y a-t-il un moyen de forcer mon programme à s'exécuter en double précision?" reviendrait soit à réimplémenter l'intégralité du Common Language Runtime, ce qui serait extrêmement compliqué, soit à utiliser des appels natifs à une DLL C ++ à partir de l'application C #, comme l'indique la réponse de l'utilisateur shelleybutterfly. Pensez aux "QNumbers" simplement comme des nombres binaires à virgule fixe, comme l'indique ma réponse (je n'avais jusqu'à présent pas vu de nombres binaires à virgule fixe appelés "QNumbers".)
Peter O.

@Pieter O. Vous n'avez pas besoin de réimplémenter le runtime. Le serveur sur lequel je travaille dans mon entreprise héberge le moteur d'exécution CLR en tant qu'application C ++ native (tout comme SQL Server). Je vous suggère google CorBindToRuntimeEx.
Jonathan Dickinson

@BlueRaja cela dépend du jeu en question. L'application d'étapes de fréquence d'images fixes à tous les jeux n'est pas une option viable - car l'algorithme AOE introduit une latence artificielle; ce qui est inacceptable par exemple dans un FPS.
Jonathan Dickinson

1
@Jonathan: Ce n'est un problème que dans les jeux peer-to-peer qui envoient uniquement l'entrée - pour ceux-ci, vous devez avoir un taux de mise à jour fixe. La plupart des FPS ne fonctionnent pas comme ça, mais les rares qui ont nécessairement un taux de mise à jour fixe. Voir cette question .
BlueRaja - Danny Pflughoeft

6

Selon cette entrée de blog MSDN légèrement ancienne, le JIT n'utilisera pas SSE / SSE2 pour la virgule flottante, tout est x87. Pour cette raison, comme vous l'avez mentionné, vous devez vous soucier des modes et des indicateurs, et en C #, ce n'est pas possible de contrôler. Ainsi, l'utilisation d'opérations normales en virgule flottante ne garantit pas exactement le même résultat sur chaque machine pour votre programme.

Pour obtenir une reproductibilité précise de la double précision, vous allez devoir faire une émulation logicielle en virgule flottante (ou virgule fixe). Je ne connais pas de bibliothèques C # pour faire cela.

Selon les opérations dont vous avez besoin, vous pourrez peut-être vous en sortir avec une seule précision. Voici l'idée:

  • stocker toutes les valeurs qui vous intéressent en une seule précision
  • pour effectuer une opération:
    • étendre les entrées pour doubler la précision
    • faire des opérations en double précision
    • convertir le résultat en simple précision

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 du fait que le registre s'est répandu en mémoire. 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 d'être la même sur tous les systèmes. Que vous obteniez la précision supplémentaire n'a pas d'importance, car 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 sin, 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!


2
C'est aussi un problème que .NET 5, 6 ou 42 n'utilise plus le mode de calcul x87. Il n'y a rien dans la norme qui l'exige.
Eric J.

5

Comme déjà indiqué par d'autres réponses: Oui, c'est un problème en C # - même en restant pur 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 une BigIntegerclasse intégrée et mettez à l' échelle 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.Decimalreprésente un nombre avec 1 bit pour un signe et 96 bits Integer et une "échelle" (représentant l'endroit où se trouve le point décimal). Pour tous les calculs que vous effectuez, il doit fonctionner sur cette structure de données et ne peut pas utiliser d'instructions à virgule flottante intégrées au CPU.

La BigInteger«solution» fait quelque chose de similaire - seulement que vous pouvez définir la quantité de chiffres dont vous avez besoin / 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 au CPU / FPU, ce qui conduit à son tour à beaucoup plus d'instructions par opération mathématique.

Pour réduire l'impact sur les performances, il existe plusieurs stratégies - comme QNumbers (voir la réponse de Jonathan Dickinson - Les mathématiques en virgule flottante sont-elles cohérentes en C #? ) Et / ou la mise en cache (par exemple, les calculs de trigonométrie ...) etc.


1
Notez que ce BigIntegern'est disponible que dans .Net 4.0.
svick

Je suppose que la performance de BigIntegerDecimal dépasse même la performance de Decimal.
CodesInChaos

Quelques fois dans les réponses ici, il y a une référence à la performance de l'utilisation de Decimal(@Jonathan Dickinson - 'dog slow') ou BigInteger(@CodeInChaos comment ci-dessus) - quelqu'un peut-il s'il vous plaît fournir une petite explication sur ces succès de performance et si / pourquoi ils sont vraiment des obstacles pour fournir une solution.
Barry Kaye

@Yahia - merci pour la modification - lecture intéressante, cependant, pourriez-vous s'il vous plaît simplement donner une estimation de la performance de la non-utilisation de `` float '' parlons-nous 10% plus lentement ou 10 fois plus lentement - je viens de veulent avoir une idée de l'ordre de grandeur implicite.
Barry Kaye

il est plus probable dans la zone de 1: 5 que "seulement 10%"
Yahia

2

Eh bien, voici ma première tentative sur la façon de procéder :

  1. Créez un projet ATL.dll contenant un objet simple à utiliser pour vos opérations critiques en virgule flottante. assurez-vous de le compiler avec des indicateurs qui désactivent l'utilisation de tout matériel non xx87 pour faire des virgules flottantes.
  2. Créez des fonctions qui appellent des opérations en virgule flottante et renvoient les résultats; commencez simplement et si cela fonctionne pour vous, vous pouvez toujours augmenter la complexité pour répondre à vos besoins de performance plus tard si nécessaire.
  3. Mettez les appels control_fp autour du calcul réel pour vous assurer que cela est fait de la même manière sur toutes les machines.
  4. Faites référence à votre nouvelle bibliothèque et testez pour vous assurer qu'elle fonctionne comme prévu.

(Je pense que vous pouvez simplement compiler vers un fichier .dll 32 bits, puis l'utiliser avec x86 ou AnyCpu [ou probablement cibler uniquement x86 sur un système 64 bits; voir le commentaire ci-dessous].)

Ensuite, en supposant que cela fonctionne, si vous souhaitez utiliser Mono, j'imagine que vous devriez être en mesure de répliquer la bibliothèque sur d'autres plates-formes x86 de la même manière (pas COM bien sûr; bien que, peut-être, avec du vin? Un peu hors de ma région une fois on y va quand même ...).

En supposant que vous puissiez le faire fonctionner, vous devriez être en mesure de configurer des fonctions personnalisées qui peuvent effectuer plusieurs opérations à la fois pour résoudre les problèmes de performances, et vous aurez des calculs en virgule flottante qui vous permettront d'obtenir des résultats cohérents sur toutes les plates-formes avec un montant minimal. de code écrit en C ++, et en laissant le reste de votre code en C #.


"compilez en .dll 32 bits et utilisez ensuite ... AnyCpu" Je pense que cela ne fonctionnera que sur un système 32 bits. Sur un système 64 bits, seul un programme ciblant x86pourra charger la DLL 32 bits.
CodesInChaos

2

Je ne suis pas un développeur de jeux, même si j'ai beaucoup d'expérience avec des problèmes de calcul difficiles ... alors, je ferai de mon mieux.

La stratégie que j'adopterais est essentiellement la suivante:

  • Utilisez une méthode plus lente (si nécessaire; s'il existe un moyen plus rapide, c'est parfait!), Mais prévisible pour obtenir des résultats reproductibles
  • Utilisez double pour tout le reste (par exemple, le rendu)

Le court et le long de ceci est: vous devez trouver un équilibre. Si vous passez 30 ms en rendu (~ 33 ips) et seulement 1 ms à la détection de collision (ou si vous insérez une autre opération très sensible) - même si vous tripler le temps nécessaire pour effectuer l'arithmétique critique, l'impact que cela a sur votre fréquence d'images est vous passez de 33,3 ips à 30,3 ips.

Je vous suggère de tout profiler, de tenir compte du temps passé à faire chacun des calculs sensiblement coûteux, puis de répéter les mesures avec une ou plusieurs méthodes pour résoudre ce problème et voir quel en est l'impact.


1

En vérifiant les liens dans les autres réponses, il est clair que vous n'aurez jamais la garantie de savoir si la virgule flottante est "correctement" implémentée ou si vous recevrez toujours une certaine précision pour un calcul donné, mais peut-être pourriez-vous faire de votre mieux en (1) tronquer tous les calculs à un minimum commun (par exemple, si différentes implémentations vous donneront 32 à 80 bits de précision, tronquer toujours chaque opération à 30 ou 31 bits), (2) avoir un tableau de quelques cas de test au démarrage (cas limites d'addition, de soustraction, de multiplication, de division, de sqrt, de cosinus, etc.) et si l'implémentation calcule des valeurs correspondant à la table, alors pas la peine de faire des ajustements.


toujours tronquer chaque opération à 30 ou 31 bits - c'est exactement ce que fait le floattype de données sur les machines x86 - cependant cela entraînera des résultats légèrement différents de la part des machines qui effectuent tous leurs calculs en utilisant seulement 32 bits, et ces petits changements se propageront avec le temps. D'où la question.
BlueRaja - Danny Pflughoeft

Si "N bits de précision" signifie que tout calcul est précis à autant de bits, et que la machine A est précise à 32 bits tandis que la machine B est précise à 48 bits, alors les 32 premiers bits de tout calcul par les deux machines doivent être identiques. La troncature à 32 bits ou moins après chaque opération ne maintiendrait-elle pas les deux machines exactement synchronisées? Sinon, quel est un exemple?
Identifiant de protection des témoins 44583292

-3

Votre question est assez difficile et technique O_o. Cependant j'ai peut-être une idée.

Vous savez certainement que le processeur effectue des ajustements après toute opération flottante. Et le 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 flux de travail d'instructions, même s'ils ont l'intention de calculer la même expression, peut fournir un autre résultat.

Les «erreurs» commises par un ajustement d'arrondi augmenteront à chaque instruction supplémentaire.

A titre d'exemple, nous pouvons dire qu'au niveau de l'assemblage: a * b * c n'est pas équivalent à a * c * b.

Je n'en suis pas tout à fait sûr, vous devrez demander à quelqu'un qui connaît bien plus que moi l'architecture CPU: 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 en .NET vous n'en avez pas. 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 de quelle manière 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 à laquelle je puisse penser 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 CLI).

J'espère avoir été assez clair, je ne suis pas parfait en anglais (... du tout: s)


9
Imaginez un jeu de tir P2P. Vous tirez sur un gars, vous le frappez et il meurt, mais c'est très proche, vous avez failli manquer. Sur le PC de l'autre gars utilise des calculs légèrement différents et il calcule que vous manquez. Voyez-vous le problème maintenant? Dans ce cas, augmenter la taille des registres n'aidera pas (du moins pas complètement). En utilisant exactement le même calcul sur chaque ordinateur.
svick

5
Dans ce scénario, on ne se soucie généralement pas de la proximité du résultat par rapport au résultat réel (tant qu'il est raisonnable), mais ce qui compte, c'est que c'est exactement le même pour tous les utilisateurs.
CodesInChaos

1
Vous avez raison, je n'ai pas pensé à ce genre de scénario. Cependant, je suis d'accord avec @CodeInChaos sur celui-ci. Je n'ai pas trouvé ça vraiment intelligent de prendre une décision importante deux fois. Il s'agit davantage d'un problème d'architecture logicielle. Un programme, l'application du tireur par exemple, doit faire le calcul et envoyer le résultat aux autres. Vous n'aurez jamais d'erreurs de cette manière. Vous avez un coup ou pas, mais un seul prend la décision. Like say @driushkin
AxFab

2
@Aesgar: Oui, c'est ainsi que fonctionnent la plupart des tireurs; cette "autorité" est appelée le serveur, et nous appelons l'architecture globale une architecture "client / serveur". Cependant, il existe un autre type d'architecture: peer-to-peer. En P2P, il n'y a pas de serveur; au contraire, tous les clients doivent vérifier toutes les actions entre eux avant que quoi que ce soit ne se produise. Cela augmente le décalage, ce qui le rend inacceptable pour les tireurs, mais diminue considérablement le trafic réseau, ce qui le rend parfait pour les jeux où un petit décalage (~ 250 ms) est acceptable, mais la synchronisation de l' état complet du jeu ne l'est pas. À savoir, les jeux RTS comme C&C et Starcraft utilisent le P2P.
BlueRaja - Danny Pflughoeft

5
Dans un jeu p2p, vous n'avez pas de machine de confiance sur laquelle compter. Si vous permettez à une station de décider si sa balle a touché ou non, vous ouvrez la possibilité à un client de tricher. De plus, les liens ne peuvent même pas gérer la quantité de données qui en résulte parfois - les jeux fonctionnent en envoyant les commandes plutôt que les résultats. Je joue à des jeux RTS et j'ai souvent vu tellement d'ordures voler qu'il n'y avait aucun moyen de les envoyer sur les liaisons montantes normales des ménages.
Loren Pechtel du
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.