Dlaczego Java nie obsługuje obsługi liczb całkowitych bez znaku?
Wydaje mi się, że jest to dziwne pominięcie, biorąc pod uwagę, że pozwalają one na napisanie kodu, który rzadziej powoduje przepełnienie na nieoczekiwanie dużych danych wejściowych.
Co więcej, używanie liczb całkowitych bez znaku może być formą samokontroli, ponieważ wskazują one, że wartość, którą intencja miała podpisać, nigdy nie powinna być ujemna.
Wreszcie w niektórych przypadkach liczby całkowite bez znaku mogą być bardziej wydajne w przypadku niektórych operacji, takich jak dzielenie.
Jakie są wady ich włączenia?
java
language-design
unsigned
integer
dsimcha
źródło
źródło
byte
nie są w stanie zapewnić prostego140
poziomu szarości, ale-116
trzeba& 0xff
uzyskać odpowiednią wartość.Odpowiedzi:
Oto wywiad z Goslingiem i innymi na temat prostoty:
źródło
Czytając między wierszami, myślę, że logika wyglądała mniej więcej tak:
Przeważnie powiedziałbym, że była to rozsądna decyzja. Być może miałbym:
Jednak przy odrobinie kłucia operacje na niepodpisanych wartościach do 32 bitów nie są zbyt złe, a większość ludzi nie potrzebuje niepodpisanego 64-bitowego podziału lub porównania.
źródło
short
jest używany - algorytmy defltate / gzip / inflate są 16-bitowe i polegają w dużej mierze na szortach ... lub przynajmniejshort[]
[ co prawda są natywne - jednak algorytm Java przenoszący terrabytes danych]. Ta ostatnia (short[]
) ma znaczącą zaletę,int[]
ponieważ zajmuje dwa razy mniej pamięci i mniej pamięci = lepsze właściwości buforowania, znacznie lepszą wydajność.To jest starsze pytanie, a Pat krótko wspomniał o char, pomyślałem, że powinienem rozwinąć tę kwestię dla innych, którzy popatrzą na to w dalszej części. Przyjrzyjmy się bliżej pierwotnym typom Java:
byte
- 8-bitowa liczba całkowita ze znakiemshort
- 16-bitowa liczba całkowita ze znakiemint
- 32-bitowa liczba całkowita ze znakiemlong
- 64-bitowa liczba całkowita ze znakiemchar
- 16-bitowy znak (liczba całkowita bez znaku)Chociaż
char
nie obsługujeunsigned
arytmetyki, zasadniczo można ją traktować jakounsigned
liczbę całkowitą. Będziesz musiał jawnie rzucić operacje arytmetyczne z powrotem nachar
, ale zapewnia to sposób na określenieunsigned
liczb.Tak, nie ma bezpośredniego wsparcia dla liczb całkowitych bez znaku (oczywiście, nie musiałbym przerzucać większości moich operacji z powrotem na char, gdyby było bezpośrednie wsparcie). Z pewnością istnieje jednak niepodpisany prymitywny typ danych. Chciałbym również zobaczyć bajt bez znaku, ale zdaje się, że podwojenie kosztu pamięci i zamiast tego użycie char jest realną opcją.
Edytować
Z JDK8 są nowe API dla
Long
iInteger
które zapewniają metody pomocnika podczas leczenialong
iint
wartości jako wartości bez znaku.compareUnsigned
divideUnsigned
parseUnsignedInt
parseUnsignedLong
remainderUnsigned
toUnsignedLong
toUnsignedString
Dodatkowo, Guava oferuje szereg metod pomocniczych do robienia podobnych rzeczy dla typów całkowitych, co pomaga wypełnić lukę pozostawioną przez brak natywnego wsparcia dla
unsigned
liczb całkowitych.źródło
char
zbyt mały, abylong
na przykład obsługiwać arytmetykę.Java ma typy niepodpisane lub przynajmniej jeden: char jest skrótem bez znaku. Więc bez względu na to, jaką wymówkę Gosling wyrzuca, tak naprawdę to jego ignorancja, dlaczego nie ma innych niepodpisanych typów.
Również typy krótkie: spodenki są używane przez cały czas do multimediów. Powodem jest to, że możesz zmieścić 2 próbki w jednym 32-bitowym długim znaku bez znaku i wektoryzować wiele operacji. To samo dotyczy danych 8-bitowych i bajtu bez znaku. W rejestrze można zmieścić 4 lub 8 próbek do wektoryzacji.
źródło
char
dla postaci.Jak tylko podpisane i niepodpisane ints są mieszane w wyrażeniu rzeczy zaczynają się bałagan i prawdopodobnie będzie tracić informacji. Ograniczenie Javy do podpisanych ints tylko naprawia wszystko. Cieszę się, że nie muszę się martwić całym biznesem podpisanym / niepodpisanym, chociaż czasami brakuje mi ósmego kawałka bajtu.
źródło
static_cast
aby je pomieszać. To jest naprawdę niechlujne.byte
zostać podpisana tak jak w Pascal.& 0xFF
każda promocja bajt-do-int sprawia, że kod jest jeszcze bardziej bałaganiarski.http://skeletoncoder.blogspot.com/2006/09/java-tutorials-why-no-unsigned.html
Ten facet mówi, ponieważ standard C definiuje operacje z udziałem znaków niepodpisanych i podpisanych, które mają być traktowane jako niepodpisane. Może to spowodować, że liczby całkowite ze znakiem ujemnym zostaną przetoczone na dużą liczbę całkowitą bez znaku, potencjalnie powodując błędy.
źródło
-1
- z dowolną niepodpisaną wielkością - nawet zerem.-1
fakt, że wiek jest „nieznany” (jak sugeruje artykuł), jest jednym z klasycznych przykładów „zapachu kodu” . Na przykład, jeśli chcesz obliczyć „ile Alice jest starsza od Boba?”, A A = 25 i B = -1, otrzymasz odpowiedź,±26
która jest po prostu błędna. Prawidłowe obchodzenie się z nieznanymi wartościami jest jakaśOption<TArg>
kiedySome(25) - None
wróciNone
.Myślę, że Java jest w porządku, ale dodanie niepodpisanego skomplikowałoby go bez większego zysku. Nawet przy uproszczonym modelu liczb całkowitych większość programistów Java nie wie, jak zachowują się podstawowe typy liczbowe - wystarczy przeczytać książkę Java Puzzlers, aby zobaczyć, jakie błędne wyobrażenia możesz mieć.
Jeśli chodzi o praktyczne porady:
Jeśli twoje wartości są nieco dowolne i nie pasują
int
, użyjlong
. Jeśli nie pasują dolong
użyciaBigInteger
.Używaj mniejszych typów tylko dla tablic, gdy potrzebujesz zaoszczędzić miejsce.
Jeśli potrzebujesz dokładnie 64/32/16/8 bitów, użyj
long
/int
/short
/byte
i przestań się martwić bitem znaku, z wyjątkiem podziału, porównania, przesunięcia w prawo i rzutowania.Zobacz także tę odpowiedź na temat „przenoszenia generatora liczb losowych z C na Javę”.
źródło
>>
i odpowiednio>>>
dla podpisanego i niepodpisanego. Przesunięcie w lewo nie stanowi problemu.>>>
nie działa dlashort
ibyte
. Na przykład(byte)0xff>>>1
plony0x7fffffff
zamiast0x7f
. Kolejny przykład:byte b=(byte)0xff; b>>>=1;
spowodujeb==(byte)0xff
. Oczywiście możesz to zrobić,b=(byte)(b & 0xff >> 1);
ale dodaje to jeszcze jedną operację (bitową i).Z JDK8 ma pewne wsparcie dla nich.
Mimo obaw Goslinga możemy jeszcze zobaczyć pełną obsługę niepodpisanych typów w Javie.
źródło
Wiem, że ten post jest za stary; jednak za zainteresowanie, w Java 8 i później, można użyć
int
typu danych do reprezentowania niepodpisane 32-bitową liczbę całkowitą, która ma minimalną wartość 0 i wartość maksymalną w wysokości 2 32 -1. UżyjInteger
klasy, aby użyćint
typu danych jako liczby całkowitej bez znaku, a do klasy dodano metody statyczne, takie jak itp.compareUnsigned()
, W celu obsługi operacji arytmetycznych dla liczb całkowitych bez znaku.divideUnsigned()
Integer
źródło
Słyszałem historie, że miały one zostać zawarte w pobliżu oryginalnego wydania Java. Dąb był prekursorem Javy, aw niektórych dokumentach specyfikacji wspomniano o użytych wartościach. Niestety nigdy nie trafiły one do języka Java. O ile ktokolwiek był w stanie się zorientować, po prostu nie został wdrożony, prawdopodobnie z powodu ograniczenia czasowego.
źródło
char
), ponieważ projektanci uważali, że to zły pomysł ... biorąc pod uwagę cele języka.Raz uczestniczyłem w kursie C ++ z kimś z komitetu normalizacyjnego C ++, który sugerował, że Java podjęła właściwą decyzję, aby unikać nieposiadających liczb całkowitych, ponieważ (1) większość programów używających liczb całkowitych bez znaku może równie dobrze robić z liczbami całkowitymi ze znakiem, a jest to bardziej naturalne w przypadku warunki myślenia ludzi i (2) używanie liczb całkowitych bez znaku powoduje wiele łatwych do utworzenia, ale trudnych do debugowania problemów, takich jak przepełnienie arytmetyczne liczb całkowitych i utrata znaczących bitów podczas konwersji między typami podpisanymi i niepodpisanymi. Jeśli przez pomyłkę odejmiesz 1 od 0 za pomocą liczb całkowitych ze znakiem, często szybciej powoduje awarię programu i łatwiej jest znaleźć błąd niż zawija się do 2 ^ 32 - 1, a kompilatory i narzędzia analizy statycznej i kontrole środowiska wykonawczego muszą Załóżmy, że wiesz, co robisz, ponieważ wybrałeś arytmetykę bez znaku. Również,
Dawno temu, kiedy pamięć była ograniczona, a procesory nie działały automatycznie na 64 bitach jednocześnie, każdy bit liczył się o wiele więcej, więc podpisywanie vs niepodpisane bajty lub skróty faktycznie miały znacznie większe znaczenie i była oczywiście właściwą decyzją projektową. Dzisiaj samo użycie int podpisanego jest więcej niż wystarczające w prawie wszystkich zwykłych przypadkach programowania, a jeśli twój program naprawdę musi używać wartości większych niż 2 ^ 31 - 1, często po prostu chcesz długo. Gdy znajdziesz się na terytorium używania długich, jeszcze trudniej jest wymyślić powód, dla którego tak naprawdę nie możesz sobie poradzić z dodatnimi liczbami całkowitymi 2 ^ 63 - 1. Ilekroć przejdziemy do 128-bitowych procesorów, będzie to jeszcze mniejszy problem.
źródło
Twoje pytanie brzmi „Dlaczego Java nie obsługuje niepodpisanych znaków int”?
A moja odpowiedź na twoje pytanie brzmi: Java chce, aby wszystkie jej prymitywne typy: bajt , char , short , int i long były traktowane odpowiednio jako bajt , słowo , dword i qword , dokładnie tak jak w asemblerze, a operatorzy Java są podpisani operacje na wszystkich typach pierwotnych oprócz char , ale tylko na char są 16-bitowe bez znaku.
Zatem metody statyczne mają być również operacjami niepodpisanymi dla 32-bitowego i 64-bitowego.
Potrzebujesz klasy końcowej, której metody statyczne można wywołać dla niepodpisanego operacji .
Możesz stworzyć tę ostatnią klasę, nazwać ją dowolną nazwą i zaimplementować jej metody statyczne.
Jeśli nie masz pojęcia, jak zaimplementować metody statyczne, kliknij ten link może ci pomóc.
Moim zdaniem, Java jest nie podobny do C ++ w ogóle , gdyby nie wsparcie niepodpisanych typów ani przeciążanie operatorów, więc myślę, że Java powinny być traktowane jako całkowicie inny język zarówno z C ++ i od C.
Nawiasem mówiąc, jest to zupełnie inna nazwa języków.
Dlatego nie polecam w Javie wpisywania kodu podobnego do C i wcale nie polecam wpisywania kodu podobnego do C ++, ponieważ wtedy w Javie nie będziesz mógł robić tego, co chcesz zrobić w C ++, tzn. kod w dalszym ciągu nie będzie w ogóle C ++, a dla mnie źle jest tak kodować, aby zmienić styl w środku.
Zalecam pisanie i używanie metod statycznych również dla operacji podpisanych, więc nie widzisz w kodzie mieszanki operatorów i metod statycznych zarówno dla operacji podpisanych, jak i niepodpisanych, chyba że potrzebujesz tylko podpisanych operacji w kodzie, i jest w porządku używaj tylko operatorów.
Również polecam aby uniknąć stosując krótkie , int i długo prymitywnych typów i użyć słowa , dword i qword odpowiednio zamiast, a ty o wywołać metody statyczne dla operacji bez znaku i / lub podpisanych operacji zamiast używać operatorów.
Jeśli masz zamiar zrobić tylko podpisane operacji i korzystania z operatorów tylko w kodzie, to jest w porządku, aby korzystać z tych prymitywnych typów short , int i długi .
Właściwie słowo , DWORD i qword zrobić nie istnieje w języku, ale można utworzyć nową klasę dla każdego i realizacja każdego powinno być bardzo proste:
Słowo klasowe zawiera tylko typ pierwotny krótki , dword klasowy posiada typ pierwotny int tylko, a słowo klasy zawiera tylko typ pierwotny długi . Teraz wszystkie metody niepodpisane i podpisane jako statyczne lub nie do wyboru, możesz zaimplementować w każdej klasie, tj. Wszystkie 16-bitowe operacje zarówno niepodpisane, jak i podpisane przez podanie nazw znaczeń w klasie słów , wszystkie 32-bitowe operacje zarówno niepodpisane, jak i podpisane przez podanie nazw znaczeń w klasie dword i wszystkie operacje 64-bitowe zarówno niepodpisane, jak i podpisane przez podanie nazw znaczeń w klasie qword .
Jeśli nie lubisz nadawać zbyt wielu różnych nazw dla każdej metody, zawsze możesz użyć przeciążenia w Javie, dobrze jest przeczytać, że Java też tego nie usunęła!
Jeśli chcesz metod zamiast operatorów dla 8-bitowych operacji podpisanych i metod dla 8-bitowych operacji niepodpisanych, które w ogóle nie mają operatorów, możesz utworzyć klasę Byte (zwróć uwagę, że pierwsza litera „B” jest wielka, więc nie jest to bajt typu pierwotnego ) i zaimplementuj metody w tej klasie.
O przekazywaniu według wartości i przekazywaniu przez referencję:
Jeśli się nie mylę, jak w języku C #, obiekty prymitywne są przekazywane wartościowo naturalnie, ale obiekty klasy są przekazywane referencyjnie naturalnie, co oznacza, że obiekty typu Byte , word , dword i qword będą przekazywane przez referencję, a nie przez wartość domyślnie. Chciałbym, żeby Java miała obiekty struct tak jak C #, aby wszystkie Byte , word , dword i qword mogły być zaimplementowane w strukturze zamiast klasy, więc domyślnie były przekazywane przez wartość, a nie przez referencję domyślnie, jak każdy obiekt struct w C #, podobnie jak typy pierwotne, są przekazywane przez wartość, a nie przez referencję domyślnie, ale ponieważ Java jest gorsza niż C # i mamy aby sobie z tym poradzić, są tylko klasy i interfejsy, które są przekazywane przez referencję, a nie domyślnie przez wartość. Więc jeśli chcesz przekazać bajt , słowo , dword i qword obiektów przez wartość, a nie przez odniesienie, jak każdego innego obiektu klasy w Javie, a także w języku C #, trzeba będzie po prostu użyć konstruktora kopii i to wszystko.
To jedyne rozwiązanie, o którym mogę myśleć. Chciałbym tylko móc wpisać podstawowe typy słów, dwordów i qwordów, ale Java nie obsługuje ani typedef, ani w ogóle nie używa, w przeciwieństwie do C #, który obsługuje używanie , co jest równoważne typedef C.
O produkcji:
Dla tej samej sekwencji bitów możesz wydrukować je na wiele sposobów: jako binarne, jako dziesiętne (jak znaczenie% u w C printf), jako ósemkowe (jak znaczenie% o w C printf), jako szesnastkowe (jak znaczenie% x w C printf) i jako liczba całkowita (jak znaczenie% d w C printf).
Zauważ, że C printf nie zna typu zmiennych przekazywanych jako parametry do funkcji, więc printf zna typ każdej zmiennej tylko z obiektu char * przekazanego do pierwszego parametru funkcji.
Tak więc w każdej z klas: Bajt , słowo , dword i qword możesz zaimplementować metodę print i uzyskać funkcjonalność printf, mimo że pierwotny typ klasy jest podpisany, nadal możesz wydrukować go jako niepodpisany, postępując zgodnie z algorytmem obejmującym operacje logiczne i zmiany, aby uzyskać cyfry do wydrukowania na wyjściu.
Niestety podany przeze mnie link nie pokazuje, jak zaimplementować te metody drukowania, ale jestem pewien, że możesz znaleźć w Google algorytmy potrzebne do wdrożenia tych metod drukowania.
To wszystko, co mogę odpowiedzieć na twoje pytanie i zasugerować.
źródło
Ponieważ
unsigned
typ jest czystym złem.Fakt, że w C
unsigned - int
produkuje,unsigned
jest jeszcze bardziej zły.Oto migawka problemu, który spalił mnie więcej niż raz:
Czy zauważyłeś już błąd? Przyznaję, że widziałem to dopiero po wejściu do debuggera.
Ponieważ
n
jest typu bez znaku,size_t
całe wyrażenien - (rays.size() - 1) / 2
ocenia się jakounsigned
. Że wyrażenie ma być podpisany pozycjan
XX ray z jednym środkowym: 1st ray z jednym środkowym po lewej stronie musiałby pozycji -1, 1st jeden po prawej musiałby pozycji +1 itd Po biorąc wartość abs i mnożąc przezdelta
kąt, uzyskałbym kąt międzyn
promieniem th a środkowym.Niestety dla mnie powyższe wyrażenie zawierało zło niepodpisane i zamiast oceny, powiedzmy, -1, wyliczyło 2 ^ 32-1. Późniejsza konwersja w celu
double
zapieczętowania błędu.Po jednym lub dwóch błędach spowodowanych niewłaściwym użyciem
unsigned
arytmetyki należy zacząć zastanawiać się, czy dodatkowy bit, który dostajemy, jest wart dodatkowych kłopotów. Staram się, w miarę możliwości, unikać wszelkiegounsigned
rodzaju typów w arytmetyce, chociaż nadal używam go do operacji nie arytmetycznych, takich jak maski binarne.źródło
unsigned
zostanie przekonwertowanyint
na każdą operację, z czego korzystaunsigned
? Nie będzie miał żadnej funkcji, którą można by odróżnićshort
. A jeśli konwertujeszint
tylko na operacje mieszane, takie jakunsigned+int
lubunsigned+float
, to nadal masz problem((unsigned)25-(unsigned)30)*1.0 > 0
, który jest główną przyczynąunsigned
powiązanych błędów.exit(1);
naprawdę „jest warta dodatkowego problemu”? Czy nie jest w stanie otwierać dużych plików naprawdę wartych bezpieczeństwa, których nie doświadczą mniej doświadczeni programiści Javaunsigned
?n - (rays.size() - 1) / 2
. Zawsze powinieneś nawiasować operatory binarne, ponieważ czytnik kodu nie powinien zakładać niczego o kolejności operacji w programie komputerowym. To, że tradycyjnie mówimy, że a + b c = a + (b c) nie oznacza, że możesz to założyć podczas czytania kodu. Ponadto obliczenia należy zdefiniować poza pętlą, aby można je było przetestować bez pętli. Jest to błąd polegający na tym, że nie wyrównujesz typów, a nie na liczbach całkowitych bez znaku. W C musisz upewnić się, że Twoje typy są w linii.W specyfikacji „C” jest kilka klejnotów, które Java upuściła z powodów pragmatycznych, ale które powoli cofają się wraz z popytem programistów (zamknięcia itp.).
Wspominam o pierwszym, ponieważ jest on związany z tą dyskusją; zgodność wartości wskaźnika z arytmetyką całkowitą bez znaku. I w związku z tym tematem wątku trudność w utrzymywaniu semantyki Unsigned w podpisanym świecie Java.
Sądzę, że gdyby ktoś otrzymał alter ego Dennisa Ritchiego, który doradziłby zespołowi projektowemu Goslinga, zasugerowałby nadanie Signedowi „zero w nieskończoności”, tak aby wszystkie żądania przesunięcia adresu najpierw dodawałyby ROZMIAR PIERŚCIENIA ALGEBRAICZNEGO, aby uniknąć ujemnych wartości.
W ten sposób każde przesunięcie rzucone na tablicę nigdy nie wygeneruje SEGFAULT. Na przykład w klasie enkapsulowanej, którą nazywam RingArray podwójnych, która wymaga zachowania bez znaku - w kontekście „samoobrotowej pętli”:
Powyższy RingArray nigdy nie „pobierałby” z indeksu ujemnego, nawet gdyby próbował go złośliwy requester. Pamiętaj, że istnieje również wiele uzasadnionych próśb o podanie wcześniejszych (ujemnych) wartości indeksu.
NB: Zewnętrzny moduł% usuwa odniesienia do uzasadnionych żądań, podczas gdy wewnętrzny moduł% maskuje jawną złośliwość przed negatywami bardziej negatywnymi niż -modulus. Gdyby miało się to kiedykolwiek pojawić w Javie + .. + 9 || 8 + .. + spec, problem rzeczywiście stałby się „programistą, który nie może„ samobrócić się ”USTERKA.
Jestem pewien, że tak zwany „brak” języka Java unsigned int można uzupełnić powyższym linkiem.
PS: Aby nadać kontekst powyższemu porządkowi RingArray, oto kandydująca operacja „ustaw”, aby dopasować do powyższej operacji elementu „pobierz”:
źródło
Mogę wymyślić jeden niefortunny efekt uboczny. W osadzonych bazach danych Java liczba identyfikatorów, które można mieć przy 32-bitowym polu identyfikatora, wynosi 2 ^ 31, a nie 2 ^ 32 (~ 2 miliardy, nie ~ 4 miliardy).
źródło
Powodem IMHO jest to, że są / byli zbyt leniwi, aby zaimplementować / poprawić ten błąd. Sugerowanie, że programiści C / C ++ nie rozumieją niepodpisanej, struktury, unii, flagi bitowej ... Jest po prostu niedorzeczne.
Eter rozmawiałeś z podstawowym programistą / bash / java na progu programowania a la C, bez żadnej realnej znajomości tego języka lub po prostu rozmawiasz ze swojego umysłu. ;)
kiedy codziennie rozprawiasz się z formatem albo z pliku, albo ze sprzętu, zaczynasz pytać, co do diabła myślą.
Dobrym przykładem może być tutaj próba użycia niepodpisanego bajtu jako samobrotującej się pętli. Dla tych z was, którzy nie rozumieją ostatniego zdania, jak do diabła nazywacie siebie programistą.
DC
źródło