Java 8 Unsigned Arithmetic Support

1. Oversikt

Fra begynnelsen av Java er alle numeriske datatyper signert. I mange situasjoner er det imidlertid nødvendig å bruke usignerte verdier. Hvis vi for eksempel teller antall forekomster av en hendelse, vil vi ikke støte på en negativ verdi.

Støtten for usignert regning har endelig vært en del av JDK fra versjon 8. Denne støtten kom i form av Unsigned Integer API, som hovedsakelig inneholder statiske metoder i Heltall og Lang klasser.

I denne opplæringen vil vi gå gjennom dette API-et og gi instruksjoner om hvordan du bruker usignerte numre riktig.

2. Bitnivå-representasjoner

For å forstå hvordan du skal håndtere signerte og usignerte numre, la oss ta en titt på deres representasjon på bitnivå først.

I Java blir tall kodet ved hjelp av de to komplementsystemet. Denne kodingen implementerer mange grunnleggende aritmetiske operasjoner, inkludert addisjon, subtraksjon og multiplikasjon, på samme måte, enten operandene er signert eller usignert.

Ting bør være tydeligere med et kodeeksempel. For enkelhets skyld vil vi bruke variabler av byte primitiv datatype. Operasjoner er like for andre integrerte numeriske typer, for eksempel kort, int, eller lang.

Anta at vi har noen type byte med verdien av 100. Dette tallet har den binære representasjonen 0110_0100.

La oss doble denne verdien:

byte b1 = 100; byte b2 = (byte) (b1 << 1);

Venstre skiftoperatør i den gitte koden flytter alle bitene i variabelen b1 en posisjon til venstre, teknisk sett gjør verdien dobbelt så stor. Den binære representasjonen av variabel b2 vil da være 1100_1000.

I et usignert typesystem representerer denne verdien et desimaltall som tilsvarer 2^7 + 2^6 + 2^3, eller 200. Likevel, i et signert system fungerer den venstre biten som tegnbit. Derfor blir resultatet -2^7 + 2^6 + 2^3, eller -56.

En rask test kan verifisere resultatet:

assertEquals (-56, b2);

Vi kan se at beregningene av signerte og usignerte numre er de samme. Forskjeller vises bare når JVM tolker en binær representasjon som et desimaltall.

Tilleggs-, subtraksjons- og multiplikasjonsoperasjonene kan fungere med usignerte tall uten at det kreves noen endringer i JDK. Andre operasjoner, som sammenligning eller inndeling, håndterer signerte og usignerte numre forskjellig.

Det er her Unsigned Integer API kommer til spill.

3. Unsigned Integer API

Unsigned Integer API gir støtte for usignert heltall-aritmetikk i Java 8. De fleste medlemmene av denne API-en er statiske metoder i Heltall og Lang klasser.

Metodene i disse klassene fungerer på samme måte. Vi vil dermed fokusere på Heltall bare klassen, og slutter Lang klasse for kortfattethet.

3.1. Sammenligning

De Heltall klasse definerer en metode som heter sammenligneUnsignert for å sammenligne usignerte tall. Denne metoden anser alle binære verdier usignerte, og ignorerer forestillingen om tegnbit.

La oss starte med to tall ved grensene for int data-type:

int positive = Heltall.MAX_VALUE; int negativ = Heltall.MIN_VALUE;

Hvis vi sammenligner disse tallene som signerte verdier, positivt er åpenbart større enn negativ:

int signertComparison = Integer.compare (positiv, negativ); assertEquals (1, signert sammenligning);

Når du sammenligner tall som usignerte verdier, regnes den venstre biten som den viktigste biten i stedet for tegnbiten. Dermed blir resultatet annerledes, med positivt å være mindre enn negativ:

int unsignedComparison = Integer.compareUnsigned (positiv, negativ); assertEquals (-1, usignert sammenligning);

Det bør være tydeligere hvis vi tar en titt på den binære representasjonen av disse tallene:

  • MAX_VALUE ->0111_1111_…_1111
  • MIN_VALUE ->1000_0000_…_0000

Når den venstre biten er en vanlig verdi-bit, MIN_VALUE er en enhet større enn MAX_VALUE i det binære systemet. Denne testen bekrefter at:

assertEquals (negativ, positiv + 1);

3.2. Divisjon og Modulo

Akkurat som sammenligningsoperasjonen, usignerte divisjons- og modulo-operasjoner behandler alle bits som verdibiter. Kvotientene og resten er derfor forskjellige når vi utfører disse operasjonene på signerte og usignerte numre:

int positiv = Heltall.MAX_VALUE; int negativ = Heltall.MIN_VALUE; assertEquals (-1, negativ / positiv); assertEquals (1, Integer.divideUnsigned (negativ, positiv)); assertEquals (-1, negativ% positiv); assertEquals (1, Integer.restenUnsigned (negativ, positiv));

3.3. Analyse

Ved analyse av en String bruker parseUnsignedInt metode, tekstargumentet kan representere et tall større enn MAX_VALUE.

En slik stor verdi kan ikke analyseres med parseInt metode, som bare kan håndtere tekstrepresentasjon av tall fra MIN_VALUE til MAX_VALUE.

Følgende testtilfelle verifiserer analyseresultatene:

Kastbar kastet = catchThrowable (() -> Integer.parseInt ("2147483648")); assertThat (kastet) .isInstanceOf (NumberFormatException.class); assertEquals (Integer.MAX_VALUE + 1, Integer.parseUnsignedInt ("2147483648"));

Legg merke til at parseUnsignedInt metoden kan analysere en streng som indikerer et tall som er større enn MAX_VALUE, men vil ikke analysere noen negativ representasjon.

3.4. Formatering

I likhet med parsing, når en formaterer et tall, ser en usignert operasjon alle biter som verdibiter. Følgelig vi kan produsere den tekstlige representasjonen av et tall omtrent dobbelt så stort som MAX_VALUE.

Følgende testsak bekrefter formateringsresultatet av MIN_VALUE i begge tilfeller - signert og usignert:

Streng signertString = Integer.toString (Integer.MIN_VALUE); assertEquals ("- 2147483648", signertString); String unsignedString = Integer.toUnsignedString (Integer.MIN_VALUE); assertEquals ("2147483648", unsignedString);

4. Fordeler og ulemper

Mange utviklere, spesielt de som kommer fra et språk som støtter usignerte datatyper, for eksempel C, gleder seg over introduksjonen av usignerte regneoperasjoner. Derimot, dette er ikke nødvendigvis en god ting.

Det er to hovedårsaker til kravet om usignerte numre.

For det første er det tilfeller der en negativ verdi aldri kan oppstå, og bruk av en usignert type kan forhindre en slik verdi i utgangspunktet. For det andre, med en usignert type, kan vi doble spekteret av brukbare positive verdier sammenlignet med den signerte motparten.

La oss analysere begrunnelsen bak anken for usignerte numre.

Når en variabel alltid skal være ikke-negativ, en verdi mindre enn 0 kan være nyttig for å indikere en eksepsjonell situasjon.

For eksempel String.indexOf metoden returnerer posisjonen for den første forekomsten av et bestemt tegn i en streng. Indeksen -1 kan lett betegne fraværet av et slikt tegn.

Den andre årsaken til usignerte tall er utvidelsen av verdirommet. Derimot, hvis rekkevidden til en signert type ikke er nok, er det lite sannsynlig at et doblet område vil være tilstrekkelig.

I tilfelle en datatype ikke er stor nok, må vi bruke en annen datatype som støtter mye større verdier, for eksempel bruk lang i stedet for int, eller BigInteger heller enn lang.

Et annet problem med Unsigned Integer API er at den binære formen på et tall er den samme uansett om den er signert eller usignert. Det er derfor enkle å blande signerte og usignerte verdier, noe som kan føre til uventede resultater.

5. Konklusjon

Støtten for usignert regning i Java har kommet på forespørsel fra mange mennesker. Fordelene det medfører er imidlertid uklare. Vi bør være forsiktige når vi bruker denne nye funksjonen for å unngå uventede resultater.

Som alltid er kildekoden for denne artikkelen tilgjengelig på GitHub.