Prepad a podtečenie v Jave

1. Úvod

V tomto tutoriáli sa pozrieme na pretečenie a podtečenie číselných dátových typov v Jave.

Nebudeme sa hlbšie venovať teoretickejším aspektom - zameriame sa iba na to, keď sa to stane v Jave.

Najskôr sa pozrieme na celočíselné dátové typy, potom na dátové typy s pohyblivou rádovou čiarkou. U oboch tiež uvidíme, ako môžeme zistiť, či dôjde k nadmernému alebo podtečeniu.

2. Prepad a podtečenie

Jednoducho povedané, k pretečeniu a podtečeniu dôjde, keď priradíme hodnotu, ktorá je mimo rozsahu deklarovaného dátového typu premennej.

Ak je (absolútna) hodnota príliš veľká, nazývame ju pretečením, ak je hodnota príliš malá, nazývame ju podtečením.

Pozrime sa na príklad, keď sa pokúsime priradiť hodnotu 101000 (a 1 s 1000 nuly) na premennú typu int alebo dvojitý. Hodnota je pre súbor príliš veľká int alebo dvojitý premennej v Jave a dôjde k pretečeniu.

Ako druhý príklad povedzme, že sa pokúsime priradiť hodnotu 10-1000 (čo je veľmi blízko 0) k premennej typu dvojitý. Táto hodnota je príliš malá pre a dvojitý premennej v Jave a dôjde k podtečeniu.

Pozrime sa, čo sa v týchto prípadoch deje v prostredí Java, podrobnejšie.

3. Celé typy údajov

Celočíselné dátové typy v Jave sú bajt (8 bitov), krátky (16 bitov), int (32 bitov) a dlho (64 bitov).

Tu sa zameriame na int Dátový typ. Rovnaké správanie platí aj pre ostatné dátové typy, okrem minimálnej a maximálnej hodnoty.

Celé číslo typu int v Jave môže byť záporná alebo kladná, čo znamená, že s jej 32 bitmi môžeme priradiť hodnoty medzi -231 (-2147483648) a 231-1 (2147483647).

Trieda zavinovačka Celé číslo definuje dve konštanty, ktoré obsahujú tieto hodnoty: Celé číslo. MIN_VALUE a Celé číslo.MAX_VALUE.

3.1. Príklad

Čo sa stane, ak definujeme premennú m typu int a pokúsiť sa priradiť príliš veľkú hodnotu (napr. 21474836478 = MAX_VALUE + 1)?

Možným výsledkom tohto zadania je hodnota m bude nedefinované alebo že sa vyskytne chyba.

Oba sú platné výsledky; v Jave však hodnota m bude -2147483648 (minimálna hodnota). Na druhej strane, ak sa pokúsime priradiť hodnotu -2147483649 (= MIN_VALUE - 1), m bude 2147483647 (maximálna hodnota). Toto správanie sa nazýva integer-wraparound.

Zvážme nasledujúci úryvok kódu, ktorý toto správanie lepšie ilustruje:

int hodnota = Integer.MAX_VALUE-1; pre (int i = 0; i <4; i ++, hodnota ++) {System.out.println (hodnota); }

Dostaneme nasledujúci výstup, ktorý demonštruje pretečenie:

2147483646 2147483647 -2147483648 -2147483647 

4. Riešenie pretečenia a pretečenia celočíselných dátových typov

Java nevyvolá výnimku, keď dôjde k pretečeniu; preto môže byť ťažké nájsť chyby vyplývajúce z pretečenia. Rovnako nemôžeme priamo získať prístup k príznaku pretečenia, ktorý je k dispozícii vo väčšine procesorov.

Existuje však niekoľko spôsobov, ako zvládnuť možné pretečenie. Pozrime sa na niekoľko z týchto možností.

4.1. Použite iný dátový typ

Ak chceme povoliť hodnoty väčšie ako 2147483647 (alebo menšie ako -2147483648), môžeme jednoducho použiť dlho údajový typ alebo a BigInteger namiesto toho.

Aj keď premenné typu dlho môžu tiež pretekať, minimálna a maximálna hodnota sú oveľa väčšie a pravdepodobne postačujú vo väčšine situácií.

Rozsah hodnôt BigInteger nie je obmedzený, s výnimkou množstva pamäte dostupnej pre JVM.

Pozrime sa, ako môžeme náš príklad prepísať pomocou BigInteger:

BigInteger largeValue = nový BigInteger (Integer.MAX_VALUE + ""); pre (int i = 0; i <4; i ++) {System.out.println (largeValue); largeValue = largeValue.add (BigInteger.ONE); }

Uvidíme nasledujúci výstup:

2147483647 2147483648 2147483649 2147483650

Ako vidíme na výstupe, tu nedochádza k pretečeniu. Náš článok BigDecimal a BigInteger v obaloch Java BigInteger podrobnejšie.

4.2. Vyhoďte výnimku

Existujú situácie, keď nechceme povoliť väčšie hodnoty, ani nechceme, aby došlo k pretečeniu, a namiesto toho chceme hodiť výnimku.

Od verzie Java 8 môžeme používať metódy na presné aritmetické operácie. Najprv sa pozrime na príklad:

int hodnota = Integer.MAX_VALUE-1; pre (int i = 0; i <4; i ++) {System.out.println (hodnota); hodnota = Math.addExact (hodnota, 1); }

Statická metóda addExact () vykoná normálne sčítanie, ale vyvolá výnimku, ak má operácia za následok pretečenie alebo podtečenie:

2147483646 2147483647 Výnimka vo vlákne „main“ java.lang.ArithmeticException: celotelový prepad na java.lang.Math.addExact (Math.java:790) na baeldung.underoverflow.OverUnderflow.main (OverUnderflow.java:115)

Okrem tohoto addExact (), Matematika balík v Jave 8 poskytuje zodpovedajúce presné metódy pre všetky aritmetické operácie. Zoznam všetkých týchto metód nájdete v dokumentácii k Java.

Ďalej existujú presné metódy prevodu, ktoré vyvolávajú výnimku, ak dôjde k prepadu počas prevodu na iný dátový typ.

Pre konverziu z a dlho do an int:

public static int toIntExact (dlhé a)

A za premenu z BigInteger do an int alebo dlho:

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

4.3. Pred programom Java 8

Presné aritmetické metódy boli pridané do Java 8. Ak používame staršiu verziu, môžeme si tieto metódy jednoducho vytvoriť sami. Jednou z možností je implementovať rovnakú metódu ako v prostredí Java 8:

public static int addExact (int x, int y) {int r = x + y; if (((x ^ r) & (y ^ r)) <0) {throw new ArithmeticException ("int overflow"); } návrat r; }

5. Celočíselné dátové typy

Iné typy ako celé číslo plavák a dvojitý nesprávajte sa rovnako ako celočíselné dátové typy, pokiaľ ide o aritmetické operácie.

Jedným rozdielom je, že aritmetické operácie s číslami s pohyblivou rádovou čiarkou môžu mať za následok a NaN. Máme vyhradený článok o NaN v Jave, takže sa mu nebudeme v tomto článku bližšie venovať. Ďalej neexistujú presné aritmetické metódy ako napr addExact alebo znásobiť presne pre neceločíselné typy v Matematika balíček.

Java sa pri svojom vydaní riadi štandardom IEEE pre pohyblivú desatinnú aritmetiku (IEEE 754) plavák a dvojitý dátové typy. Tento štandard je základom pre spôsob, akým Java spracováva pretečenia a podtečenia čísel s pohyblivou rádovou čiarkou.

V nasledujúcich častiach sa zameriame na pretečenie a podtečenie dvojitý dátový typ a čo môžeme urobiť, aby sme zvládli situácie, v ktorých k nim dôjde.

5.1. Pretečenie

Pokiaľ ide o celočíselné dátové typy, môžeme očakávať, že:

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

To však neplatí pre premenné s pohyblivou rádovou čiarkou. Platí toto:

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

Je to preto, že a dvojitý hodnota má iba obmedzený počet významných bitov. Ak zvýšime hodnotu veľkého dvojitý hodnotu iba o jednu, nezmeníme žiadny z významných bitov. Preto hodnota zostáva rovnaká.

Ak zvýšime hodnotu našej premennej tak, že zvýšime jeden z významných bitov premennej, bude mať premenná hodnotu NEKONEČNOSŤ:

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

a NEGATÍVNE_INFINITY pre záporné hodnoty:

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

Vidíme, že na rozdiel od celých čísel neexistuje obtekanie, ale dva rôzne možné výsledky pretečenia: hodnota zostáva rovnaká alebo dostaneme jednu zo špeciálnych hodnôt, POZITÍVNE_INFINITY alebo NEGATÍVNE_INFINITY.

5.2. Podtečenie

Pre minimálne hodnoty a sú definované dve konštanty dvojitý hodnota: MIN_VALUE (4,9e-324) a MIN_NORMÁLNE (2.2250738585072014E-308).

Štandard IEEE pre aritmetiku s pohyblivou desatinnou čiarkou (IEEE 754) podrobnejšie vysvetľuje podrobnosti rozdielov medzi nimi.

Zamerajme sa na to, prečo vôbec potrebujeme minimálnu hodnotu pre čísla s pohyblivou rádovou čiarkou.

A dvojitý hodnota nemôže byť ľubovoľne malá, pretože na vyjadrenie hodnoty máme iba obmedzený počet bitov.

Kapitola o typoch, hodnotách a premenných v špecifikácii jazyka Java SE popisuje, ako sú reprezentované typy s pohyblivou rádovou čiarkou. Minimálny exponent pre binárnu reprezentáciu a dvojitý sa udáva ako -1074. To znamená, že najmenšia kladná hodnota, ktorú môže mať dvojník, je Math.pow (2, -1074), ktorá sa rovná 4,9e-324.

V dôsledku toho presnosť a dvojitý v Jave nepodporuje hodnoty medzi 0 a 4,9e-324, alebo medzi -4,9e-324 a 0 pre záporné hodnoty.

Čo sa teda stane, ak sa pokúsime priradiť príliš malú hodnotu premennej typu dvojitý? Pozrime sa na príklad:

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

S výstupom:

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

Vidíme, že ak priradíme príliš malú hodnotu, dostaneme podtečenie a výsledná hodnota je 0.0 (kladná nula).

Podobne pre záporné hodnoty bude mať podtečenie hodnotu -0.0 (záporná nula).

6. Detekcia podtečenia a pretečenia dátových typov s pohyblivou rádovou čiarkou

Pretože pretečenie bude mať za následok kladné alebo záporné nekonečno a podtečenie kladnou alebo zápornou nulou, nepotrebujeme presné aritmetické metódy ako pre celočíselné dátové typy. Namiesto toho môžeme skontrolovať, či tieto špeciálne konštanty detekujú prekročenie a podtečenie.

Ak chceme v tejto situácii vyvolať výnimku, môžeme implementovať pomocnú metódu. Pozrime sa, ako to môže hľadať umocňovanie:

public static double powExact (dvojitá báza, dvojitý exponent) {if (základ == 0,0) {návrat 0,0; } dvojitý výsledok = Math.pow (základ, exponent); if (result == Double.POSITIVE_INFINITY) {throw new ArithmeticException ("Double overflow result in POSITIVE_INFINITY"); } else if (result == Double.NEGATIVE_INFINITY) {throw new ArithmeticException ("Double overflow result in NEGATIVE_INFINITY"); } else if (Double.compare (-0.0f, result) == 0) {hodit novu ArithmeticException ("Dvojité pretečenie vedúce k zápornej nule"); } else if (Double.compare (+ 0,0f, result) == 0) {throw new ArithmeticException ("Double overflow result in positive zero"); } vrátiť výsledok; }

V tejto metóde musíme túto metódu použiť Double.compare (). Bežní operátori porovnania (< a >) nerozlišujú medzi kladnou a zápornou nulou.

7. Pozitívne a negatívne Nula

Na záver sa pozrime na príklad, ktorý ukazuje, prečo musíme byť pri práci s kladnou a zápornou nulou a nekonečnom opatrní.

Na ukážku definujeme niekoľko premenných:

dvojité a = + 0f; dvojité b = -0f;

Pretože pozitívny aj negatívny 0 sa považujú za rovnocenné:

assertTrue (a == b);

Zatiaľ čo pozitívna a negatívna nekonečnosť sa považujú za odlišné:

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

Nasledujúce tvrdenie je však správne:

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

Čo sa zdá byť v rozpore s našim prvým tvrdením.

8. Záver

V tomto článku sme videli, čo je prekročenie a prekročenie, ako to môže nastať v Jave a aký je rozdiel medzi celočíselnými a pohyblivými rádovými čiarkami.

Tiež sme videli, ako sme mohli zistiť preplnenie a podtečenie počas vykonávania programu.

Celý zdrojový kód je ako obvykle k dispozícii na serveri Github.


$config[zx-auto] not found$config[zx-overlay] not found