Java 8 niet-ondertekende rekenkundige ondersteuning

1. Overzicht

Vanaf het begin van Java zijn alle numerieke gegevenstypen ondertekend. In veel situaties is het echter vereist om niet-ondertekende waarden te gebruiken. Als we bijvoorbeeld het aantal keren dat een gebeurtenis voorkomt, tellen, willen we geen negatieve waarde tegenkomen.

De ondersteuning voor ongetekende rekenkunde maakt vanaf versie 8 eindelijk deel uit van de JDK. Deze ondersteuning kwam in de vorm van de Unsigned Integer API, die voornamelijk statische methoden in de Geheel getal en Lang klassen.

In deze zelfstudie bespreken we deze API en geven we instructies voor het correct gebruiken van niet-ondertekende nummers.

2. Vertegenwoordigingen op bitniveau

Om te begrijpen hoe om te gaan met ondertekende en niet-ondertekende nummers, laten we eerst hun representatie op bitniveau bekijken.

In Java worden getallen gecodeerd met behulp van het twee-complementsysteem. Deze codering implementeert veel elementaire rekenkundige bewerkingen, waaronder optellen, aftrekken en vermenigvuldigen, op dezelfde manier, of de operanden nu ondertekend of niet-ondertekend zijn.

Het moet duidelijker zijn met een codevoorbeeld. Eenvoudigheidshalve gebruiken we variabelen van de byte primitief gegevenstype. Bewerkingen zijn vergelijkbaar voor andere integrale numerieke typen, zoals kort, int, of lang.

Stel dat we een type hebben byte met de waarde van 100. Dit nummer heeft de binaire weergave 0110_0100.

Laten we deze waarde verdubbelen:

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

De linker shift-operator in de gegeven code verplaatst alle bits in variabele b1 een positie aan de linkerkant, waardoor de waarde technisch twee keer zo groot is. De binaire weergave van variabele b2 zal dan zijn 1100_1000.

In een systeem van een niet-ondertekend type vertegenwoordigt deze waarde een decimaal getal dat gelijk is aan 2^7 + 2^6 + 2^3, of 200. Niettemin, in een ondertekend systeem werkt de meest linkse bit als de tekenbit. Daarom is het resultaat -2^7 + 2^6 + 2^3, of -56.

Een snelle test kan het resultaat verifiëren:

assertEquals (-56, b2);

We kunnen zien dat de berekeningen van ondertekende en niet-ondertekende nummers hetzelfde zijn. Verschillen verschijnen alleen wanneer de JVM een binaire weergave interpreteert als een decimaal getal.

De bewerkingen voor optellen, aftrekken en vermenigvuldigen kunnen werken met niet-ondertekende getallen zonder dat er wijzigingen in de JDK nodig zijn. Andere bewerkingen, zoals vergelijken of delen, behandelen ondertekende en niet-ondertekende nummers op een andere manier.

Dit is waar de Unsigned Integer API in het spel komt.

3. De niet-ondertekende integer-API

De Unsigned Integer API biedt ondersteuning voor berekeningen met gehele getallen zonder teken in Java 8. De meeste leden van deze API zijn statische methoden in de Geheel getal en Lang klassen.

De methoden in deze klassen werken op dezelfde manier. We zullen ons dus concentreren op de Geheel getal alleen klasse, waarbij de Lang klasse voor beknoptheid.

3.1. Vergelijking

De Geheel getal class definieert een methode met de naam vergelijkOngetekend om niet-ondertekende nummers te vergelijken. Deze methode beschouwt alle binaire waarden als ongetekend en negeert het begrip tekenbit.

Laten we beginnen met twee cijfers op de grenzen van de int data type:

int positief = Geheel getal.MAX_VALUE; int negatief = Geheel getal.MIN_VALUE;

Als we deze getallen vergelijken als waarden met teken, positief is duidelijk groter dan negatief:

int SignedComparison = Integer.compare (positief, negatief); assertEquals (1, getekendvergelijking);

Bij het vergelijken van getallen als niet-ondertekende waarden, wordt de meest linkse bit beschouwd als de meest significante bit in plaats van de tekenbit. Het resultaat is dus anders, met positief kleiner zijn dan negatief:

int unsignedComparison = Integer.compareUnsigned (positief, negatief); assertEquals (-1, unsignedComparison);

Het zou duidelijker moeten zijn als we de binaire weergave van die getallen bekijken:

  • MAXIMUM WAARDE ->0111_1111_…_1111
  • MIN_VALUE ->1000_0000_…_0000

Als het meest linkse bit een bit met een normale waarde is, MIN_VALUE is één eenheid groter dan MAXIMUM WAARDE in het binaire systeem. Deze test bevestigt dat:

assertEquals (negatief, positief + 1);

3.2. Divisie en Modulo

Net als bij de vergelijkingsoperatie, de niet-ondertekende divisie- en modulo-bewerkingen verwerken alle bits als waardebits. De quotiënten en restanten zijn daarom verschillend wanneer we deze bewerkingen uitvoeren op ondertekende en niet-ondertekende nummers:

int positief = Geheel getal.MAX_VALUE; int negatief = Geheel getal.MIN_VALUE; assertEquals (-1, negatief / positief); assertEquals (1, Integer.divideUnsigned (negatief, positief)); assertEquals (-1, negatief% positief); assertEquals (1, Integer.remainderUnsigned (negatief, positief));

3.3. Parseren

Bij het ontleden van een Draad de ... gebruiken parseUnsignedInt methode, het tekstargument kan een getal vertegenwoordigen groter dan MAXIMUM WAARDE.

Een dergelijke grote waarde kan niet worden geparseerd met de parseInt methode, die alleen tekstuele weergave van getallen van MIN_VALUE naar MAXIMUM WAARDE.

De volgende testcase verifieert de ontledingsresultaten:

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

Merk op dat de parseUnsignedInt methode kan een string ontleden die een getal aangeeft dat groter is dan MAXIMUM WAARDE, maar slaagt er niet in om een ​​negatieve representatie te analyseren.

3.4. Formatteren

Net als bij het parseren, beschouwt een niet-ondertekende bewerking bij het formatteren van een getal alle bits als waardebits. Bijgevolg, we kunnen de tekstuele weergave produceren van een getal dat ongeveer twee keer zo groot is als MAXIMUM WAARDE.

De volgende testcase bevestigt het opmaakresultaat van MIN_VALUE in beide gevallen - getekend en ongetekend:

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

4. Voors en tegens

Veel ontwikkelaars, vooral degenen die afkomstig zijn uit een taal die niet-ondertekende gegevenstypen ondersteunt, zoals C, verwelkomen de introductie van niet-ondertekende rekenkundige bewerkingen. Echter, dit is niet per se een goede zaak.

Er zijn twee hoofdredenen voor de vraag naar ongetekende nummers.

Ten eerste zijn er gevallen waarin een negatieve waarde nooit kan voorkomen, en het gebruik van een niet-ondertekend type kan een dergelijke waarde in de eerste plaats voorkomen. Ten tweede kunnen we dat met een niet-ondertekend type het dubbele bereik van bruikbare positieve waarden vergeleken met zijn ondertekende tegenhanger.

Laten we de grondgedachte achter de oproep voor niet-ondertekende nummers analyseren.

Als een variabele altijd niet-negatief moet zijn, een waarde kleiner dan 0 kan handig zijn om een ​​uitzonderlijke situatie aan te duiden.

Bijvoorbeeld de String.indexOf methode geeft de positie terug van de eerste keer dat een bepaald teken in een tekenreeks voorkomt. De index -1 kan gemakkelijk de afwezigheid van een dergelijk teken aangeven.

De andere reden voor niet-ondertekende getallen is de uitbreiding van de waarderuimte. Echter, als het bereik van een ondertekend type niet voldoende is, is het onwaarschijnlijk dat een verdubbeld bereik volstaat.

Als een gegevenstype niet groot genoeg is, moeten we een ander gegevenstype gebruiken dat veel grotere waarden ondersteunt, zoals het gebruik van lang in plaats van int, of BigInteger liever dan lang.

Een ander probleem met de Unsigned Integer API is dat de binaire vorm van een getal hetzelfde is, ongeacht of het ondertekend of ongetekend is. Het is daarom gemakkelijk om ondertekende en niet-ondertekende waarden te combineren, wat tot onverwachte resultaten kan leiden.

5. Conclusie

De ondersteuning voor ongetekend rekenen in Java is op verzoek van veel mensen gekomen. De voordelen die het met zich meebrengt, zijn echter onduidelijk. We moeten voorzichtig zijn bij het gebruik van deze nieuwe functie om onverwachte resultaten te voorkomen.

Zoals altijd is de broncode voor dit artikel beschikbaar op GitHub.