Czy liczba całkowita jest zbyt często używana jako typ danych?

9

Czy większość programistów aplikacji używa podpisanych liczb całkowitych w miejscach, w których naprawdę chcą używać liczb całkowitych bez znaku? Robię to cały czas, podobnie jak moi współpracownicy. Nie widziałem wielu innych obszernych baz kodu (innych niż Delphi VCL), a przykłady w Internecie zwykle używają liczb całkowitych. Podczas gdy programiści VCL używają własnych typów danych (co byłoby najbardziej leniwym sposobem na zadeklarowanie zmiennych).

Coś takiego wydaje się trochę przerażające w takim kodzie

TStuffRec = record
   recordID : Integer;
   thingID : Integer;
   otherThingID : Integer;
end;

kiedy można to zapisać jako

TStuffRec = record
   recordID : Cardinal;
   thingID : Cardinal;
   otherThingID : Cardinal;
end;

Funkcjonalnie te rekordy prawie zawsze działają tak samo (i mam nadzieję, że będą nadal działać tak samo, nawet w 64-bitowym Delphi). Ale bardzo duże liczby będą miały problemy z konwersją.

Ale są też wady używania niepodpisanych int. Wynika to głównie z tego, jak denerwujące jest mieszanie tych dwóch.

Prawdziwe pytanie brzmi: czy jest to rzecz, o której naprawdę się myśli lub która jest uwzględniana w najlepszych praktykach? Czy zwykle zależy to od programisty?

Peter Turner
źródło
5
Peter, szukasz tylko odpowiedzi specyficznych dla Delphi?
Adam Lear
3
@Anna Zrozumienie, jak działają typy danych Delphi, zapewniłoby najbardziej doskonałą odpowiedź. Jestem dość pewien, że programiści C mogliby zrozumieć i odpowiedzieć na to pytanie.
Peter Turner

Odpowiedzi:

9

Jednym z powodów, dla których nie używam tak często liczb całkowitych bez znaku w Delphi, jest to, że mogą powodować problemy po zmieszaniu z podpisanymi liczbami całkowitymi. Oto jeden, który mnie raz ugryzł:

for i := 0 to List.Count - 1 do
  //do something here

Miałem izadeklarowane jako liczba całkowita bez znaku, (mimo wszystko, jest to wskaźnik do listy, która rozpoczyna się od 0, to nigdy nie musi być ujemna, prawda?), Ale kiedy List.Countbyło 0, to nie zwarcie pętli zgodnie z oczekiwaniami, ponieważ 0 - 1ocenia na naprawdę wysoką liczbę dodatnią. Ups!

Pomiędzy potencjalnymi problemami związanymi z bezpieczeństwem związanymi z mieszaniem liczb całkowitych ze znakiem i bez znaku oraz problemami z zasięgiem (jeśli będziesz potrzebować liczb dodatnich większych niż high(signed whatever), jest całkiem prawdopodobne, że również będziesz potrzebować liczb dodatnich większych high(unsigned whatever), więc poruszasz się do następnego większego rozmiaru zamiast przełączania z podpisanego na niepodpisany o tym samym rozmiarze jest zwykle poprawną czynnością). Naprawdę nie znalazłem zbyt wielu zastosowań dla liczb całkowitych bez znaku, gdy reprezentuję większość danych.

Mason Wheeler
źródło
2
Nieco powiązane, jednym z głównych zagrożeń związanych z użyciem typu danych, który jest potencjalnie mniejszy niż to konieczne (w przeciwieństwie do zwykłego niepodpisanego vs. podpisanego) jest to, że jeśli warunek wyjścia jest większy niż planowano, można faktycznie uzyskać nieskończoną pętlę gdy licznik przepełnia się raz po raz. Z perspektywy czasu wydaje się to głupie, ale kiedyś napisałem program, który miał zapętlać każdą możliwą wartość bajtu i zajęło mi to około 15 minut, by w końcu przekonać się, że nie jest możliwe wykonanie licznika bajtów.
Aaronaught
@Aaronaught: Not in Delphi. (Przynajmniej nie, chyba że zrobisz coś głupiego, jak wyłączenie wbudowanego sprawdzania przepełnienia.) Skończysz z wyjątkiem, gdy licznik się przepełni, zamiast nieskończonej pętli. To wciąż błąd, ale łatwiej go wyśledzić.
Mason Wheeler,
Skoro tak mówisz. Zawsze wyłączałem sprawdzanie przepełnienia w Delphi; po niekończącym się bombardowaniu fałszywymi pozytywami z takich rzeczy, jak kody mieszające i sumy kontrolne, po prostu całkowicie zrezygnowałem z tej „funkcji”. Ale przypuszczam, że masz rację, wychwyciłby ten konkretny błąd.
Aaronaught
@Aaronaught: Cóż, tak, chciałbyś to wyłączyć dla takich rzeczy, jak kody skrótu i ​​sumy kontrolne, które są specjalnie zaprojektowane do przepełnienia i zawinięcia. Ale w przypadku obliczeń ogólnego przeznaczenia, które nie są zaprojektowane tak, aby się przepełniać i owijać, jest to ważna funkcja bezpieczeństwa, a jej wyłączenie przypomina trochę jazdę bez pasa bezpieczeństwa.
Mason Wheeler,
Być może zapomniałeś, ale w starszych wersjach Delphi błędy sprawdzania przepełnienia i dyrektywy kompilatora były niesamowicie błędne. Pamiętam, jak wielokrotnie zdejmowałem włosy po tym, jak zobaczyłem, jak debugger zatrzymuje się bezpośrednio w środku bloku {$ O -} / {$ O +}, aby wesoło zgłosić przepełnienie. Po pewnym czasie nie mogłem już tego znieść i po prostu wyłączyłem go globalnie. Znowu tak, wychwyciłby ten problem, ale nadal nie sądzę, aby był wart liczby fałszywych trafień. Oczywiście każdemu swojemu!
Aaronaught
3

Szczerze mówiąc, zwykle używam liczb całkowitych z przyzwyczajenia. Przyzwyczaiłem się, że oferują zakresy wystarczająco duże dla większości sytuacji i pozwalają na wartości ujemne (takie jak -1). Rzeczywiście, wiele razy użycie bajtów / słów / skrótów byłoby bardziej odpowiednie. Teraz myśląc o tym, mogę skupić się na tych miejscach:

  • Perspektywiczny. Rozmiar Tilemap jest ograniczony do 192x192 kafelków, więc mógłbym użyć bajtu do adresowania kafelków i pętli. Ale jeśli rozmiar mapy zostanie zwiększony, będę musiał przejść przez każde użycie i zastąpić go np. Słowem. Kiedy muszę zezwolić na obiekty poza mapą, musiałbym wrócić jeszcze raz, aby przejść na smallint.

  • Pętle Często piszę pętlę „od i: = 0 do Count-1”, co dzieje się, gdy „i” jest bajtem, a Count = 0 oznacza, że ​​pętla działa od 0 do 255. Nie, żebym tego chciał.

  • Uniforming. Łatwiej jest zapamiętać i zastosować „var i: integer;” niż zatrzymać się w każdym przypadku i pomyśleć „Hm .. tutaj mamy do czynienia z zakresem 0..120 .. bajt .. nie, czekaj, możemy potrzebować -1 dla niezainicjowanego .. skrótu .. czekaj .. co jeśli 128 to za mało .. Arrgh! " lub „Dlaczego jest to małe miejsce w tym miejscu, a nie skrót?”

  • Łączenie Kiedy muszę połączyć dwie lub więcej klas razem, mogą one używać różnych typów danych do swoich celów, użycie szerszych typów pozwala pominąć niepotrzebne konwersje.

  • -1. Nawet gdy wartości mieszczą się w zakresie 0..n-1, często muszę ustawić wartość „brak wartości / nieznana / niezainicjowana / pusta”, co jest powszechną praktyką -1.

Korzystanie z liczb całkowitych pozwala pominąć wszystkie te problemy, zapomnieć o optymalizacji niskiego poziomu tam, gdzie nie jest to potrzebne, przejść na wyższy poziom i skupić się na bardziej realnych problemach.

PS Kiedy używam innych typów?

  • Liczniki, nigdy nie są ujemne i są tylko do odczytu poza swoją klasą.
  • Ze względu na wydajność / pamięć wymuszaj stosowanie krótszych typów danych w niektórych miejscach.
Kromster
źródło
1

Najlepszą praktyką jest użycie typu danych, który odpowiada potrzebom wykorzystywanych danych (dane oczekiwane).

Przykłady C #: Gdybym tylko musiał obsługiwać od 0 do 255, użyłbym bajtu.

Gdybym musiał wesprzeć 1 000 000 negatywnych i pozytywnych, to int.

Większy niż 4,2 miliarda, a następnie użyj długiego.

Wybierając odpowiedni typ, program będzie korzystał z optymalnej ilości pamięci, a różne typy używają różnych ilości pamięci.

Oto odwołanie do C # int z MSDN.

int 
 -2,147,483,648 to 2,147,483,647
 Signed 32-bit integer

uint 
 0 to 4,294,967,295
 Unsigned 32-bit integer

long 
 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
 Signed 64-bit integer

ulong 
 0 to 18,446,744,073,709,551,615
 Unsigned 64-bit integer
Jon Raynor
źródło
Czy w języku C # (lub .net w ogóle) długie i długie byłyby 128 bitami na maszynie 128-bitowej? Ponieważ w Delphi Integertyp danych to 32 bity na maszynie 32-bitowej i najwyraźniej będą to 64 bity na maszynie 64-bitowej.
Peter Turner,
1
@Peter Turner: Nie, w C # intto tylko skrót System.Int32, bez względu na to, na jakiej maszynie działa kod.
nikie
@nikie, czy to jest po prostu type int System.Int32coś takiego? Czy można to łatwo zmienić w przyszłej wersji frameworka?
Peter Turner,
@Peter Turner / nikie (sizeof (int) .ToString ()); ==> Zwraca 4 (sizeof (Int64) .ToString ()); ==> Zwraca 8 W moim 64-bitowym systemie operacyjnym Windows. Jako nikie, statystyki, int jest naprawdę sprawiedliwy, a Int32.
Jon Raynor,
1
Należy zauważyć, że nie wszystkie typy są zgodne ze specyfikacją Common Language Specification . uintjest jednym z takich niezgodnych typów, co oznacza, że ​​nie należy go używać w publicznie dostępnym interfejsie API, aby uniknąć zerwania możliwości korzystania z tego interfejsu API w językach .NET innych niż język, w którym biblioteka jest napisana. Z tego też powodu platforma .NET Sam API używa intgdzie uintby to zrobił.
Adam Lear
1

Niepodpisane typy liczb całkowitych powinny być używane tylko do reprezentowania liczb kardynalnych w językach, w których reprezentują liczby kardynalne. Ze względu na sposób, w jaki działały komputery z systemem C, niepodpisane typy liczb całkowitych zachowywały się jako elementy pierścieni algebraicznych mod-2 ^ n (co oznacza, że ​​przepełnione obliczenia „zawiną się” przewidywalnie), a język określa, że ​​w wielu przypadkach takie typy są wymagane, by zachowywały się jak abstrakcyjne pierścienie algebraiczne, nawet jeśli takie zachowanie byłoby niezgodne z zachowaniem liczb głównych lub liczb matematycznych.

Jeśli platforma w pełni wspierała osobne typy dla liczb głównych i pierścieni algebraicznych, sugerowałbym, że liczby główne powinny być przetwarzane przy użyciu typów liczb głównych (i rzeczy, które muszą się zawijać przy użyciu typów pierścieni). Takie typy mogą nie tylko przechowywać liczby dwa razy większe niż typy podpisane, ale metoda otrzymująca parametr takiego typu nie musi sprawdzać, czy jest ujemna.

Biorąc jednak pod uwagę względny brak typów liczb kardynalnych, zazwyczaj najlepiej jest po prostu używać liczb całkowitych do reprezentowania zarówno liczb matematycznych, jak i liczb głównych.

supercat
źródło