Point mathématique fixe en c #?

Je me demandais si quelqu’un ici connaissait de bonnes ressources pour les maths à virgule fixe en c #? J’ai vu des choses comme ceci ( http://2ddev.72dpiarmy.com/viewtopic.php?id=156 ) et ceci ( quelle est la meilleure façon de faire des calculs à virgule fixe? ), Et un certain nombre de discussions pour savoir si est vraiment en virgule fixe ou en virgule flottante (mise à jour: les répondeurs ont confirmé que c’est définitivement un virgule flottante), mais je n’ai pas vu de bibliothèque solide en C # pour calculer le cosinus et le sinus.

Mes besoins sont simples – j’ai besoin des opérateurs de base, plus le cosinus, le sinus, l’arctan2, le PI … Je pense que c’est à peu près tout. Peut-être sqrt. Je programme un jeu de RTS en 2D, que je travaille en grande partie, mais le mouvement unitaire en utilisant des calculs à virgule flottante (double) a de très petites inexactitudes au fil du temps (10-30 minutes) sur plusieurs machines, conduisant à des désynchronisations. Ce n’est actuellement qu’entre un système d’exploitation 32 bits et un système d’exploitation 64 bits, toutes les machines 32 bits semblent restr synchronisées sans problème, ce qui me fait penser qu’il s’agit d’un problème en virgule flottante.

J’étais conscient de cela comme un problème possible dès le début, et j’ai donc limité autant que possible l’utilisation des calculs de position non entiers, mais pour un mouvement en diagonale fluide à des vitesses variables, je calcule l’angle entre les points en radians, puis obtenir les composantes x et y du mouvement avec sin et cos. C’est le problème principal. Je fais également des calculs pour des intersections de segments de lignes, des intersections de cercles de lignes, des intersections à rectangle circulaire, etc., qui doivent probablement également passer de points flottants à points fixes pour éviter les problèmes inter-machines.

S’il y a quelque chose en open source en Java ou VB ou un autre langage comparable, je pourrais probablement convertir le code pour mes utilisations. La principale priorité pour moi est l’exactitude, même si je souhaite une perte de vitesse aussi faible que la performance actuelle. Tout ce truc mathématique à point fixe est tout nouveau pour moi, et je suis surpris par le peu d’informations pratiques disponibles sur Google – la plupart des choses semblent être soit des fichiers d’entête C ++, soit des fichiers d’entête C ++ denses.

Tout ce que vous pourriez faire pour me diriger dans la bonne direction est très apprécié. Si je peux y arriver, je prévois d’ouvrir les fonctions mathématiques que j’ai rassemblées pour qu’il y ait une ressource pour les autres programmeurs C #.

MISE À JOUR: Je pourrais certainement faire fonctionner une table de consultation cosinus / sine pour mes besoins, mais je ne pense pas que cela fonctionnerait pour arctan2, car je devais générer une table avec environ 64 000 x 64 000 entrées (yikes). Si vous connaissez des explications programmatiques de moyens efficaces pour calculer des choses comme arctan2, ce serait génial. Mes connaissances en mathématiques sont bonnes, mais les formules avancées et la notation mathématique traditionnelle sont très difficiles à traduire en code.

Ok, voici ce que j’ai mis au point pour une structure en virgule fixe, basée sur le lien dans ma question initiale mais comprenant également des corrections à la gestion de la division et de la multiplication, et une logique supplémentaire pour les modules, comparaisons, changements, etc. :

public struct FInt { public long RawValue; public const int SHIFT_AMOUNT = 12; //12 is 4096 public const long One = 1 << SHIFT_AMOUNT; public const int OneI = 1 << SHIFT_AMOUNT; public static FInt OneF = FInt.Create( 1, true ); #region Constructors public static FInt Create( long StartingRawValue, bool UseMultiple ) { FInt fInt; fInt.RawValue = StartingRawValue; if ( UseMultiple ) fInt.RawValue = fInt.RawValue << SHIFT_AMOUNT; return fInt; } public static FInt Create( double DoubleValue ) { FInt fInt; DoubleValue *= (double)One; fInt.RawValue = (int)Math.Round( DoubleValue ); return fInt; } #endregion public int IntValue { get { return (int)( this.RawValue >> SHIFT_AMOUNT ); } } public int ToInt() { return (int)( this.RawValue >> SHIFT_AMOUNT ); } public double ToDouble() { return (double)this.RawValue / (double)One; } public FInt Inverse { get { return FInt.Create( -this.RawValue, false ); } } #region FromParts ///  /// Create a fixed-int number from parts. For example, to create 1.5 pass in 1 and 500. ///  /// The number above the decimal. For 1.5, this would be 1. /// The number below the decimal, to three digits. /// For 1.5, this would be 500. For 1.005, this would be 5. /// A fixed-int representation of the number parts public static FInt FromParts( int PreDecimal, int PostDecimal ) { FInt f = FInt.Create( PreDecimal, true ); if ( PostDecimal != 0 ) f.RawValue += ( FInt.Create( PostDecimal ) / 1000 ).RawValue; return f; } #endregion #region * public static FInt operator *( FInt one, FInt other ) { FInt fInt; fInt.RawValue = ( one.RawValue * other.RawValue ) >> SHIFT_AMOUNT; return fInt; } public static FInt operator *( FInt one, int multi ) { return one * (FInt)multi; } public static FInt operator *( int multi, FInt one ) { return one * (FInt)multi; } #endregion #region / public static FInt operator /( FInt one, FInt other ) { FInt fInt; fInt.RawValue = ( one.RawValue << SHIFT_AMOUNT ) / ( other.RawValue ); return fInt; } public static FInt operator /( FInt one, int divisor ) { return one / (FInt)divisor; } public static FInt operator /( int divisor, FInt one ) { return (FInt)divisor / one; } #endregion #region % public static FInt operator %( FInt one, FInt other ) { FInt fInt; fInt.RawValue = ( one.RawValue ) % ( other.RawValue ); return fInt; } public static FInt operator %( FInt one, int divisor ) { return one % (FInt)divisor; } public static FInt operator %( int divisor, FInt one ) { return (FInt)divisor % one; } #endregion #region + public static FInt operator +( FInt one, FInt other ) { FInt fInt; fInt.RawValue = one.RawValue + other.RawValue; return fInt; } public static FInt operator +( FInt one, int other ) { return one + (FInt)other; } public static FInt operator +( int other, FInt one ) { return one + (FInt)other; } #endregion #region - public static FInt operator -( FInt one, FInt other ) { FInt fInt; fInt.RawValue = one.RawValue - other.RawValue; return fInt; } public static FInt operator -( FInt one, int other ) { return one - (FInt)other; } public static FInt operator -( int other, FInt one ) { return (FInt)other - one; } #endregion #region == public static bool operator ==( FInt one, FInt other ) { return one.RawValue == other.RawValue; } public static bool operator ==( FInt one, int other ) { return one == (FInt)other; } public static bool operator ==( int other, FInt one ) { return (FInt)other == one; } #endregion #region != public static bool operator !=( FInt one, FInt other ) { return one.RawValue != other.RawValue; } public static bool operator !=( FInt one, int other ) { return one != (FInt)other; } public static bool operator !=( int other, FInt one ) { return (FInt)other != one; } #endregion #region >= public static bool operator >=( FInt one, FInt other ) { return one.RawValue >= other.RawValue; } public static bool operator >=( FInt one, int other ) { return one >= (FInt)other; } public static bool operator >=( int other, FInt one ) { return (FInt)other >= one; } #endregion #region <= public static bool operator <=( FInt one, FInt other ) { return one.RawValue <= other.RawValue; } public static bool operator <=( FInt one, int other ) { return one <= (FInt)other; } public static bool operator <=( int other, FInt one ) { return (FInt)other <= one; } #endregion #region > public static bool operator >( FInt one, FInt other ) { return one.RawValue > other.RawValue; } public static bool operator >( FInt one, int other ) { return one > (FInt)other; } public static bool operator >( int other, FInt one ) { return (FInt)other > one; } #endregion #region < public static bool operator <( FInt one, FInt other ) { return one.RawValue < other.RawValue; } public static bool operator <( FInt one, int other ) { return one < (FInt)other; } public static bool operator <( int other, FInt one ) { return (FInt)other < one; } #endregion public static explicit operator int( FInt src ) { return (int)( src.RawValue >> SHIFT_AMOUNT ); } public static explicit operator FInt( int src ) { return FInt.Create( src, true ); } public static explicit operator FInt( long src ) { return FInt.Create( src, true ); } public static explicit operator FInt( ulong src ) { return FInt.Create( (long)src, true ); } public static FInt operator <<( FInt one, int Amount ) { return FInt.Create( one.RawValue << Amount, false ); } public static FInt operator >>( FInt one, int Amount ) { return FInt.Create( one.RawValue >> Amount, false ); } public override bool Equals( object obj ) { if ( obj is FInt ) return ( (FInt)obj ).RawValue == this.RawValue; else return false; } public override int GetHashCode() { return RawValue.GetHashCode(); } public override ssortingng ToSsortingng() { return this.RawValue.ToSsortingng(); } } public struct FPoint { public FInt X; public FInt Y; public static FPoint Create( FInt X, FInt Y ) { FPoint fp; fp.X = X; fp.Y = Y; return fp; } public static FPoint FromPoint( Point p ) { FPoint f; fX = (FInt)pX; fY = (FInt)pY; return f; } public static Point ToPoint( FPoint f ) { return new Point( fXIntValue, fYIntValue ); } #region Vector Operations public static FPoint VectorAdd( FPoint F1, FPoint F2 ) { FPoint result; result.X = F1.X + F2.X; result.Y = F1.Y + F2.Y; return result; } public static FPoint VectorSubtract( FPoint F1, FPoint F2 ) { FPoint result; result.X = F1.X - F2.X; result.Y = F1.Y - F2.Y; return result; } public static FPoint VectorDivide( FPoint F1, int Divisor ) { FPoint result; result.X = F1.X / Divisor; result.Y = F1.Y / Divisor; return result; } #endregion } 

D’après les commentaires de ShuggyCoUk, je constate que c’est au format Q12. C’est raisonnablement précis pour mes objectives. Bien sûr, hormis les corrections de bogues, j’avais déjà ce format de base avant de poser ma question. Ce que je cherchais, c’était des moyens de calculer Sqrt, Atan2, Sin et Cos en C # en utilisant une structure comme celle-ci. Je ne connais aucune autre chose en C # qui gérera cela, mais en Java, j’ai réussi à trouver la bibliothèque MathFP d’Onno Hommes. C’est une licence de logiciel source libérale, j’ai donc converti certaines de ses fonctions à mes fins en C # (avec une correction à atan2, je pense). Prendre plaisir:

  #region PI, DoublePI public static FInt PI = FInt.Create( 12868, false ); //PI x 2^12 public static FInt TwoPIF = PI * 2; //radian equivalent of 260 degrees public static FInt PIOver180F = PI / (FInt)180; //PI / 180 #endregion #region Sqrt public static FInt Sqrt( FInt f, int NumberOfIterations ) { if ( f.RawValue < 0 ) //NaN in Math.Sqrt throw new ArithmeticException( "Input Error" ); if ( f.RawValue == 0 ) return (FInt)0; FInt k = f + FInt.OneF >> 1; for ( int i = 0; i < NumberOfIterations; i++ ) k = ( k + ( f / k ) ) >> 1; if ( k.RawValue < 0 ) throw new ArithmeticException( "Overflow" ); else return k; } public static FInt Sqrt( FInt f ) { byte numberOfIterations = 8; if ( f.RawValue > 0x64000 ) numberOfIterations = 12; if ( f.RawValue > 0x3e8000 ) numberOfIterations = 16; return Sqrt( f, numberOfIterations ); } #endregion #region Sin public static FInt Sin( FInt i ) { FInt j = (FInt)0; for ( ; i < 0; i += FInt.Create( 25736, false ) ) ; if ( i > FInt.Create( 25736, false ) ) i %= FInt.Create( 25736, false ); FInt k = ( i * FInt.Create( 10, false ) ) / FInt.Create( 714, false ); if ( i != 0 && i != FInt.Create( 6434, false ) && i != FInt.Create( 12868, false ) && i != FInt.Create( 19302, false ) && i != FInt.Create( 25736, false ) ) j = ( i * FInt.Create( 100, false ) ) / FInt.Create( 714, false ) - k * FInt.Create( 10, false ); if ( k <= FInt.Create( 90, false ) ) return sin_lookup( k, j ); if ( k <= FInt.Create( 180, false ) ) return sin_lookup( FInt.Create( 180, false ) - k, j ); if ( k <= FInt.Create( 270, false ) ) return sin_lookup( k - FInt.Create( 180, false ), j ).Inverse; else return sin_lookup( FInt.Create( 360, false ) - k, j ).Inverse; } private static FInt sin_lookup( FInt i, FInt j ) { if ( j > 0 && j < FInt.Create( 10, false ) && i < FInt.Create( 90, false ) ) return FInt.Create( SIN_TABLE[i.RawValue], false ) + ( ( FInt.Create( SIN_TABLE[i.RawValue + 1], false ) - FInt.Create( SIN_TABLE[i.RawValue], false ) ) / FInt.Create( 10, false ) ) * j; else return FInt.Create( SIN_TABLE[i.RawValue], false ); } private static int[] SIN_TABLE = { 0, 71, 142, 214, 285, 357, 428, 499, 570, 641, 711, 781, 851, 921, 990, 1060, 1128, 1197, 1265, 1333, 1400, 1468, 1534, 1600, 1665, 1730, 1795, 1859, 1922, 1985, 2048, 2109, 2170, 2230, 2290, 2349, 2407, 2464, 2521, 2577, 2632, 2686, 2740, 2793, 2845, 2896, 2946, 2995, 3043, 3091, 3137, 3183, 3227, 3271, 3313, 3355, 3395, 3434, 3473, 3510, 3547, 3582, 3616, 3649, 3681, 3712, 3741, 3770, 3797, 3823, 3849, 3872, 3895, 3917, 3937, 3956, 3974, 3991, 4006, 4020, 4033, 4045, 4056, 4065, 4073, 4080, 4086, 4090, 4093, 4095, 4096 }; #endregion private static FInt mul( FInt F1, FInt F2 ) { return F1 * F2; } #region Cos, Tan, Asin public static FInt Cos( FInt i ) { return Sin( i + FInt.Create( 6435, false ) ); } public static FInt Tan( FInt i ) { return Sin( i ) / Cos( i ); } public static FInt Asin( FInt F ) { bool isNegative = F < 0; F = Abs( F ); if ( F > FInt.OneF ) throw new ArithmeticException( "Bad Asin Input:" + F.ToDouble() ); FInt f1 = mul( mul( mul( mul( FInt.Create( 145103 >> FInt.SHIFT_AMOUNT, false ), F ) - FInt.Create( 599880 >> FInt.SHIFT_AMOUNT, false ), F ) + FInt.Create( 1420468 >> FInt.SHIFT_AMOUNT, false ), F ) - FInt.Create( 3592413 >> FInt.SHIFT_AMOUNT, false ), F ) + FInt.Create( 26353447 >> FInt.SHIFT_AMOUNT, false ); FInt f2 = PI / FInt.Create( 2, true ) - ( Sqrt( FInt.OneF - F ) * f1 ); return isNegative ? f2.Inverse : f2; } #endregion #region ATan, ATan2 public static FInt Atan( FInt F ) { return Asin( F / Sqrt( FInt.OneF + ( F * F ) ) ); } public static FInt Atan2( FInt F1, FInt F2 ) { if ( F2.RawValue == 0 && F1.RawValue == 0 ) return (FInt)0; FInt result = (FInt)0; if ( F2 > 0 ) result = Atan( F1 / F2 ); else if ( F2 < 0 ) { if ( F1 >= 0 ) result = ( PI - Atan( Abs( F1 / F2 ) ) ); else result = ( PI - Atan( Abs( F1 / F2 ) ) ).Inverse; } else result = ( F1 >= 0 ? PI : PI.Inverse ) / FInt.Create( 2, true ); return result; } #endregion #region Abs public static FInt Abs( FInt F ) { if ( F < 0 ) return F.Inverse; else return F; } #endregion 

Il existe un certain nombre d’autres fonctions dans la bibliothèque MathFP de Dr. Hommes, mais elles dépassaient ce dont j'avais besoin et je n’ai donc pas pris le temps de les traduire en C # (ce qui rendait la tâche plus difficile avec un long, et j'utilise la structure FInt, ce qui rend les règles de conversion un peu difficiles à voir immédiatement.

La précision de ces fonctions telles qu’elles sont codées ici est plus que suffisante pour mes besoins, mais si vous en avez besoin, vous pouvez augmenter la valeur de SHIFT AMOUNT sur FInt. Sachez simplement que si vous le faites, les constantes sur les fonctions de Dr. Hommes devront alors être divisées par 4096, puis multipliées par tout ce que votre nouvelle SHIFT AMOUNT nécessite. Vous risquez de rencontrer des bogues si vous faites cela et de ne pas faire attention, alors assurez-vous d'exécuter des vérifications sur les fonctions mathématiques intégrées pour vous assurer que vos résultats ne sont pas remis à plus tard.

Jusqu'à présent, cette logique FInt semble aussi rapide, sinon peut-être un peu plus rapide, que les fonctions intégrées .net équivalentes. Cela varie évidemment d'une machine à l'autre, puisque le coprocesseur fp le détermine, donc je n'ai pas exécuté de tests spécifiques. Mais ils sont intégrés dans mon jeu maintenant, et j'ai vu une légère diminution de l'utilisation du processeur par rapport à avant (ceci est sur un quad core Q6600 - une baisse d'environ 1% de l'utilisation en moyenne).

Merci encore à tous ceux qui ont commenté votre aide. Personne ne m'a dirigé directement vers ce que je cherchais, mais vous m'avez donné des indices qui m'ont aidé à le trouver moi-même sur Google. J'espère que ce code sera utile à quelqu'un d'autre, car il ne semble pas y avoir de comparaison dans C # publié publiquement.

Utilisez des entiers de 64 bits, par exemple à l’échelle 1/1000. Vous pouvez append et soustraire normalement. Lorsque vous devez multiplier, multipliez les nombres entiers et divisez ensuite par 1000. Lorsque vous avez besoin de sqrt, sin, cos etc., convertissez-le en double long, divisez par 1000, sqrt, multipliez par 1000, convertissez en entier. Les différences entre les machines ne devraient alors pas avoir d’importance.

Vous pouvez utiliser une autre échelle pour des divisions plus rapides, par exemple 1024 comme x/1024 == x >> 10 .

Je sais que ce sujet est un peu ancien, mais pour mémoire, voici un lien vers un projet qui implémente des maths à virgule fixe en C #: http://www.isquaredsoftware.com/XrossOneGDIPlus.php

J’ai implémenté un type Q31.32 à virgule fixe en C #. Il effectue toutes les opérations arithmétiques de base, sqrt, sin, cos, tan et est bien couvert par les tests unitaires. Vous pouvez le trouver ici , le type intéressant est Fix64. :

Notez que la bibliothèque inclut également les types Fix32, Fix16 et Fix8, mais ceux-ci étaient principalement destinés à l’expérimentation et ne sont pas aussi complets et sans bug.

En plus des entiers mis à l’échelle, il existe quelques bibliothèques numériques de précision arbitraires qui incluent généralement un type “BigRational”, et le point fixe est juste une puissance fixe de dix dénominateurs.

J’ai créé une structure similaire à point fixe. Vous obtenez un impact sur les performances en utilisant new () car il place des données sur le tas même si vous utilisez une structure. Voir Google (C # Heap (ing) Vs Stack (ing) dans .NET: Partie I) Le véritable pouvoir de struct est la possibilité de ne pas utiliser new et de passer par valeur à la stack. Mon exemple ci-dessous fait ce qui suit sur la stack. 1. [result int] sur la stack 2. [a int] sur la stack 3. [b int] sur la stack 4. [*] opérateur sur la stack 5. valeur résultat n’a pas renvoyé de coûts de tas.

  public static Num operator *(Num a, Num b) { Num result; result.NumValue = a.NumValue * b.NumValue; return result; }