Tipuri de date în virgulă mobilă

Mulțimile de valori pentru tipurile de date în virgulă mobilă

Conceptual, datele care aparțin acestor tipuri sunt numere reale. În limbajul Java există două tipuri de date reale (numite și tipuri de date flotante sau în virgulă mobilă):
 
Tipul Lungimea Intervalul de valori
float 4 octeti (32 biti) [-3.402347e+38f, ... 3.402347e38f] 
double 8 octeti (64 biti) [-1.7976931348623157e+308, ... 1.7976931348623157e308] 

Reprezentarea internă a datelor în virgulă mobilă adoptată pentru mașina virtuală Java corespunde standardului ANSI/IEEE 754-1985.

Reprezentarea externă a numerelor în virgulă mobilă se  poate face în următoarele moduri:
    a/ ca numere reale fără exponent, în care partea întreagă este separată de cea fracționara prin punct;
    b/ ca numere reale cu exponent, în care un număr întreg sau unul real fără exponent, numit mantisa sau coeficient, este urmat de un exponent zecimal, format din litera e sau E urmata de un numar intreg. Semnificația este că mantisa se înmultește cu 10 ridicat la o putere egală cu exponentul.

O caracteristică importantă a numerelor în virgulă mobilă este precizia. Se numește precizie numărul maxim de cifre semnificative pe care îl poate avea mantisa pentru tipul de date respectiv. Aceasta depinde de numărul de biți alocați mantisei în reprezentarea internă. În cazul limbajului Java, datele de tip float pot avea cel mult 7 cifre semnificative, iar cele de tip double cel mult 16 cifre semnificative. În reprezentarea externă a numărului se pot folosi și mai multe cifre, dar cele care depășesc lungimea admisă nu vor fi luate în considerație la conversia din forma externă în cea internă.
 
Cifrele semnificative sunt cifrele mantisei numărului, care încep de la prima cifră diferită de zero și se termină cu ultima cifra diferita de zero. De exemplu, numărul 000102.3015000 are 7 cifre semnificative (cele subliniate), deoarece zerourile de la început și de la sfârșit nu influențează valoarea numărului.
Același număr poate avea diferite reprezentări externe, de exemplu: 102.3015, 10.23015E1, 1.023015E2, 0.1023015E3, 0.01023015E4 etc. Pentru toate aceste numere, reprezentarea internă va fi aceeași.
Menționăm că numerele de mai sus, deși au numai 7 cifre semnificative, vor fi reprezentate intern ca numere de tip double, deci pe 8 octeți fiecare. Pentru a fi reprezentate intern ca date de tip float (pe 4 octeti) trebuie scrise cu litera f sau F la sfârșit, de exemplu 102.3015f

În afară de valorile numerice, pentru datele în virgulă mobilă sunt prevăzute și următoarele valori speciale:

    Infinity   - corespunde valorii plus infinit;
    -Infinity  - corespunde valorii minus infinit;
    NaN        - reprezintă ceva care nu este număr (Nota Number).
În consecință, în cazul datelor de tipuri reale, dacă a este un numar real pozitiv, operația a/0.0 dă ca rezultat Infinity, iar operația a/(-0.0) da rezultatul -Infinity. Operația 0.0/0.0 dă rezultatul NaN.
Atenție: valorile Infinity, -Infinity și NaN sunt formele externe sub care acestea se afișează pe ecran, se tipăresc la imprimantă sau se scriu într-un fișier de text. Fiecăreia dintre ele îi corespunde o reprezentare internă, sub forma unui șir de 4 sau 8 octeți, corespunzător tipului float sau double. Aceste reprezentări interne respectă regula, conform căreia, dacă sunt interpretate ca numere în virgulă mobilă, îndeplinesc următoarele condiții: 
   Infinity este mai mare decat orice valoare de tipul respectiv;
   -Infinity este mai mică decât orice valoare de tipul respectiv.
În acest fel, valorile menționate păstrează consistența operațiilor de comparație.

Literalii în virgulă mobilă

Literalii în virgulă mobilă sunt reprezentările valorilor reale în programele Java. La scrierea literalilor se respectă formele externe de numere reale fără exponent sau cu exponent prezentate mai sus, cu precizarea ca literalii de tip float se termina cu litera f sau F, în timp ce literalii de tip double nu au un astfel de sufix.
 
Exemple de literali în virgulă mobilă
  a/ literali de tip float fără exponent: 123.7f, -1876.53f;
  b/ literali de tip float cu exponent:
    7.28765e12f    (echivalent cu 7.28765x1012)
    5.0754286e-8f  (echivalent cu 5.075428x10-8)
    -3.9104e3f     (echivalent cu -3.9104x103)
  c/ literali de tip double fără exponent: 154236789.20973, -3401.76398653;
  d/ literali de tip double cu exponent:
    2935.87628091e-12
    -1.20937543872E23
    12e23 (echivalent cu 12.0x1023)

Atât la tipul float, cât și la tipul double, valoarea 0.0 are semn: poate exista atât +0.0 cât și -0.0. Un 0 fără semn este echivalent cu +0.0. Pentru tipul float, aceste valori sunt scrise sub forma +0.0f și -0.0f.
 
Valorile speciale Infinity, -Infinity și NaN nu sunt literali, ci doar forme externe de afișare a valorilor interne corespunzătoare. În consecință, ele nu pot fi folosite în programele sursă. De exemplu, daca x este o variabilă în virgulă mobilă, nu putem face atribuirea x=Infinity.

Operații și operatori pentru date în virgulă mobilă

Asupra datelor în virgulă mobilă se pot aplica operații de atribuire,  de conversie de tip, operații aritmetice și de comparație, atribuirea compusă. Acestea au fost deja prezentate, astfel că vom indica aici numai unele particularități ale aplicării lor în cazul datelor în virgulă mobilă.

Conversia de tip și atribuirea

S-a arătat deja ca orice dată numerică de tip întreg se poate converti implicit în oricare din tipurile în virgulă mobilă. De asemenea, are loc conversie implicită de la float la double. În consecință,  folosirea conversiei explicite (cast) este necesară atunci când se trece de la double la float, sau de la un tip în virgulă mobilă la unul întreg.

Conversia din double în float nu conduce niciodata la alterarea valorii, ci numai la pierderea de precizie. Aceasta se produce, întrucat se trece de la o mantisă de 52 de biți la una de 24 biți , deci de la cca 17 cifre semnificative la numai cca 7 cifre semnificative în sistemul zecimal.

În schimb, conversia explicită de la date în virgulă mobilă la oricare din datele de tip întreg poate duce la denaturarea valorii și a semnului numărului, la fel ca în cazul tipurilor intregi. Aceasta se întâmplă atunci când valoarea care trebuie convertită nu se încadrează în mulțimea de valori specifică tipului în care se face conversia.

Unei variabile în virgulă mobilă i se pot atribui orice valori de tipuri numerice.
 

Operații aritmetice cu numere reale

Asupra datelor în virgulă mobilă se pot aplica toți operatorii aritmetici prezentați anterior, inclusiv operatorii de incrementare (++), decrementare (--) și restul împărțirii întregi (%). Vom prezenta aici numai unele particularități ale operațiilor aritmetice în virgulă mobilă și vom da exemple.

O particularitate importantă a operațiilor aritmetice în virgulă mobilă în limbajul Java este că împărțirea la zero nu mai este considerată o exceptie, ci este o operație permisă. Rezultatul împărțirii la zero depinde de valoarea deîmpărțitului și de semnele celor doi operanzi. Rezultatul împărțirii 0/0 este NaN (Not a Number, deci o valoare nedefinită). Dacă deîmpărțitul este diferit de zero, rezultatul împărțirii este Infinity cu un semn care se stabilește după semnele celor doi operanzi: + (plus) dacă au același semn, sau - (minus)dacă semnele operanzilor sunt diferite. Amintim că la numerele în virgulă mobilă în Java și valoarea zero are semn, deci există +0.0 si -0.0.

Exemplu
În programul din fișierul VirgulaMobila.java, pe care îl reproducem mai jos, se testează diferite operații aritmetice în virgulă mobilă, inclusiv cele care au ca rezultate valori speciale.
 
/* Testarea operatiilor aritmetice cu numere in virgula mobila */

class VirgulaMobila {
  public static void main(String args[]) {
    double a=12.7865, b=-158.07, c=0.0, d=-0.0, inf1, inf2, n;
    float p=3476.15f, q=0.00237621f, r=0.0f, s=-0.0f;
    System.out.println("a/b="+(a/b)+" b/a="+(b/a)+" b%a="+(b%a));
    System.out.println("a/c="+(a/c)+" b/c="+(b/c));
    System.out.println("a/d="+(a/d)+" b/d="+(b/d));
    System.out.println("c/d="+(c/d)+" d/c="+(d/c));
    System.out.println("p/q="+(p/q)+" q/p="+(q/p)+" p%q="+(p%q));
    System.out.println("p/r="+(p/r)+" p/s="+(p/s));
    System.out.println("a/q="+(a/q)+" p/b="+(p/b));
    inf1=a/c; inf2=a/d; n=c/d;
    System.out.println("a/inf1="+(a/inf1)+" a/inf2="+(a/inf2));
    System.out.println("inf1/inf2="+(inf1/inf2)+" inf2/inf1="+
      (inf2/inf1));
    System.out.println("a*inf1="+(a*inf1)+" c*inf1="+(c*inf1));
  }
}

La executarea acestui program se afișează următorul rezultat:
 
a/b=-0.08089137723793256 b/a=-12.362257068001407 b%a=-4.631999999999991
a/c=Infinity b/c=-Infinity
a/d=-Infinity b/d=Infinity
c/d=NaN d/c=NaN
p/q=1462896.8 q/p=6.835752E-7 p%q=0.001879394
p/r=Infinity p/s=-Infinity
a/q=5381.048097062204 p/b=-21.991205809728285
a/inf1=0.0 a/inf2=-0.0
inf1/inf2=NaN inf2/inf1=NaN
a*inf1=Infinity c*inf1=NaN

Din aceste rezultate constatăm că:
    - operațiile cu infiniți și nedeterminări respectă regulile cunoscute din matematică (împărțirea la zero dă rezultat infinit, iar 0/0, infinit impartit la infinit și zero înmulțit cu infinit sunt nedeterminări);
    - dacă cel puțin un operand este în dublă precizie (double), rezultatul este în dublă precizie; dacă ambii operanzi sunt în simplă precizie (float), rezultatul este float;
    - se verifică egalitatea [b/a]*a+(b%a)=b, in care [b/a] este partea intreagă a câtului b/a, iar b%a este restul împărțirii întregi a lui b la a; de exemplu, (-12)*12.7865-4.632=-158.07.

Comparația

Pentru datele in virgula mobilă se aplică aceiași operatori de comparatie (relaționali) ca și pentru numerele întregi.

Atribuirea compusă

În cazul tipurilor de date reale se aplică următorii operatori de atribuire compusă:+=, -=, *=, /=, %=.



© Copyright 2000 - Severin BUMBARU, Universitatea "Dunărea de Jos" din Galați