W jakich przypadkach typy danych „uint” i „short” są lepiej dopasowane niż standardowe int (32)?

24

Rozumiem różnice w zdolnościach i wartościach, które mogą reprezentować, ale wydaje się, że ludzie zawsze używają, Int32niezależnie od tego, czy jest to właściwe. Wydaje się, że nikt nigdy nie używa wersji bez znaku ( uint), mimo że przez większość czasu pasuje ona lepiej, ponieważ opisuje wartość, która nie może być ujemna (być może reprezentuje identyfikator rekordu bazy danych). Ponadto wydaje się, że nikt nigdy nie używa, short/Int16niezależnie od wymaganej pojemności wartości.

Obiektywnie, istnieją przypadki, w których lepiej użytku uintlub short/Int16a jeśli tak, to jakie one są?

Alternatex
źródło
13
Popularność nie zawsze jest opłacalnym miernikiem do oceny decyzji projektowych oprogramowania. To, że praktyka jest popularna, nie oznacza, że ​​jest to odpowiednia praktyka do konkretnego zastosowania lub nawet, że jest to dobra praktyka.
Robert Harvey,
1
Myślę, że krótka odpowiedź jest taka, że ​​programiści przyzwyczaili się do semantyki ze znakiem i skłonni są ją przyjmować, nawet w przypadku typów niepodpisanych (a zatem semantyki bez znaku). Większość ludzi zakłada, że ​​programista jest leniwy lub niewykształcony, ale programista może być bardzo wykształcony i bardzo ostrożny i chce uniknąć subtelnych pułapek. Jeśli chcesz, spójrz na soundoftware.ac.uk/c-pitfall-unsigned i anteru.net/2010/05/17/736 .
Theodoros Chatzigiannakis
W liczbach bez znaku znak jest więcej nullniż dodatni lub ujemny. Jeśli myślisz o tym jako o czymś, co nigdy nie może być negatywne lub zawsze jest pozytywne, będziesz zaskoczony (i często zły) na wyniki, ponieważ tak naprawdę nie działa w ten sposób, zwłaszcza w porównaniu z / odejmowanym do / z podpisanych wartości.
Adam D. Ruppe,
1
Z mojego doświadczenia wynika, że ​​wielu programistów, którzy kiedykolwiek programowali w języku C, ma tendencję do dbania o bajty, nawet w dzisiejszych czasach, o GB pamięci i przestrzeni dyskowej.
user1451111

Odpowiedzi:

25

Podejrzewam, że masz na myśli perspektywę pokolorowaną własnymi doświadczeniami, w której nie pracowałeś z ludźmi, którzy właściwie używają typów integralnych. Może to być częste zjawisko, ale z mojego doświadczenia wynika, że ​​ludzie często używają ich poprawnie.

Korzyścią jest miejsce w pamięci i czas procesora, być może także przestrzeń we / wy, w zależności od tego, czy typy są kiedykolwiek przesyłane przewodowo, czy na dysk. Niepodpisane typy zapewniają sprawdzanie kompilatora, aby upewnić się, że nie wykonasz pewnych operacji, które są niemożliwe, a także rozszerzenie dostępnego zakresu przy jednoczesnym zachowaniu mniejszego rozmiaru w celu zwiększenia wydajności tam, gdzie może to być konieczne.

Prawidłowe stosowanie jest jak można się spodziewać - zawsze wiesz na pewno, można z nich korzystać na stałe (nie ograniczają bez pewności czy ty będziesz żałować później).

  • Jeśli próbujesz przedstawić coś, co nigdy nie może być negatywne ( public uint NumberOfPeople), użyj typu bez znaku.
  • Jeśli próbujesz przedstawić coś, co nigdy nie może być rozsądnie większe niż 255 ( public byte DamagedToothCount), użyj bajtu.
  • Jeśli próbujesz przedstawić coś, co może być rozsądnie większe niż 255, ale nigdy znaczącej liczby tysięcy , użyj short ( public short JimmyHoffasBankBalance).
  • Jeśli próbujesz przedstawić coś, co może być setkami tysięcy, a nawet milionami, ale raczej nie osiągnie wielu miliardów, użyj int ( public int HoursSinceUnixEpoch).
  • Jeśli wiesz na pewno, ta liczba może mieć nieskończenie dużą wartość lub uważasz, że może mieć wiele miliardów, ale nie masz pewności, ile miliardów, długi jest najlepszym wyborem. Jeśli długość nie jest wystarczająco duża, masz interesujący problem i musisz zacząć szukać liczb o dowolnej precyzji ( public long MyReallyGreatAppsUserCountThisIsNotWishfulThinkingAtAll).

Takie rozumowanie można wykorzystać do wyboru między podpisanymi, niepodpisanymi i różnymi rozmiarami typów itp. Pomyśl tylko o logicznych prawdach danych, które reprezentujesz w rzeczywistości.

Jimmy Hoffa
źródło
11
+1, chociaż muszę wyjaśnić, że „numery” telefonu to nie liczby, ale ciągi cyfr i opcjonalnie formatowanie. Wygląda na to, że zdajesz sobie z tego sprawę, ale nie chcemy dawać złego przykładu, prawda? Również arbitralne ograniczanie zakresu pewnej wartości jest krótkowzrocznym antypatternem - intwszędzie, chyba że wiesz, że domena problemowa faktycznie ogranicza wartość - żaden bank nie chciałby ograniczyć rachunków do 33 tys. Funtów (i pomyśleć o dobrej zabawie kiedy to się przelewa…!).
amon
3
Nowy cel życiowy: Znaczne przekroczenie salda, które jest niższe niż integralny typ mojego konta bankowego.
recursion.ninja
11
Istnieją dobre powody, aby nie używać typów niepodpisanych w niektórych miejscach, na przykład, gdy arytmetyka jest mieszana między znakiem i niepodpisanym. Zobacz jakie są najlepsze praktyki dotyczące podpisania ints? .
19
Nie zgadzam się z uzasadnieniem tutaj. Typy niepodpisane są często błędem, ponieważ odejmowanie i porównywanie są nieoczekiwane, jeśli przyzwyczaiłeś się do ints (działają w spójny sposób, ale nie zawsze są „pozytywne”). Unikałbym ich, chyba że masz bardzo konkretny powód, aby z nich korzystać. Ponadto, dlaczego rozmiar ma znaczenie dla bajtu vs skrótu vs int? Często nawet nie oszczędzasz miejsca, ponieważ struktury będą wypełniać te elementy lub tablice do pewnego wyrównania. Użyłbym bajtu tylko wtedy, gdy rozmiar jest naprawdę ważny (mało prawdopodobne, szczególnie dla kodu C #, który widziałem) lub jeśli chcesz konkretnie zawinąć w 255 za coś.
Adam D. Ruppe,
4
„korzyścią jest miejsce w pamięci i czas procesora” ... Nie widzę żadnego przypadku, w którym małe typy faktycznie mogłyby zaoszczędzić czas procesora. Operacje na liczbach całkowitych nigdy nie są szybsze niż na maszynach wielkości maszyny , tzn. Jeśli chodzi o procesor, równie dobrze możesz go użyć long. Oszczędność pamięci może oczywiście pośrednio zaoszczędzić czas poprzez poprawę wydajności linii pamięci podręcznej i tak dalej, ale OTOH problemy z wyrównaniem małych typów mogą pośrednio kosztować czas.
lewej około
16

Jasne, są przypadki, w których lepiej jest użyć uintlub shortlub Int16. Jeśli wiesz, że twój zakres danych będzie pasował do ograniczeń tego typu zmiennej, możesz użyć tego typu.

W środowiskach o ograniczonej pamięci lub w przypadku dużych ilości obiektów sensowne może być użycie najmniejszej zmiennej wielkości. Na przykład, istnieje znacząca różnica wielkości dla tablicy milionów elementów ints w porównaniu z shorts.

Często nie dzieje się tak w rzeczywistym kodzie z jednego lub więcej z następujących powodów:

  • Wcześniejsze ograniczenia danych nie były znane
  • Istniała szansa, że ​​ograniczenia danych nie były stałe lub wiadomo, że prawdopodobnie zostaną zmienione
  • Istniała nadzieja na ponowne użycie funkcji z szerszym zakresem danych
  • Deweloper nie poświęcił czasu na przemyślenie ograniczeń
  • Oszczędności pamięci były nieznaczne, aby uzasadnić użycie mniejszego typu zmiennej

Jest o wiele więcej możliwych przyczyn, ale sprowadzają się one do tego: czas poświęcony na podjęcie decyzji i użycie innego typu zmiennej nie zapewnił wystarczającej korzyści, aby to uzasadnić.


źródło
8

W C, w kontekstach nieobjętych promocją liczb całkowitych , określono niepodpisane wartości, które będą zachowywać się jak elementy „zawijającego” abstrakcyjnego pierścienia algebraicznego (więc dla każdego X i Y, XY da unikalną wartość, która po dodaniu do Y da X ), podczas gdy typy liczb całkowitych ze znakiem były określone jako zachowujące się jak liczby całkowite, gdy obliczenia pozostawały w określonym zakresie, i pozwalały na wykonanie dowolnej czynności, gdy obliczenia wykraczały poza to. Jednak semantyka numeryczna w języku C # jest zupełnie inna. W sprawdzonym kontekście liczbowym zarówno podpisane, jak i niepodpisane typy zachowują się jak liczby całkowite, pod warunkiem, że obliczenia pozostają w zasięgu i rzucają, OverflowExceptiongdy nie; w niesprawdzonym kontekście oba zachowują się jak pierścienie algebraiczne.

Jedynym czasem, w którym ogólnie warto jest użyć dowolnego typu danych mniejszego niż Int32jest to konieczne, gdy konieczne jest pakowanie lub rozpakowywanie rzeczy w celu kompaktowego przechowywania lub transportu. Jeśli trzeba zapisać pół miliarda liczb dodatnich, a wszystkie będą w zakresie od 0 do 100, użycie jednego bajtu zamiast czterech spowoduje oszczędność 1,5 gigabajta pamięci. To duże oszczędności. Jeśli jednak fragment kodu musi przechowywać w sumie kilkaset wartości, uczynienie każdego z nich jednym bajtem zamiast czterech pozwoliłoby zaoszczędzić około 600 bajtów. Prawdopodobnie nie warto się tym przejmować.

Jeśli chodzi o typy niepodpisane, naprawdę przydatne są tylko podczas wymiany informacji lub dzielenia liczb na części. Jeśli na przykład trzeba wykonać matematykę na 96-bitowych liczbach całkowitych, prawdopodobnie łatwiej będzie wykonać obliczenia na grupach trzech 32-bitowych liczb całkowitych bez znaku, niż na grupach liczb całkowitych ze znakiem. W przeciwnym razie nie ma zbyt wielu sytuacji, w których zakres podpisanej 32- lub 64-bitowej wartości byłby nieodpowiedni, ale wystarczający byłby ten sam rozmiar niepodpisanej wartości.

supercat
źródło
4

Generalnie złym pomysłem jest używanie typów niepodpisanych, ponieważ przepełniają się w nieprzyjemny sposób. x = 5-6jest nagle w twoim kodzie czasową bombą zegarową. Tymczasem korzyści z niepodpisanych typów sprowadzają się do jednego dodatkowego kawałka precyzji, a jeśli ten kawałek jest dla ciebie tego wart, prawie na pewno powinieneś użyć większego typu.

Są przypadki użycia, w których mniejszy typ może mieć sens, ale chyba że martwisz się zużyciem pamięci lub koniecznością spakowania danych w celu zwiększenia wydajności transmisji lub pamięci podręcznej lub kilku innych problemów, zazwyczaj nie ma korzyści z używania mniejszego typu . Co więcej, na wielu architekturach korzystanie z tych typów jest wolniejsze, więc mogą one nałożyć niewielki koszt.

Jack Aidley
źródło
3
W C podpisane przepełnienie jest nawet gorsze niż przepełnienie niepodpisane (ponieważ jest niezdefiniowanym zachowaniem, podczas gdy niepodpisane ma zostać przeniesione jak licznik kilometrów). OTOH, niedopełniony / niedopełniony podpis jest w praktyce znacznie mniej powszechny niż niedomiar niepodpisany.
Kevin,
To prawda, ale podpisane przepełnienie jest zwykle bardziej oczywiste i przewidywalne.
Jack Aidley,
I na ogół zgadzają się, ale trzeba mieć świadomość, na przykład, że współczesne kompilatory mogą zoptymalizować i+1>ipod 1jeśli ijest podpisana, wraz z niezliczoną liczbą innych paskudne zachowanie. Niepodpisane przepełnienie może spowodować błąd w skrzynce narożnej. Podpisany przepełnienie może sprawić, że cały program będzie bez znaczenia .
Kevin
@JackAidley Jestem pewien, że to, co mówisz, nie ma sensu, ponieważ 5-6 daje ten sam wzór bitowy, bez względu na to, czy jest niepodpisany, czy nie.
Ingo
@Ingo: jak często patrzysz na wzory bitów? Liczy się znaczenie wzorca bitowego, a nie to, które bity są włączone lub wyłączone.
Jack Aidley,
2

Często zapomnianym i prawdopodobnie stycznym do twojego pytania, szczególnie w przypadku typów .NET, jest Zgodność z CLS . Nie wszystkie typy są dostępne dla wszystkich języków zbudowanych w .NET Framework.

Jeśli piszesz kod, który ma być używany przez języki inne niż C #, i chcesz, aby kod ten współpracował z jak największą liczbą języków .NET, musisz ograniczyć użycie tego typu do tych, które są zgodne z CLS.

Na przykład wczesne wersje VB.NET (7.0 i 7.1) nie obsługiwały liczb całkowitych bez znaku ( UInteger):

http://msdn.microsoft.com/en-us/library/aa903459(v=vs.71).aspx

Niezapisane liczby całkowite nie są zgodne z CLS, dlatego należy zachować ostrożność, jeśli nie masz pewności, kto będzie Twoim konsumentem biblioteki klas.

Kev
źródło