Overflow og Underflow i Java

1. Introduksjon

I denne opplæringen vil vi se på overflyt og underflyt av numeriske datatyper i Java.

Vi vil ikke dykke dypere inn i de mer teoretiske aspektene - vi vil bare fokusere på når det skjer i Java.

Først skal vi se på heltal datatyper, deretter på flytende punkt datatyper. For begge ser vi også hvordan vi kan oppdage når over- eller understrøm oppstår.

2. Overflow og Underflow

Enkelt sagt, overflow og underflow skjer når vi tildeler en verdi som er utenfor rekkevidden til den deklarerte datatypen til variabelen.

Hvis (absolutt) verdien er for stor, kaller vi den overflyt, hvis verdien er for liten, kaller vi den understrøm.

La oss se på et eksempel der vi prøver å tilordne verdien 101000 (en 1 med 1000 nuller) til en variabel av typen int eller dobbelt. Verdien er for stor for en int eller dobbelt variabel i Java, og det vil være et overløp.

La oss si at vi prøver å tilordne verdien som et andre eksempel 10-1000 (som er veldig nær 0) til en variabel av typen dobbelt. Denne verdien er for liten for en dobbelt variabel i Java, og det vil være en underflyt.

La oss se hva som skjer i Java i disse tilfellene mer detaljert.

3. Heltalldatatyper

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

Her vil vi fokusere på int data-type. Den samme oppførselen gjelder for de andre datatypene, bortsett fra at minimums- og maksimumsverdiene er forskjellige.

Et helt tall av typen int i Java kan være negativt eller positivt, noe som betyr at vi med sine 32 biter kan tildele verdier mellom -231 (-2147483648) og 231-1 (2147483647).

Pakkeklassen Heltall definerer to konstanter som har disse verdiene: Heltall.MIN_VALUE og Heltall.MAX_VALUE.

3.1. Eksempel

Hva vil skje hvis vi definerer en variabel m av typen int og prøv å tilordne en verdi som er for stor (f.eks. 21474836478 = MAX_VALUE + 1)?

Et mulig resultat av denne oppgaven er at verdien av m vil være udefinert eller at det vil være en feil.

Begge er gyldige utfall; imidlertid i Java, verdien av m vil være -2147483648 (minimumsverdien). På den annen side, hvis vi prøver å tilordne en verdi på -2147483649 (= MIN_VALUE - 1), m vil være 2147483647 (maksimumsverdien). Denne oppførselen kalles heltall-omvikling.

La oss vurdere følgende kodebit for å illustrere denne oppførselen bedre:

int-verdi = Heltall.MAX_VALUE-1; for (int i = 0; i <4; i ++, verdi ++) {System.out.println (verdi); }

Vi får følgende utdata, som demonstrerer overløpet:

2147483646 2147483647 -2147483648 -2147483647 

4. Håndtering av understrøm og overflyt av helhetdatatyper

Java kaster ikke et unntak når det oppstår et overløp; det kan derfor være vanskelig å finne feil som skyldes overløp. Vi kan heller ikke få direkte tilgang til overløpsflagget, som er tilgjengelig i de fleste CPUer.

Det er imidlertid forskjellige måter å håndtere et mulig overløp på. La oss se på flere av disse mulighetene.

4.1. Bruk en annen datatype

Hvis vi vil tillate verdier som er større enn 2147483647 (eller mindre enn -2147483648), kan vi ganske enkelt bruke lang datatype eller a BigInteger i stedet.

Selv om variabler av typen lang kan også overløpe, minimums- og maksimumsverdiene er mye større og er sannsynligvis tilstrekkelig i de fleste situasjoner.

Verdiområdet av BigInteger er ikke begrenset, bortsett fra hvor mye minne som er tilgjengelig for JVM.

La oss se hvordan vi omskriver eksemplet ovenfor med BigInteger:

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

Vi ser følgende utdata:

2147483647 2147483648 2147483649 2147483650

Som vi kan se i utgangen, er det ingen overløp her. Vår artikkel BigDecimal og BigInteger i Java-omslag BigInteger i mer detalj.

4.2. Kast et unntak

Det er situasjoner hvor vi ikke vil tillate større verdier, og vi vil heller ikke at et overløp skal forekomme, og vi vil i stedet kaste et unntak.

Fra og med Java 8 kan vi bruke metodene for nøyaktige aritmetiske operasjoner. La oss se på et eksempel først:

int-verdi = Heltall.MAX_VALUE-1; for (int i = 0; i <4; i ++) {System.out.println (verdi); verdi = Math.addExact (verdi, 1); }

Den statiske metoden addExact () utfører et normalt tillegg, men kaster et unntak hvis operasjonen resulterer i overløp eller understrøm:

2147483646 2147483647 Unntak i tråden "hoved" java.lang.ArithmeticException: heltalloverløp ved java.lang.Math.addExact (Math.java:790) ved baeldung.underoverflow.OverUnderflow.main (OverUnderflow.java:115)

I tillegg til addExact (), den Matte pakken i Java 8 gir tilsvarende eksakte metoder for alle aritmetiske operasjoner. Se Java-dokumentasjonen for en liste over alle disse metodene.

Videre er det eksakte konverteringsmetoder, som kaster et unntak hvis det er overløp under konverteringen til en annen datatype.

For konvertering fra en lang til en int:

offentlig statisk int toIntExact (lang a)

Og for 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øyaktige aritmetiske metodene ble lagt til Java 8. Hvis vi bruker en tidligere versjon, kan vi ganske enkelt lage disse metodene selv. Et alternativ for å gjøre det er å implementere samme metode som i Java 8:

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

5. Datatyper som ikke er heltall

Ikke-heltallstyper flyte og dobbelt ikke oppføre deg på samme måte som heltalldatatypene når det gjelder regneoperasjoner.

En forskjell er at aritmetiske operasjoner på flytende tall kan resultere i a NaN. Vi har en dedikert artikkel om NaN i Java, så vi vil ikke se nærmere på det i denne artikkelen. Videre er det ingen eksakte aritmetiske metoder som addExact eller multipliser for ikke-heltallstyper i Matte pakke.

Java følger IEEE Standard for Floating-Point Arithmetic (IEEE 754) for sin flyte og dobbelt datatyper. Denne standarden er grunnlaget for måten Java håndterer over- og understrømning av flytende punktum.

I avsnittene nedenfor vil vi fokusere på over- og understrøm av dobbelt datatype og hva vi kan gjøre for å håndtere situasjonene der de oppstår.

5.1. Flyte

Når det gjelder heltalldatatypene, kan vi forvente at:

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

Dette er imidlertid ikke tilfelle for variabler med flytende punkt. Følgende stemmer:

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

Dette er fordi en dobbelt verdien har bare et begrenset antall signifikante bits. Hvis vi øker verdien av en stor dobbelt verdi med bare én, endrer vi ikke noen av de vesentlige bitene. Derfor forblir verdien den samme.

Hvis vi øker verdien av variabelen vår slik at vi øker en av de signifikante bitene av variabelen, vil variabelen ha verdien EVIGHET:

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

og NEGATIVE_INFINITY for negative verdier:

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

Vi kan se at, i motsetning til for heltall, er det ingen innpakning, men to forskjellige mulige utfall av overløpet: verdien forblir den samme, eller vi får en av spesialverdiene, POSITIVE_INFINITY eller NEGATIVE_INFINITY.

5.2. Understrøm

Det er to konstanter definert for minimumsverdiene til a dobbelt verdi: MIN_VALUE (4.9e-324) og MIN_NORMAL (2.2250738585072014E-308).

IEEE Standard for Floating-Point Arithmetic (IEEE 754) forklarer detaljene for forskjellen mellom de mer detaljert.

La oss fokusere på hvorfor vi i det hele tatt trenger en minimumsverdi for flytende tall.

EN dobbelt verdien kan ikke være vilkårlig liten, ettersom vi bare har et begrenset antall biter som representerer verdien.

Kapittelet om Typer, verdier og variabler i Java SE-språkspesifikasjonen beskriver hvordan flytende punkttyper er representert. Minimumseksponenten for den binære representasjonen av a dobbelt er gitt som -1074. Det betyr at den minste positive verdien en dobbel kan ha er Math.pow (2, -1074), som er lik 4.9e-324.

Som en konsekvens, presisjonen til en dobbelt i Java støtter ikke verdier mellom 0 og 4.9e-324, eller mellom -4.9e-324 og 0 for negative verdier.

Så hva skjer hvis vi prøver å tilordne en for liten verdi til en variabel av typen dobbelt? La oss se på et eksempel:

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

Med utgang:

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 verdi som er for liten, får vi en understrøm, og den resulterende verdien er 0.0 (positivt null).

Tilsvarende, for negative verdier, vil en understrømning resultere i en verdi på -0.0 (negativt null).

6. Oppdage understrømning og overflyt av flytende punktdatatyper

Ettersom overløp vil resultere i enten positiv eller negativ uendelig, og understrømning i et positivt eller negativt nullpunkt, trenger vi ikke nøyaktige aritmetiske metoder som for heltalldatatypene. I stedet kan vi se etter at disse spesielle konstantene oppdager over- og understrøm.

Hvis vi vil kaste et unntak i denne situasjonen, kan vi implementere en hjelpermetode. La oss se på hvordan det kan se etter eksponentiering:

offentlig statisk dobbel powExact (dobbel base, dobbel eksponent) {if (base == 0,0) {return 0,0; } dobbelt resultat = Math.pow (base, eksponent); hvis (resultat == Double.POSITIVE_INFINITY) {kast nytt ArithmeticException ("Dobbelt overløp som resulterer i POSITIVE_INFINITY"); } annet hvis (resultat == Double.NEGATIVE_INFINITY) {kast nytt ArithmeticException ("Dobbelt overløp som resulterer i NEGATIVE_INFINITY"); } ellers hvis (Double.compare (-0.0f, resultat) == 0) {kast ny ArithmeticException ("Dobbel overløp som resulterer i negativ null"); } ellers hvis (Double.compare (+ 0.0f, result) == 0) {throw new ArithmeticException ("Double overflow resulterer i positivt null"); } returnere resultat; }

I denne metoden må vi bruke metoden Double.compare (). De normale sammenligningsoperatørene (< og >) ikke skille mellom positivt og negativt null.

7. Positivt og negativt Null

Til slutt, la oss se på et eksempel som viser hvorfor vi må være forsiktige når vi jobber med positivt og negativt null og uendelig.

La oss definere et par variabler som skal demonstreres:

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

Fordi positivt og negativt 0 regnes som like:

assertTrue (a == b);

Mens positiv og negativ uendelig anses som forskjellig:

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

Følgende påstand er imidlertid riktig:

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

Som ser ut til å være en motsetning til vår første påstand.

8. Konklusjon

I denne artikkelen så vi hva som er over- og understrøm, hvordan det kan oppstå i Java, og hva er forskjellen mellom datatypene for heltall og flytende punkt.

Vi så også hvordan vi kunne oppdage over- og understrøm under programutførelsen.

Som vanlig er den komplette kildekoden tilgjengelig på Github.


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