Túlcsordulás és alulcsordulás a Java-ban

1. Bemutatkozás

Ebben az oktatóanyagban megvizsgáljuk a Java numerikus adattípusainak túlcsordulását és alulcsordulását.

Nem fogunk elmélyülni az elméleti szempontokban - csak arra fogunk koncentrálni, amikor Java-ban történik.

Először egész szám adattípusokat, majd lebegőpontos adattípusokat vizsgálunk meg. Mindkettőnél azt is meglátjuk, hogyan tudjuk észlelni a túl- vagy alulcsordulást.

2. Túlcsordulás és alulcsordulás

Egyszerűen fogalmazva: a túlcsordulás és az alulcsordulás akkor történik, amikor olyan értéket rendelünk, amely kívül esik a változó deklarált adattípusának tartományán.

Ha az (abszolút) érték túl nagy, akkor azt túlcsordulásnak, ha az érték túl kicsi, akkor azt alulcsordulásnak nevezzük.

Nézzünk meg egy példát, ahol megkíséreljük az érték hozzárendelését 101000 (a 1 val vel 1000 nullák) típusú változóra int vagy kettős. Az érték túl nagy egy int vagy kettős változó a Java-ban, és túlcsordulás lesz.

Második példaként tegyük fel, hogy megpróbáljuk az értéket hozzárendelni 10-1000 (ami nagyon közel áll a 0-hoz) egy típusú változóhoz kettős. Ez az érték túl kicsi a kettős változó a Java-ban, és lesz alulcsordulás.

Nézzük meg részletesebben, hogy ezekben az esetekben mi történik a Java-ban.

3. Egész adattípusok

A Java egész számadatai a következők byte (8 bit), rövid (16 bit), int (32 bit), és hosszú (64 bit).

Itt a következőkre fogunk összpontosítani int adattípus. Ugyanez a viselkedés érvényes a többi adattípusra is, azzal a különbséggel, hogy a minimális és a maximális érték eltér.

Típusú egész szám int a Java-ban lehet negatív vagy pozitív, ami azt jelenti, hogy 32 bitjével értékeket rendelhetünk egymáshoz -231 (-2147483648) és 231-1 (2147483647).

A burkoló osztály Egész szám két állandót határoz meg, amelyek ezeket az értékeket tartják: Egész szám.MIN_VALUE és Egész.MAX_VALUE.

3.1. Példa

Mi fog történni, ha meghatározunk egy változót m típusú int és próbáljon túl nagy értéket rendelni (pl. 21474836478 = MAX_VALUE + 1)?

Ennek a hozzárendelésnek az egyik lehetséges eredménye, hogy a m nem lesz meghatározva, vagy hiba lesz.

Mindkettő érvényes eredmény; Java-ban azonban az értéke m lesz -2147483648 (a minimális érték). Másrészt, ha -2147483649 (= MIN_VALUE - 1), m lesz 2147483647 (a maximális érték). Ezt a viselkedést nevezzük egész számnak.

Vizsgáljuk meg a következő kódrészletet, hogy jobban szemléltessük ezt a viselkedést:

int érték = Egész.MAX_ÉRTÉK-1; for (int i = 0; i <4; i ++, érték ++) {System.out.println (érték); }

A következő kimenetet kapjuk, amely a túlcsordulást mutatja:

2147483646 2147483647 -2147483648 -2147483647 

4. Az egész adattípusok túlcsordulásának és túlcsordulásának kezelése

A Java nem vet ki kivételt túlcsordulás esetén; ezért nehéz megtalálni a túlcsordulásból eredő hibákat. A túlfolyási jelzőhöz sem férhetünk hozzá közvetlenül, amely a legtöbb CPU-ban elérhető.

Az esetleges túlcsordulás kezelésére azonban számos módszer létezik. Vizsgáljuk meg ezeket a lehetőségeket.

4.1. Használjon más adattípust

Ha nagyobb értékeket akarunk engedélyezni 2147483647 (vagy kisebb, mint -2147483648), egyszerűen használhatjuk a hosszú adattípus vagy a BigInteger helyette.

Bár típusú változók hosszú túlcsordulhat is, a minimális és a maximális érték sokkal nagyobb, és valószínűleg a legtöbb helyzetben elegendő.

Értéktartománya BigInteger nincs korlátozva, kivéve a JVM rendelkezésére álló memória mennyiségét.

Lássuk, hogyan írhatjuk felül a fenti példánkat BigInteger:

BigInteger largeValue = új BigInteger (Integer.MAX_VALUE + ""); for (int i = 0; i <4; i ++) {System.out.println (nagyérték); largeValue = nagyValue.add (BigInteger.ONE); }

A következő kimenetet látjuk:

2147483647 2147483648 2147483649 2147483650

Amint a kimeneten láthatjuk, itt nincs túlcsordulás. Cikkünk BigDecimal és BigInteger Java borítókban BigInteger részletesebben.

4.2. Dobj egy kivételt

Vannak olyan helyzetek, amikor nem akarunk nagyobb értékeket megengedni, és nem is akarunk túlcsordulást, és inkább kivételt akarunk dobni.

A Java 8-tól kezdve pontosan számtani műveletekhez használhatjuk a módszereket. Először nézzünk meg egy példát:

int érték = Egész.MAX_ÉRTÉK-1; for (int i = 0; i <4; i ++) {System.out.println (érték); érték = Math.addExact (érték, 1); }

A statikus módszer addExact () normál összeadást hajt végre, de kivételt vet, ha a művelet túlcsordulást vagy alulcsordulást eredményez:

2147483646 2147483647 Kivétel a "main" szálban java.lang.ArithmeticException: egész szám túlcsordulás a java.lang.Math.addExact (Math.java:790) címen a baeldung.underoverflow.OverUnderflow.main (OverUnderflow.java:115)

Továbbá addExact (), a Math A Java 8 csomag a megfelelő pontos módszereket biztosítja az összes aritmetikai művelethez. A módszerek listáját a Java dokumentációjában találja.

Vannak pontos konverziós módszerek, amelyek kivételt képeznek, ha egy másik adattípusra való átalakítás során túlcsordulás tapasztalható.

Az átszámításhoz a hosszú egy int:

public static int toIntExact (hosszú a)

És az áttéréshez BigInteger egy int vagy hosszú:

BigInteger largeValue = BigInteger.TEN; long longValue = nagyValue.longValueExact (); int intValue = nagyValue.intValueExact ();

4.3. Java 8 előtt

A pontos számtani módszereket hozzáadtuk a Java 8-hoz. Ha egy korábbi verziót használunk, egyszerűen magunk hozhatjuk létre ezeket a módszereket. Ennek egyik lehetősége ugyanaz a módszer megvalósítása, mint a Java 8-ban:

public static int addExact (int x, int y) {int r = x + y; if (((x ^ r) & (y ^ r)) <0) {dobja új ArithmeticException ("int túlcsordulás"); } return r; }

5. Nem egész adattípusok

A nem egész típusú típusok úszó és kettős nem viselkednek ugyanúgy, mint az egész adattípusok, ha aritmetikai műveletekről van szó.

Az egyik különbség az, hogy a lebegőpontos számok számtani műveletei a NaN. Van egy dedikált cikkünk a NaN-ról a Java-ban, ezért ebben a cikkben nem foglalkozunk vele tovább. Továbbá nincsenek pontos számtani módszerek, mint pl addExact vagy szorozzukPontos nem egész típusú típusokhoz a Math csomag.

A Java követi az IEEE lebegőpontos számtani szabványt (IEEE 754) úszó és kettős adattípusok. Ez a szabvány az alapja annak, ahogy a Java kezeli a lebegőpontos számok túl- és alulcsordulását.

Az alábbi szakaszokban a túl- és alulcsordulásra koncentrálunk kettős az adattípus és mit tehetünk annak érdekében, hogy kezeljük azokat a helyzeteket, amelyekben előfordulnak.

5.1. Túlcsordulás

Ami az egész adattípusokat illeti, arra számíthatunk, hogy:

assertTrue (dupla.MAX_VALUE + 1 == Double.MIN_VALUE);

A lebegőpontos változók esetében azonban nem ez a helyzet. A következő igaz:

assertTrue (dupla.MAX_VALUE + 1 == Double.MAX_VALUE);

Ez azért van, mert a kettős értéke csak korlátozott számú jelentős bitet tartalmaz. Ha növeljük egy nagy értékét kettős értéke csak egy, a jelentős bitek egyikét sem változtatjuk meg. Ezért az érték ugyanaz marad.

Ha úgy növeljük a változónk értékét, hogy megnöveljük a változó egyik jelentős bitjét, akkor a változó megkapja az értékét VÉGTELENSÉG:

assertTrue (Double.MAX_VALUE * 2 == Double.POSITIVE_INFINITY);

és NEGATIVE_INFINITY negatív értékek esetén:

assertTrue (Double.MAX_VALUE * -2 == Double.NEGATIVE_INFINITY);

Láthatjuk, hogy az egész számokkal ellentétben nincs átfedés, de a túlcsordulásnak két különböző lehetséges eredménye lehet: az érték ugyanaz marad, vagy megkapjuk a speciális értékek egyikét, POSITIVE_INFINITY vagy NEGATIVE_INFINITY.

5.2. Alulcsordulás

Két konstans van definiálva az a minimális értékeire kettős érték: MIN_VALUE (4.9e-324) és MIN_NORMAL (2.2250738585072014E-308).

Az IEEE lebegőpontos számtani szabvány (IEEE 754) részletesebben elmagyarázza a különbségeket a különbségek között.

Koncentráljunk arra, miért van szükségünk egyáltalán a lebegőpontos számok minimális értékére.

A kettős Az érték nem lehet önkényesen kicsi, mivel csak korlátozott számú bitünk van az érték képviseletére.

A Java SE nyelvi specifikációjának típusairól, értékeiről és változóiról szóló fejezet leírja a lebegőpontos típusok ábrázolását. Az a bináris reprezentációjának minimális kitevője kettős -ként adják meg -1074. Ez azt jelenti, hogy a duplának a legkisebb pozitív értéke lehet Math.pow (2, -1074), ami egyenlő a következővel: 4.9e-324.

Ennek következtében az a kettős a Java-ban nem támogatja a 0 és a közötti értékeket 4.9e – 324, vagy között -4,9e-324 és 0 negatív értékekre.

Tehát mi történik, ha megpróbálunk túl kicsi értéket rendelni egy típusú változóhoz kettős? Nézzünk meg egy példát:

for (int i = 1073; i <= 1076; i ++) {System.out.println ("2 ^" + i + "=" + Math.pow (2, -i)); }

Kimenettel:

2 ^ 1073 = 1,0E-323 2 ^ 1074 = 4,9E-324 2 ^ 1075 = 0,0 2 ^ 1076 = 0,0 

Látjuk, hogy ha túl kicsi értéket rendelünk hozzá, akkor alulfolyást kapunk, és az eredményül kapott érték az 0.0 (pozitív nulla).

Hasonlóképpen, negatív értékek esetén az alulcsordulás értéke -0.0 (negatív nulla).

6. A lebegőpontos adattípusok alul- és túlcsordulásának észlelése

Mivel a túlcsordulás pozitív vagy negatív végtelen, az alulcsordulás pedig pozitív vagy negatív nulla lesz, nincs szükségünk pontos aritmetikai módszerekre, mint például az egész adattípusokra. Ehelyett ellenőrizhetjük ezeket a speciális állandókat a túl- és alulcsordulás észleléséhez.

Ha kivételt akarunk vetni ebben a helyzetben, akkor megvalósíthatunk egy segítő módszert. Nézzük meg, hogyan lehet ez a hatványozást keresni:

public static double powExact (kettős alap, kettős kitevő) {if (alap == 0,0) {visszatér 0,0; } kettős eredmény = Math.pow (alap, kitevő); if (eredmény == Dupla.POSITIVE_INFINITY) {új ArithmeticException dobása ("Dupla túlcsordulás POSITIVE_INFINITY-t eredményez"); } else if (result == Double.NEGATIVE_INFINITY) {dobjon új ArithmeticException-t ("Dupla túlcsordulás NEGATIVE_INFINITY-t eredményez"); } else if (Double.compare (-0.0f, eredmény) == 0) {dobj új ArithmeticException-t ("Dupla túlcsordulás negatív nullát eredményez"); } else if (Double.compare (+ 0.0f, eredmény) == 0) {dobj új ArithmeticException-t ("Dupla túlcsordulás pozitív nullát eredményez"); } visszatérési eredmény; }

Ebben a módszerben a módszert kell használnunk Double.compare (). A normál összehasonlító operátorok (< és >) nem tesznek különbséget pozitív és negatív nulla között.

7. Pozitív és negatív Nulla

Végül nézzünk meg egy példát, amely megmutatja, miért kell vigyáznunk pozitív és negatív nulla és végtelen munkával.

Definiáljunk pár változót a bemutatásra:

kettős a = + 0f; kettős b = -0f;

Mert pozitív és negatív 0 egyenlőnek tekinthetők:

assertTrue (a == b);

Mivel a pozitív és a negatív végtelenséget különbözőnek tekintik:

assertTrue (1 / a == Dupla.POSITIVE_INFINITY); assertTrue (1 / b == Dupla.NEGATIVE_INFINITY);

A következő állítás azonban helytálló:

assertTrue (1 / a! = 1 / b);

Ami ellentmondásnak tűnik első állításunkkal.

8. Következtetés

Ebben a cikkben azt láttuk, mi a túl- és alulcsordulás, hogyan fordulhat elő a Java-ban, és mi a különbség az egész és lebegőpontos adattípusok között.

Láttuk azt is, hogy miként észlelhetjük a túl- és alulcsordulást a program végrehajtása során.

Szokás szerint a teljes forráskód elérhető a Githubon.