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
double
calculs utilisent IEEE-754 64 bits) à l'aide de_controlfp_s
(Windows),_FPU_SETCW
(Linux?) Oufpsetprec
(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
float
etdouble
tout à fait.decimal
fonctionnerait dans ce but, mais serait beaucoup plus lent, et aucune desSystem.Math
fonctions 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?
strictfp
mot - clé, qui force tous les calculs à être effectués dans la taille indiquée (float
oudouble
) 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.Réponses:
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
float
n'est donc pas possible d' utiliser des s normaux dans un code .net déterministe.Les solutions de contournement que j'ai envisagées:
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é.
la source
decimal
choses, 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.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.
la source
double
chaque fois après une opération ne supprimerait pas les bits indésirables, donnant des résultats cohérents?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:
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:
la source
const
plutôt questatic
pour les constantes, afin que le compilateur puisse les optimiser; préférez les fonctions membres aux fonctions statiques (afin que nous puissions appeler, par exemplemyDouble.LeadingZeros()
au lieu deIntDouble.LeadingZeros(myDouble)
); essayez d'éviter les noms de variables à une seule lettre (MultiplyAnyLength
par exemple, a 9, ce qui le rend très difficile à suivre)unchecked
et types non conformes CLS commeulong
,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 commelong
etint
. 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!strictfp
.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
n
c'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.
la source
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:
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!
la source
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
BigInteger
classe 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.Decimal
repré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.
la source
BigInteger
n'est disponible que dans .Net 4.0.BigInteger
Decimal dépasse même la performance de Decimal.Decimal
(@Jonathan Dickinson - 'dog slow') ouBigInteger
(@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.Eh bien, voici ma première tentative sur la façon de procéder :
(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 #.
la source
x86
pourra charger la DLL 32 bits.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:
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.
la source
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.
la source
float
type 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.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)
la source