Java 8 előjel nélküli számtani támogatás

1. Áttekintés

A Java hajnalától kezdve az összes numerikus adattípust aláírják. Sok esetben azonban aláíratlan értékeket kell használni. Például, ha megszámoljuk egy esemény előfordulásának számát, akkor nem akarunk negatív értékkel találkozni.

Az aláíratlan számtan támogatása a 8. verziótól kezdve végre a JDK része volt. Ez a támogatás az Unsigned Integer API formájában érkezett, amely elsősorban statikus módszereket tartalmaz a Egész szám és Hosszú osztályok.

Ebben az oktatóanyagban áttekintjük ezt az API-t, és útmutatást adunk az aláíratlan számok helyes használatához.

2. Bitszintű ábrázolások

Ahhoz, hogy megértsük, hogyan kell kezelni az aláírt és az aláíratlan számokat, először vessünk egy pillantást azok megjelenítésére bites szinten.

A Java-ban a számokat a kettő komplementer rendszerével kódolják. Ez a kódolás számos alapvető számtani műveletet hajt végre, beleértve az összeadást, a kivonást és a szorzást, ugyanúgy, függetlenül attól, hogy az operandusok alá vannak-e írva vagy nincsenek-e aláírva.

A dolgoknak egyértelműbbnek kell lenniük egy kódpéldával. Az egyszerűség kedvéért a byte primitív adattípus. A műveletek hasonlóak más integrált numerikus típusoknál is, mint pl rövid, int, vagy hosszú.

Tegyük fel, hogy van valamilyen típusunk byte értékével 100. Ez a szám bináris reprezentációval rendelkezik 0110_0100.

Megduplázzuk ezt az értéket:

bájt b1 = 100; bájt b2 = (bájt) (b1 << 1);

Az adott kód bal váltás operátora az összes bitet változóban mozgatja b1 egy bal oldali pozíció, amely technikailag kétszer akkora értéket eredményez. A változó bináris ábrázolása b2 akkor lesz 1100_1000.

Aláíratlan típusú rendszerben ez az érték a decimális számot jelenti 2^7 + 2^6 + 2^3, vagy 200. Mindazonáltal, aláírt rendszerben a bal oldali legtöbb bit jel bitként működik. Ezért az eredmény az -2^7 + 2^6 + 2^3, vagy -56.

Gyors teszt segítségével ellenőrizhető az eredmény:

assertEquals (-56, b2);

Láthatjuk, hogy az aláírt és az aláíratlan számok számítása megegyezik. A különbségek csak akkor jelennek meg, ha a JVM bináris ábrázolást tizedes számként értelmez.

Az összeadási, kivonási és szorzási műveletek aláíratlan számokkal is működhetnek, anélkül, hogy bármilyen módosítást igényelnének a JDK-ban. Más műveletek, például az összehasonlítás vagy az osztás, az aláírt és az aláíratlan számokat másként kezelik.

Itt jelenik meg az Unsigned Integer API.

3. Az Unsigned Integer API

Az Unsigned Integer API támogatja a Java 8 aláíratlan egész számtanát. Ennek az API-nak a legtöbb tagja statikus módszer a Egész szám és Hosszú osztályok.

Ezen osztályok módszerei hasonlóan működnek. Így összpontosítunk a Egész szám osztályban, elhagyva a Hosszú osztály a rövidség kedvéért.

3.1. Összehasonlítás

A Egész szám osztály nevű módszert határoz meg hasonlítJelentetlen az aláíratlan számok összehasonlításához. Ez a módszer az összes bináris értéket aláíratlannak tekinti, figyelmen kívül hagyva a jelbit fogalmát.

Kezdjük két számmal a int adattípus:

int pozitív = Egész.MAX_ÉRTÉK; int negatív = Egész.MIN_ÉRTÉK;

Ha ezeket a számokat aláírt értékként hasonlítjuk össze, pozitív nyilvánvalóan nagyobb, mint negatív:

int jelöltComparison = Egész.összehasonlítás (pozitív, negatív); assertEquals (1, aláírt összehasonlítás);

A számok aláíratlan értékekként való összehasonlításakor a bal szélső bit számít a legjelentősebb bitnek az előjel bit helyett. Így az eredmény más, a pozitív kisebb, mint negatív:

int unsignedComparison = Integer.compareUsigned (pozitív, negatív); assertEquals (-1, előjel nélküli összehasonlítás);

Világosabbnak kell lennie, ha megnézzük ezen számok bináris ábrázolását:

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

Amikor a bal oldali legtöbb bit szabályos értékű bit, MIN_VALUE egységgel nagyobb, mint MAX_VALUE a bináris rendszerben. Ez a teszt megerősíti, hogy:

assertEquals (negatív, pozitív + 1);

3.2. Osztály és Modulo

Csakúgy, mint az összehasonlítási művelet, az aláíratlan osztás és modulo műveletek az összes bitet értékbitként dolgozzák fel. A hányadosok és a maradékok ezért különböznek, ha ezeket a műveleteket aláírt és előjel nélküli számokon hajtjuk végre:

int pozitív = Egész.MAX_ÉRTÉK; int negatív = Egész.MIN_ÉRTÉK; assertEquals (-1, negatív / pozitív); assertEquals (1, Integer.divideUnsigned (negatív, pozitív)); assertEquals (-1, negatív% pozitív); assertEquals (1, Integer.remainderUnsigned (negatív, pozitív));

3.3. Elemzés

Az elemzéskor a Húr használni a parseUnsignedInt módszer, a szöveges argumentum nagyobb számot képviselhet, mint MAX_VALUE.

Egy ilyen nagy értéket nem lehet elemezni a parseInt módszer, amely csak a számok szöveges ábrázolásával képes kezelni MIN_VALUE nak nek MAX_VALUE.

A következő teszteset ellenőrzi az elemzési eredményeket:

Dobható dobás = catchThrowable (() -> Egész szám.parseInt ("2147483648")); assertThat (dobott) .isInstanceOf (NumberFormatException.class); assertEquals (Integer.MAX_VALUE + 1, Integer.parseUnsignedInt ("2147483648"));

Figyeljük meg, hogy a parseUnsignedInt metódus elemezhet egy karakterláncot, amely nagyobb, mint MAX_VALUE, de nem fog elemezni semmilyen negatív ábrázolást.

3.4. Formázás

Az elemzéshez hasonlóan egy szám formázásakor az aláíratlan művelet az összes bitet értékbitnek tekinti. Következésképpen, körülbelül kétszer akkora szám szöveges ábrázolását állíthatjuk elő, mint MAX_VALUE.

A következő teszteset megerősíti a MIN_VALUE mindkét esetben - aláírással és aláírással:

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

4. Érvek és ellenérvek

Sok fejlesztő, különösen azok, akik olyan nyelvről származnak, amely támogatja az aláíratlan adattípusokat, például a C, örömmel fogadja az aláíratlan aritmetikai műveletek bevezetését. Azonban, ez nem feltétlenül jó dolog.

Az aláíratlan számok iránti keresletnek két fő oka van.

Először is vannak olyan esetek, amelyeknél negatív érték soha nem fordulhat elő, és egy aláíratlan típus használata elsősorban megakadályozhatja az ilyen értéket. Másodszor, aláíratlan típussal megtehetjük megduplázza a használható pozitív értékek tartományát aláírt társához képest.

Elemezzük az aláíratlan számok fellebbezésének okait.

Amikor egy változónak mindig nem negatívnak kell lennie, akkor az értéke kisebb, mint 0 hasznos lehet egy rendkívüli helyzet jelzésében.

Például a String.indexOf A metódus egy karakterben egy karakter karakter első előfordulásának pozícióját adja vissza. A -1 index könnyen jelölheti egy ilyen karakter hiányát.

Az aláíratlan számok másik oka az értéktér bővítése. Azonban, ha az aláírt típus tartománya nem elegendő, nem valószínű, hogy a duplázott tartomány is elegendő lenne.

Abban az esetben, ha egy adattípus nem elég nagy, akkor egy másik adattípust kell használnunk, amely sokkal nagyobb értékeket támogat, például a hosszú ahelyett int, vagy BigInteger inkább mint hosszú.

Az Unsigned Integer API másik problémája, hogy a szám bináris alakja ugyanaz, függetlenül attól, hogy aláírt vagy aláíratlan. Ezért van könnyen keverhetők az aláírt és az aláíratlan értékek, ami váratlan eredményekhez vezethet.

5. Következtetés

A Java aláíratlan számtanának támogatása sok ember kérésére történt. Az előnyök azonban nem egyértelműek. Óvatosan kell eljárnunk az új funkció használatakor, hogy elkerüljük a váratlan eredményeket.

Mint mindig, a cikk forráskódja is elérhető a GitHubon.


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