Overflow og Underflow i Java

1. Introduktion

I denne vejledning ser vi på overflow og underflow af numeriske datatyper i Java.

Vi dykker ikke dybere ned i de mere teoretiske aspekter - vi fokuserer bare på, når det sker i Java.

Først ser vi på heltal datatyper, derefter på flydende datatyper. For begge ser vi også, hvordan vi kan registrere, hvornår over- eller underflow opstår.

2. Overflow og Underflow

Kort sagt, overløb og underflow sker, når vi tildeler en værdi, der er uden for rækkevidden af ​​den deklarerede datatype for variablen.

Hvis den (absolutte) værdi er for stor, kalder vi den overløb, hvis værdien er for lille, kalder vi den understrømning.

Lad os se på et eksempel, hvor vi forsøger at tildele værdien 101000 (en 1 med 1000 nuller) til en variabel af typen int eller dobbelt. Værdien er for stor til en int eller dobbelt variabel i Java, og der vil være et overløb.

Lad os som et andet eksempel forsøge at tildele værdien 10-1000 (som er meget tæt på 0) til en variabel af typen dobbelt. Denne værdi er for lille til en dobbelt variabel i Java, og der vil være en underflow.

Lad os se, hvad der sker i Java i disse tilfælde mere detaljeret.

3. Heltalsdatatyper

Heltalsdatatyperne i Java er byte (8 bit), kort (16 bits), int (32 bit) og lang (64 bit).

Her vil vi fokusere på int datatype. Den samme adfærd gælder for de andre datatyper, bortset fra at minimums- og maksimumværdierne er forskellige.

Et heltal af typen int i Java kan være negativ eller positiv, hvilket betyder, at vi med sine 32 bits kan tildele værdier imellem -231 (-2147483648) og 231-1 (2147483647).

Indpakningsklassen Heltal definerer to konstanter, der har disse værdier: Heltal.MIN_VALUE og Heltal.MAX_VALUE.

3.1. Eksempel

Hvad sker der, hvis vi definerer en variabel m af typen int og forsøg at tildele en værdi, der er for stor (f.eks. 21474836478 = MAX_VALUE + 1)?

Et muligt resultat af denne opgave er, at værdien af m vil være udefineret, eller at der vil være en fejl.

Begge er gyldige resultater; dog i Java, værdien af m vil være -2147483648 (minimumsværdien). På den anden side, hvis vi forsøger at tildele en værdi på -2147483649 (= MIN_VALUE - 1), m vil være 2147483647 (den maksimale værdi). Denne adfærd kaldes integer-wraparound.

Lad os overveje følgende kodestykke for at illustrere denne adfærd bedre:

int-værdi = Heltal.MAX_VALUE-1; for (int i = 0; i <4; i ++, værdi ++) {System.out.println (værdi); }

Vi får følgende output, som demonstrerer overløbet:

2147483646 2147483647 -2147483648 -2147483647 

4. Håndtering af underflow og overflow af heltal datatyper

Java kaster ikke en undtagelse, når der opstår et overløb; det kan derfor være svært at finde fejl som følge af et overløb. Vi kan heller ikke direkte få adgang til overløbsflagget, som er tilgængeligt i de fleste CPU'er.

Der er dog forskellige måder at håndtere et muligt overløb på. Lad os se på flere af disse muligheder.

4.1. Brug en anden datatype

Hvis vi vil tillade værdier større end 2147483647 (eller mindre end -2147483648), kan vi simpelthen bruge lang datatype eller a BigInteger i stedet.

Skønt variabler af typen lang kan også overløbe, minimums- og maksimumværdierne er meget større og er sandsynligvis tilstrækkelige i de fleste situationer.

Værdiområdet for BigInteger er ikke begrænset, undtagen med den mængde hukommelse, der er tilgængelig for JVM.

Lad os se, hvordan vi omskriver vores ovenstående eksempel med BigInteger:

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

Vi ser følgende output:

2147483647 2147483648 2147483649 2147483650

Som vi kan se i output, er der ikke noget overløb her. Vores artikel BigDecimal og BigInteger i Java-omslag BigInteger mere detaljeret.

4.2. Kast en undtagelse

Der er situationer, hvor vi ikke ønsker at tillade større værdier, og vi vil heller ikke have et overløb, og vi vil i stedet kaste en undtagelse.

Fra og med Java 8 kan vi bruge metoderne til nøjagtige aritmetiske operationer. Lad os se på et eksempel først:

int-værdi = Heltal.MAX_VALUE-1; for (int i = 0; i <4; i ++) {System.out.println (værdi); værdi = Math.addExact (værdi, 1); }

Den statiske metode addExact () udfører en normal tilføjelse, men kaster en undtagelse, hvis operationen resulterer i et overløb eller underflow:

2147483646 2147483647 Undtagelse i tråd "main" java.lang.ArithmeticException: heltal overløb ved java.lang.Math.addExact (Math.java:790) ved baeldung.underoverflow.OverUnderflow.main (OverUnderflow.java:115)

I tillæg til addExact (), det Matematik pakke i Java 8 giver tilsvarende nøjagtige metoder til alle aritmetiske operationer. Se Java-dokumentationen for en liste over alle disse metoder.

Desuden er der nøjagtige konverteringsmetoder, som kaster en undtagelse, hvis der er et overløb under konverteringen til en anden datatype.

Til konvertering fra en lang til en int:

offentlig statisk int toIntExact (lang a)

Og til konvertering fra BigInteger til en int eller lang:

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

4.3. Før Java 8

De nøjagtige aritmetiske metoder blev føjet til Java 8. Hvis vi bruger en tidligere version, kan vi simpelthen selv oprette disse metoder. En mulighed for at gøre det er at implementere den samme metode som i Java 8:

offentlig statisk int addExact (int x, int y) {int r = x + y; hvis (((x ^ r) & (y ^ r)) <0) {smid ny ArithmeticException ("int overflow"); } returnere r; }

5. Ikke-heltal datatyper

Ikke-heltalstyperne flyde og dobbelt opfører sig ikke på samme måde som heltal datatyperne, når det kommer til aritmetiske operationer.

En forskel er, at aritmetiske operationer på flydende tal kan resultere i a NaN. Vi har en dedikeret artikel om NaN i Java, så vi ser ikke nærmere på det i denne artikel. Desuden er der ingen nøjagtige aritmetiske metoder såsom addExact eller multiplicereeksakt for ikke-heltalstyper i Matematik pakke.

Java følger IEEE-standarden for flydende aritmetik (IEEE 754) for sin flyde og dobbelt datatyper. Denne standard er grundlaget for den måde, hvorpå Java håndterer over- og underflow af flydende numre.

I nedenstående sektioner fokuserer vi på over- og underflowet af dobbelt datatype og hvad vi kan gøre for at håndtere de situationer, hvor de opstår.

5.1. Flyde over

Hvad angår heltal datatyperne, kan vi forvente, at:

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

Dette er dog ikke tilfældet med variabler med flydende punkt. Det følgende gælder:

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

Dette skyldes, at en dobbelt værdi har kun et begrænset antal signifikante bits. Hvis vi øger værdien af ​​en stor dobbelt værdi kun med én, ændrer vi ikke nogen af ​​de signifikante bits. Derfor forbliver værdien den samme.

Hvis vi øger værdien af ​​vores variabel, så vi øger en af ​​de signifikante bits i variablen, vil variablen have værdien UENDELIGHED:

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

og NEGATIVE_INFINITY for negative værdier:

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

Vi kan se, at der i modsætning til for heltal ikke er nogen indpakning, men to forskellige mulige resultater af overløbet: værdien forbliver den samme, eller vi får en af ​​de specielle værdier, POSITIVE_INFINITY eller NEGATIVE_INFINITY.

5.2. Underflow

Der er to konstanter defineret for minimumsværdierne for a dobbelt værdi: MIN_VALUE (4.9e-324) og MIN_NORMAL (2.2250738585072014E-308).

IEEE Standard for Floating Point Arithmetic (IEEE 754) forklarer detaljerne for forskellen mellem dem mere detaljeret.

Lad os fokusere på, hvorfor vi overhovedet har brug for en minimumsværdi for flydende tal.

EN dobbelt værdi kan ikke være vilkårligt lille, da vi kun har et begrænset antal bits til at repræsentere værdien.

Kapitlet om typer, værdier og variabler i Java SE-sprogspecifikationen beskriver, hvordan floating-point-typer er repræsenteret. Minimumseksponenten for den binære repræsentation af a dobbelt er givet som -1074. Det betyder, at den mindste positive værdi, en dobbelt kan have, er Math.pow (2, -1074), som er lig med 4.9e-324.

Som en konsekvens, præcisionen af ​​en dobbelt i Java understøtter ikke værdier mellem 0 og 4.9e-324 eller mellem -4,9e-324 og 0 for negative værdier.

Så hvad sker der, hvis vi forsøger at tildele en for lille værdi til en variabel af typen dobbelt? Lad os se på et eksempel:

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

Med output:

2 ^ 1073 = 1.0E-323 2 ^ 1074 = 4.9E-324 2 ^ 1075 = 0.0 2 ^ 1076 = 0.0 

Vi ser, at hvis vi tildeler en værdi, der er for lille, får vi en underflow, og den resulterende værdi er 0.0 (positivt nul).

Tilsvarende vil en understrømning for negative værdier resultere i en værdi på -0.0 (negativt nul).

6. Registrering af underflow og overløb af datatyper med flydende punkt

Da overløb vil resultere i enten positiv eller negativ uendelighed og understrømning i et positivt eller negativt nul, behøver vi ikke nøjagtige aritmetiske metoder som for heltal datatyper. I stedet kan vi kontrollere, om disse specielle konstanter registrerer over- og underflow.

Hvis vi vil kaste en undtagelse i denne situation, kan vi implementere en hjælpermetode. Lad os se på, hvordan det kan se efter eksponentiering:

offentlig statisk dobbelt powExact (dobbelt base, dobbelt eksponent) {if (base == 0,0) {return 0,0; } dobbelt resultat = Math.pow (base, eksponent); hvis (resultat == Double.POSITIVE_INFINITY) {kast ny ArithmeticException ("Dobbelt overløb resulterer i POSITIVE_INFINITY"); } ellers hvis (resultat == Double.NEGATIVE_INFINITY) {kast nyt ArithmeticException ("Dobbelt overløb resulterer i NEGATIVE_INFINITY"); } ellers hvis (Double.compare (-0.0f, result) == 0) {throw new ArithmeticException ("Dobbelt overløb resulterer i negativt nul"); } ellers hvis (Double.compare (+ 0.0f, result) == 0) {throw new ArithmeticException ("Dobbelt overløb resulterer i positivt nul"); } returnere resultat }

I denne metode skal vi bruge metoden Double.compare (). De normale sammenligningsoperatører (< og >) skel ikke mellem positivt og negativt nul.

7. Positiv og negativ Nul

Lad os endelig se på et eksempel, der viser, hvorfor vi skal være forsigtige, når vi arbejder med positivt og negativt nul og uendeligt.

Lad os definere et par variabler, der skal demonstreres:

dobbelt a = + 0f; dobbelt b = -0f;

Fordi positive og negative 0 betragtes som lige:

assertTrue (a == b);

Mens positiv og negativ uendelighed betragtes som forskellige:

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

Følgende påstand er dog korrekt:

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

Hvilket synes at være en modsætning til vores første påstand.

8. Konklusion

I denne artikel så vi, hvad der er over- og underflow, hvordan det kan forekomme i Java, og hvad er forskellen mellem heltal og flydende datatyper.

Vi så også, hvordan vi kunne registrere over- og underflow under programudførelse.

Som sædvanlig er den komplette kildekode tilgængelig på Github.