Arithmétique à virgule fixe en C ++

Aujourd'hui, je vais vous dire ce qu'est un virgule fixe, pourquoi il est nécessaire et comment il peut être utilisé.

Il existe un tel problème lorsque les performances de l'application peuvent se dégrader sensiblement en raison des particularités des calculs en virgule flottante. En règle générale, le processeur est affiné pour les opérations entières et le coprocesseur FPU (unité à virgule flottante) fonctionne à un ordre de grandeur plus lent. Il existe de telles plates-formes où il n'y a pas de FPU du tout et émuler des opérations avec des nombres prendrait beaucoup de temps. Par exemple, en présence de FPU, la multiplication des nombres à virgule flottante est effectuée par une seule commande fmul, et en l'absence de FPU, la multiplication est effectuée par la fonction d'émulation __mulsf3. Par rapport à la commande fmul, la fonction __mulsf3 émule les opérations sur les nombres à virgule flottante, tandis que les calculs sont effectués sous forme d'entier, ce qui entraîne une augmentation du code machine et du temps d'exécution, tandis que la commande fmul effectuera cette opération rapidement, à l'aide de matériel fonds.

Il existe une solution à ce problème, qui permet d'effectuer des calculs avec un point fixe sur un type entier.

Le principe de ce type est un décalage fixe du nombre de N bits, à la suite duquel le nombre fractionnaire peut être représenté comme un entier et il aura une précision de 2 ^ N après le point. Un exemple de conversion d'un nombre à virgule flottante en un nombre à virgule fixe de l'ordre de 8 bits (2 ^ 8 = 1024).

Voici un exemple de conversion d'un nombre à virgule flottante en nombre à virgule fixe:

Fixed(12345,6789) = 1024 * 12345,6789 = 12641975,<s>1936</s> 

Ce nombre, après le point, a une précision de 2 ^ 8 après le point décimal.

Un exemple de la traduction inverse d'un nombre à virgule fixe en nombre à virgule flottante.

 Float(12641975) = 12641975 / 1024 = 12345,678<s>7109375</s> 

Dans ce cas, le nombre après la traduction inverse a la forme 12345.6787109375 et est précis à 3 chiffres après la période, la précision maximale est en fait de 2 ^ 8 = 1024.

Comment les calculs sur un type à virgule fixe se produisent-ils?


Les opérations de somme et de différence sont équivalentes aux opérations entières ordinaires.

Fixed(x) + Fixed(y) Fixed(x) - Fixed(y) , dans n'importe quel ordre
(1024 * x) + (1024 * y) (1024 * x) - (1024 * y)

La multiplication de ces nombres se fait sous cette forme.
(Fixed(x) * Fixed(y)) / p , c'est équivalent, avec un ordre de 8 bits
((1024 * x) * (1024 * y)) / 1024

Division.
(Fixed(x) * p) / Fixed(y) , également avec un ordre de 8 bits, ce
(1024 * 1024 * x)*(1024 * y)

Débordement


Lors des opérations de multiplication et de division, un cas de débordement est possible, ce qui conduira à un résultat incorrect. Cela se produira si, par exemple, un type entier 32 bits est utilisé et pendant les calculs, un débordement de ce type se produira et à la suite de ce débordement, le nombre perdra les bits les plus significatifs. Il existe deux façons d'éliminer le débordement:

  • Effectuez des calculs dans un type entier 64 bits.
  • Effectuer des calculs sous une forme «démontée», par exemple, lors de la multiplication, (xi + xf) * (yi + yf) = xi * yi + xf * yf + xi * yf + yi * xf, les préfixes i et f désignent la partie entière et la partie après des points.

Classe pour travailler avec virgule fixe en C ++


 #define DIGITS 1024 //  #define EPS 20 //       using namespace std; typedef signed int __int32_t; class Fixed { signed int x; Fixed(signed int a){ x = a; } public: Fixed(){ x = 0; } static Fixed fromInt(signed int val){ return Fixed(val*DIGITS); } static Fixed fromFloat(float val){ return Fixed((signed int)(val*DIGITS)); } float fixed2float(){ return ((float)x)/DIGITS; } Fixed sum(Fixed a,Fixed b){ return Fixed(a.x+bx); } Fixed diff(Fixed a,Fixed b){ return Fixed(ax-bx); } Fixed mul(Fixed a,Fixed b){ signed int c=ax*bx; if(c/bx != ax){ // Overflow! signed int i1 = ax/DIGITS; signed int i2 = bx/DIGITS; signed int f1 = (ax&(DIGITS-1)); signed int f2 = (bx&(DIGITS-1)); return Fixed((i1*i2)*DIGITS+(f1*f2)/DIGITS+i1*f2+i2*f1); }else{ return Fixed(c/DIGITS); } } Fixed div(Fixed a,Fixed b){ if(ax>(1<<21)){ // Overflow! signed int i = ax/DIGITS; signed int f = (ax&(DIGITS-1)); return Fixed(((i*DIGITS)/bx)*DIGITS+(f*DIGITS)/bx); }else{ return Fixed((ax*DIGITS)/bx); } } Fixed sqrt(Fixed k){ Fixed tmp(0); tmp.x = kx/2; signed int min = 0; signed int max = kx; Fixed quick(0); do{ tmp.x = (min+max)/2; quick = Fixed::mul(tmp,tmp); if(abs(quick.xk.x)<EPS) return Fixed(tmp); if(quick.x>kx){ max = tmp.x; }else{ min = tmp.x; } }while(true); } }; 

Source: https://habr.com/ru/post/fr451922/


All Articles