Liigu peamise sisu juurde

Numbrilised andmetüübid

Sissejuhatus

Javas on kasutusel 6 primitiivset numbrilist andmetüüpi, mis jagunevad kahte kategooriasse:

  • Täisarvud: byte, short, int, long
  • Ujukomaarvud: float, double

NB: Antud peatükk käsitleb ainult andmetüüpe. See, milliseid operatsioone need võimaldavad, saad lugeda siit:

Täisarvulised andmetüübid

Täisarvulised andmetüübid hoiavad endas täisarve ilma murdosata. Nende erinevus seisneb suuruses ehk mitu bitti nad mälus kasutavad ning kui suurt arvu võimalik neis hoida.

TüüpSuurusVahemikVaikeväärtusNäide
byte8 biti (1 bait)-128 kuni 1270byte age = 25;
short16 biti (2 baiti)-32,768 kuni 32,7670short year = 2024;
int32 biti (4 baiti)-2,147,483,648 kuni 2,147,483,6470int population = 1000000;
long64 biti (8 baiti)-9,223,372,036,854,775,808 kuni 9,223,372,036,854,775,8070Llong distance = 9460730472580800L;

Vaikimisi kasutatakse enamus juhtudel int andmetüüpi, kuid sõltuvalt salvestatava arvu võimalikust suurusest (näiteks ajatemplite puhul) kasutatakse ka long tüüpi. byte kasutatakse peamiselt binaarsete andmete jaoks (failid, võrguandmed, vood), kuna see vastab täpselt 8-bitisele andmeühikule ning sobib hästi suurte andmehulkade töötlemiseks. short on tänapäeval harva kasutusel ning esineb peamiselt pärandkoodis või spetsiifiliste binaarsete formaatidega töötamisel.

Täisarvude väärtustamine

Täisarve saab tähistada Javas mitmel erineval viisil. Kõige levinum viis on kirjutada number välja kümnendsüsteemis (decimal), kuid on võimalik ka kasutada kahend- (binary), kaheksand- (octal) ja kuueteistkümnendsüsteemi (hexadecimal).
Järgnevas näites on välja toodud kõik need süsteemid arvu 26 näitel:

int decimal = 26;           // Decimal (default)
int hexadecimal = 0x1A; // Hexadecimal (0x prefix)
int binary = 0b11010; // Binary (0b prefix)
int octal = 032; // Octal (0 prefix)

System.out.println(decimal); // 26
System.out.println(hexadecimal); // 26
System.out.println(binary); // 26
System.out.println(octal); // 26

long-tüüpi väärtuste puhul tuleb lõppu lisada L täht (võimalik on kasutada ka väikest l tähte, kuid seda võiks vältida kuna see sarnaneb numbrile 1).

long bigNumber = 9876543210L;

Java keeles on võimalik arvväärtusi määrata ka teadusliku notatsiooni abil (scientific notation), kus e tähistab kümne astet. Teaduslik notatsioon loob alati ujukomaarvulise väärtuse (double).

double value = 1e9; // 1*10^9 -> 1 billion

Ning alates Java versioon 7-st saab numbreid parema loetavuse nimel osadeks jaotada, kasutades selleks alakriipsu (_):

int million = 1_000_000;
long distance = 384_400_000L;
int binary = 0b1101_0101_1010_1010;
int hex = 0xABC_DEF; // hexadecimal literal, equals 11_259_375 in decimal (A–F are valid digits that represent values 10–15)

Alakriipse ei saa lisada:

  • Arvu algusesse ega lõppu
  • Eesliite ja numbri vahele
  • L või F tähe ette

Näited:

int correct = 1_000_000;      // OK
int wrong1 = _1000000; // Error
int wrong2 = 1000000_; // Error
int wrong3 = 0_x1A; // Error

Ujukomaarvud

Ujukomaarvud võimaldavad esitada numbreid murdosadega.

TüüpSuurusTäpsusVaikeväärtusNäide
float32 biti (4 baiti)~7 komakohta0.0Ffloat pi = 3.14F;
double64 biti (8 baiti)~15 komakohta0.0Ddouble precise = 3.141592653589793;

Reeglina eelistatakse kasutada double-tüüpi ujukomaarvude salvestamiseks kuna see on täpsem, parema jõudlusega võrreldes float-iga ning kõik komakohtadega arvud on vaikimisi double-tüüpi. float-il on omad kasutusalad olemas (mänguarendus, graafikatöötlus) või näiteks kui arvutused ei nõua suurt täpsust, kuid neid esineb harva.

oht

float ega double ei sobi olukordades, kus arvutuste tulemus peab täpne olema ning kus ümardusvigu ei tohi tekkida (nt: raha käsitlemine). Sellistes oludes kasutatakse BigDecimal klassi, millest saab lugeda siit.

Ujukomaarvude väärtustamine

float-tüüpi arvude väärtustamise puhul peab alati lõppu lisama f või F tähe:

float price = 19.99f;        // Correct
float ratio = 0.5F; // Correct
float wrong = 19.99; // Error - Defaults to a double

double-tüüpi arvude puhul on d või D lõppu lisamine valikuline:

double pi = 3.14159;
double e = 2.71828d;
double exact = 2.71828D;

Samuti kehtib eelnevalt mainitud teaduslik notatsioon ja alakriipsudega eristamine ka ujukomaarvude peal.

Probleemid ujukomaarvude täpsuse ja võrdlemisega

Ujukomaarvud salvestatakse mällu kahendsüsteemi kujul IEEE 754 standardi järgi. See tähendab, et kõik arvud ei ole kahendsüsteemis täpselt esitatavad. Seda ilmestab hästi klassikaline probleem - mis juhtub kui kokku liita 0.1 ja 0.2 omavahel:

double a = 0.1;
double b = 0.2;
double sum = a + b;

System.out.println(sum); // 0.30000000000000004
System.out.println(sum == 0.3); // false

0.1 ja 0.2 kokku liitmisel ei ole vastuseks 0.3 vaid 0.30000000000000004. See on tavapärane käitumine ning tuleneb sellest, et antud arvu ei ole võimalik kahendsüsteemis korralikult esindada.

Siit saab ka järeldada, et ujukomaarvude võrdlemisel ei tohiks kasutada == operaatorit. Selle asemel kasutatakse epsilon-väärtuse põhist võrdlemist ehk kahe ujukomaarvu võrdlemisel kontrollitakse, kas nende vahe absoluutväärtus on ligikaudselt võrdne, näiteks:

double epsilon = 0.0001;
double a = 0.1 + 0.2;
double b = 0.3;

boolean areClose = Math.abs(a - b) < epsilon;
System.out.println(areClose); // true

Erilised ujukomaväärtused

Nii double kui ka float toetavad kolme erilist väärtust: lõpmatused (positiivne, negatiivne) ning NaN ehk not-a-number.

Lõpmatused (infinity)

Lõpmatut arvu on võimalik koostada läbi selleks ette nähtud konstandi või arvu nulliga jagades:

double positiveInf = 1.0 / 0.0;
double negativeInf = -1.0 / 0.0;

System.out.println(positiveInf); // Infinity
System.out.println(negativeInf); // -Infinity

// Constants
double inf1 = Double.POSITIVE_INFINITY;
double inf2 = Double.NEGATIVE_INFINITY;
hoiatus

Kui antud olukorras nulliga jagamine annab tulemuseks lõpmatuse, siis täisarvude puhul see ei kehti. Täisarvude nulliga jagamisel tekib ArithmeticException.

Samuti kehtivad IEEE 754 standardi reeglid ka lõpmatusega arvutamisel:

System.out.println(Double.POSITIVE_INFINITY + 100);           // Infinity
System.out.println(Double.POSITIVE_INFINITY * 2); // Infinity
System.out.println(Double.POSITIVE_INFINITY > 1000000); // true
System.out.println(1.0 / Double.POSITIVE_INFINITY); // 0.0

Seda kas arv on lõpmatu või mitte saab kontrollida järgnevalt:

double value = 1.0 / 0.0;

if (Double.isInfinite(value)) {
System.out.println("Is infinite");
}

if (Double.isFinite(value)) {
System.out.println("Is finite");
}

NaN - Not-a-Number

NaN tähistab määramata või võimatut tulemust. Selle omadused on:

  • Kõik tehted NaN-iga annavad tulemuseks NaN.
  • NaN ei võrdu mitte millegiga, sealhulgas ka iseendaga ehk NaN != NaN tulemus on true

NaN kontrollimiseks on ette nähtud eraldi meetodid: Double.isNaN(double v) ja Float.isNaN(float v).

NaN näited:

double nan = Double.NaN;

System.out.println(nan == nan); // false
System.out.println(nan != nan); // true
System.out.println(nan > 5); // false
System.out.println(nan + 10); // NaN

// Wrong
if (nan == Double.NaN) {
System.out.println("Is NaN");
}

// Correct
if (Double.isNaN(nan)) {
System.out.println("Is NaN");
}

Ületäide (overflow)

Iga numbriline muutuja kasutab mälus kindlat arvu bitte. Bittide arv ei olene mitte väärtusest, mida ta sisaldab, vaid valitud andmetüübist. Seetõttu on oluline andmetüübi valimisel mõelda, kui suuri väärtusi plaanitakse muutujas hoida.

Juhul kui muutuja väärtustamisel antakse väärtus, mis on väljaspool andmetüübi lubatud piire, väljastab Java kompilaator vastava hoiatuse. Arenduskeskkonnad nagu IntelliJ leiavad vea üles juba koodi kirjutamisel ning hoiatavad teid kohe. Kui aga aritmeetiline ületäitumine tekib mõne operatsiooni käigus, ei ilmu selle kohta ühtegi veateadet. Operatsioon justkui õnnestub, kuid väärtus on vale – kõige kõrgemat bitti ei arvestata ning minnakse ringiga kõige väiksema (või suurema) väärtuse juurde tagasi.

Näide:

byte b = 127; // Largest possible byte value
b++; // New value -128 (smallest possible)

Wrapper klassid

Tuleme tagasi eelmises peatükis tutvustatud wrapper klasside juurde. Iga primitiivse numbrilise tüübi jaoks on olemas ka vastav klass:

Primitiivne tüüpKlass
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble

Need klassid sisaldavad endas teatud konstante (näiteks MIN_VALUE ja MAX_VALUE) ning ka abimeetodeid arvude võrdlemiseks või teisendamiseks.

Üks kasulikematest abimeetoditest neis on parse ehk sõne numbriks muutmine:

String numStr = "123";
int num = Integer.parseInt(numStr);
System.out.println(num + 10); // 133

double price = Double.parseDouble("19.99");
System.out.println(price * 2); // 39.98

long distance = Long.parseLong("9460730472580800");
System.out.println(distance); // 9460730472580800
oht

Vigase sisendi korral tekib NumberFormatException. Seda saab vältida viga kinni püüdes try/catch plokiga:

try {
int num = Integer.parseInt("abc"); // letters can't be parsed into a number, throws error
} catch (NumberFormatException e) { // Catch error, go on as usual
// Error handling
}

Samuti saab ka numbreid sõneks muuta:

int num = 42;
String str1 = Integer.toString(num);
String str2 = String.valueOf(num);
String str3 = "" + num; // Works, but not recommended if sole purpose is to just convert num to str

Meetodite kohta täpsemalt ning nende näiteid saate uurida järgnevatest allikatest (Java ametlik dokumentatsioon):