Patrząc na ten kod C #:
byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'
Wynik dowolnej matematyki wykonanej na typach byte
(lub short
) jest domyślnie rzutowany z powrotem na liczbę całkowitą. Rozwiązaniem jest jawne przeniesienie wyniku z powrotem do bajtu:
byte z = (byte)(x + y); // this works
Zastanawiam się, dlaczego? Czy to jest architektoniczne? Filozoficzny?
Mamy:
int
+int
=int
long
+long
=long
float
+float
=float
double
+double
=double
Dlaczego więc nie:
byte
+byte
=byte
short
+short
=short
?
Trochę tła: wykonuję długą listę obliczeń dla „małych liczb” (tj. <8) i przechowuję wyniki pośrednie w dużej tablicy. Korzystanie z tablicy bajtów (zamiast tablicy int) jest szybsze (z powodu trafień w pamięci podręcznej). Ale rozległe rzutowania bajtów rozłożone w kodzie sprawiają, że jest on o wiele bardziej nieczytelny.
c#
type-conversion
Robert Cartaino
źródło
źródło
byte1 | byte2
wcale nie traktuje ich jak liczb. To traktuje je dokładnie jako wzory bitów. Rozumiem twój punkt widzenia, ale tak się składa, że za każdym razem, gdy robiłem jakąkolwiek arytmetykę na bajtach w C #, faktycznie traktowałem je jako bity, a nie liczby, a takie zachowanie zawsze przeszkadza.Odpowiedzi:
Trzeci wiersz fragmentu kodu:
właściwie znaczy
Tak więc nie ma operacji + na bajtach, bajty są najpierw rzutowane na liczby całkowite, a wynikiem dodania dwóch liczb całkowitych jest (32-bitowa) liczba całkowita.
źródło
int
ma miejsce: stackoverflow.com/a/43578929/4561887Jeśli chodzi o „dlaczego tak się dzieje”, to dlatego, że nie ma żadnych operatorów zdefiniowanych przez C # dla arytmetyki z bajtem, sbyte, short lub ushort, tak jak mówili inni. Ta odpowiedź dotyczy powodów, dla których nie zdefiniowano tych operatorów.
Wierzę, że to w zasadzie ze względu na wydajność. Procesory mają natywne operacje do wykonywania arytmetyki z 32 bitami bardzo szybko. Automatyczne przekształcenie z wyniku na bajt może być wykonane automatycznie , ale skutkowałoby to karami wydajnościowymi w przypadku, gdy tak naprawdę nie chcesz tego zachowania.
Myślę, że jest to wspomniane w jednym z adnotowanych standardów C #. Szukam ...
EDYCJA: Irytujące, przejrzałem teraz specyfikację ECMA C # 2 z adnotacją, specyfikację MS C # 3 z adnotacją i specyfikację CLI adnotacji, i żadne z nich nie wspomina o tym, o ile widzę. Jestem pewien , że widziałem powód podany powyżej, ale jestem pod wrażeniem, jeśli wiem, gdzie. Przepraszamy, fani referencji :(
źródło
Myślałem, że już gdzieś to widziałem. Z tego artykułu The Old New Thing :
EDYCJA : Raymond broni zasadniczo podejścia C i C ++ pierwotnie przyjętego. W komentarzach broni faktu, że C # przyjmuje to samo podejście, ze względu na kompatybilność wsteczną języka.
źródło
DO#
ECMA-334 stwierdza, że dodawanie jest zdefiniowane jako legalne tylko dla int + int, uint + uint, long + long i ultrong + ulong (ECMA-334 14.7.4). Jako takie, są to operacje kandydujące do rozważenia w odniesieniu do 14.4.2. Ponieważ istnieją niejawne rzutowania z bajtu na int, uint, long i ultrong, wszystkie elementy funkcji dodawania są odpowiednimi elementami funkcji zgodnie z 14.4.2.1. Musimy znaleźć najlepszą domyślną obsadę według reguł w 14.4.2.3:
Rzutowanie (C1) na int (T1) jest lepsze niż rzutowanie (C2) na uint (T2) lub ulong (T2), ponieważ:
Rzutowanie (C1) na int (T1) jest lepsze niż rzutowanie (C2) na długi (T2), ponieważ istnieje ukryty rzut z int na długi:
Dlatego używana jest funkcja int + int, która zwraca wartość int.
Co jest bardzo długą drogą do stwierdzenia, że jest głęboko schowany w specyfikacji C #.
CLI
Interfejs CLI działa tylko na 6 typach (int32, native int, int64, F, O i &). (ECMA-335 partycja 3 sekcja 1.5)
Bajt (int8) nie jest jednym z tych typów i jest automatycznie konwertowany na int32 przed dodaniem. (ECMA-335 partycja 3 sekcja 1.6)
źródło
byte3 = byte1 And byte2
bez użycia rzutowania, ale bezskutecznie rzuci wyjątek środowiska wykonawczego, jeśli zwróciint1 = byte1 + byte2
wartość powyżej 255. Nie wiem, czy którykolwiek język zezwolibyte3 = byte1+byte2
i wyrzuci wyjątek, gdy przekroczy 255, ale nie wyrzuci wyjątku, jeśliint1 = byte1+byte2
zwróci wartość z zakresu 256–510.Odpowiedzi wskazujące na nieefektywność dodawania bajtów i obcięcia wyniku z powrotem do bajtu są niepoprawne. Procesory x86 mają instrukcje specjalnie zaprojektowane do działania na liczbach całkowitych w ilościach 8-bitowych.
W rzeczywistości dla procesorów x86 / 64 wykonywanie operacji 32-bitowych lub 16-bitowych jest mniej wydajne niż operacji 64-bitowych lub 8-bitowych ze względu na bajt prefiksu argumentu, który należy zdekodować. Na maszynach 32-bitowych wykonywanie operacji 16-bitowych wiąże się z taką samą karą, ale nadal istnieją dedykowane kody dla operacji 8-bitowych.
Wiele architektur RISC ma podobne rodzime instrukcje wydajne słowo / bajt. Te, które na ogół nie mają wartości przechowywania-i-konwersji-do-podpisanej-o-pewnej długości.
Innymi słowy, ta decyzja musiała być oparta na percepcji typu bajtu, a nie z powodu leżącej u podstaw nieefektywności sprzętu.
źródło
byte
(ichar
), ale nie dlashort
którego semantycznie jest wyraźnie liczbą.Pamiętam, jak raz przeczytałem coś od Jona Skeeta (nie mogę tego teraz znaleźć, będę dalej szukać) o tym, jak bajt nie przeciąża operatora +. W rzeczywistości, podczas dodawania dwóch bajtów jak w twojej próbce, każdy bajt jest w rzeczywistości domyślnie konwertowany na liczbę całkowitą. Wynikiem tego jest oczywiście int. Teraz, DLACZEGO to zostało zaprojektowane w ten sposób, poczekam, aż sam Jon Skeet opublikuje :)
EDYCJA: Znaleziono! Świetne informacje na ten temat tutaj .
źródło
Wynika to z przelania i przenoszenia.
Jeśli dodasz dwie liczby 8-bitowe, mogą one przelać się na 9-ty bit.
Przykład:
Nie wiem na pewno, ale zakładam, że
ints
,longs
idoubles
mają więcej miejsca, ponieważ są dość duże. Są to także wielokrotności 4, które są bardziej wydajne w obsłudze komputerów, ponieważ szerokość wewnętrznej szyny danych wynosi 4 bajty lub 32 bity (64 bity stają się coraz bardziej powszechne). Bajt i skrót są nieco bardziej nieefektywne, ale mogą zaoszczędzić miejsce.źródło
Ze specyfikacji języka C # 1.6.7.5 7.2.6.2 Binarne promocje numeryczne konwertuje oba operandy na int, jeśli nie można go dopasować do kilku innych kategorii. Domyślam się, że nie przeciążyli operatora +, aby przyjąć bajt jako parametr, ale chcą, aby działał nieco normalnie, więc po prostu używają typu danych int.
Język C # Spec
źródło
Podejrzewam, że C # w rzeczywistości wywołuje
operator+
zdefiniowaneint
(co zwraca „int
chyba, że jesteś wchecked
bloku”) i pośrednio rzutuje oba twojebytes
/shorts
naints
. Dlatego zachowanie wydaje się niespójne.źródło
Prawdopodobnie była to praktyczna decyzja projektantów języków. W końcu int to Int32, 32-bitowa liczba całkowita ze znakiem. Ilekroć wykonujesz operację na liczbach całkowitych na typie mniejszym niż int, i tak zostanie ona przekonwertowana na 32-bitowy znak int przez większość dowolnego 32-bitowego procesora. To, w połączeniu z prawdopodobieństwem przepełnienia małych liczb całkowitych, prawdopodobnie przypieczętowało umowę. Pozwala to uniknąć ciągłego sprawdzania przekroczenia / niedopełnienia, a gdy końcowy wynik wyrażenia w bajtach będzie w zakresie, pomimo faktu, że na pewnym etapie pośrednim będzie poza zakresem, otrzymasz poprawną wynik.
Inna myśl: należałoby zasymulować przekroczenie / niedopełnienie tych typów, ponieważ nie wystąpiłoby to naturalnie na najbardziej prawdopodobnych docelowych procesorach. Po co się męczyć?
źródło
Jest to w przeważającej części moja odpowiedź na ten temat, postawiona najpierw na podobne pytanie tutaj .
Wszystkie operacje z liczbami całkowitymi mniejszymi niż Int32 są domyślnie zaokrąglane w górę do 32 bitów przed obliczeniem. Powodem, dla którego wynikiem jest Int32, jest po prostu pozostawienie go takim, jakim jest po obliczeniach. Jeśli zaznaczysz arytmetyczne kody MSIL, jedynym integralnym typem numerycznym, z którym działają, są Int32 i Int64. To „z założenia”.
Jeśli chcesz uzyskać wynik w formacie Int16, nie ma znaczenia, czy wykonasz rzutowanie kodu, czy kompilator (hipotetycznie) wyśle konwersję „pod maską”.
Na przykład, aby wykonać arytmetykę Int16:
Dwie liczby rozszerzyłyby się do 32 bitów, zostaną dodane, a następnie skrócone z powrotem do 16 bitów, tak właśnie planowało to MS.
Zaletą korzystania z krótkich (lub bajtów) jest przede wszystkim przechowywanie w przypadkach, gdy masz ogromne ilości danych (dane graficzne, przesyłanie strumieniowe itp.)
źródło
Dodawanie nie jest zdefiniowane dla bajtów. Więc są dodawane do int dla dodania. Dotyczy to większości operacji matematycznych i bajtów. (zwróć uwagę, że tak było kiedyś w starszych językach, zakładam, że tak jest dzisiaj).
źródło
Myślę, że jest to decyzja projektowa o tym, która operacja była bardziej powszechna ... Jeśli bajt + bajt = bajt, być może znacznie więcej osób będzie się przejmować koniecznością rzutowania na int, gdy wynik jest wymagany.
źródło
Z kodu .NET Framework:
Uprość dzięki .NET 3.5 i nowszym:
teraz możesz zrobić:
źródło
Testowałem wydajność między bajtem a liczbą całkowitą.
Z wartościami int:
Z wartościami bajtów:
Oto wynik:
bajt: 3,57s 157mo, 3,71s 171mo, 3,74s 168mo z procesorem ~ = 30%
int: 4,05s 298mo, 3,92s 278mo, 4,28 294mo z procesorem ~ = 27%
Wniosek:
bajt używa więcej procesora, ale kosztuje mniej pamięci i jest szybszy (być może dlatego, że do przydzielenia jest mniej bajtów)
źródło
Oprócz wszystkich innych świetnych komentarzy, pomyślałem, że dodam jeszcze jeden mały smakołyk. Wiele komentarzy zastanawiało się, dlaczego int, long i właściwie jakikolwiek inny typ liczbowy również nie przestrzega tej zasady ... zwraca „większy” typ w odpowiedzi na arytmatykę.
Wiele odpowiedzi miało związek z wydajnością (cóż, 32 bity są szybsze niż 8 bitów). W rzeczywistości liczba 8-bitowa jest nadal liczbą 32-bitową w procesorze 32-bitowym .... nawet jeśli dodasz dwa bajty, część danych, na których działa procesor, będzie 32-bitowa niezależnie od tego ... więc dodanie ints nie będzie być „szybszym” niż dodawanie dwóch bajtów ... to wszystko to samo dla procesora. TERAZ dodanie dwóch liczb wewnętrznych BĘDZIE szybsze niż dodanie dwóch długich sekwencji na 32-bitowym procesorze, ponieważ dodanie dwóch długich wymaga więcej mikroprocesorów, ponieważ pracujesz z liczbami szerszymi niż słowo procesorów.
Myślę, że podstawowy powód spowodowania, że arytmetyka bajtów powoduje ints, jest dość jasny i prosty: 8 bitów po prostu nie idzie bardzo daleko! : D Przy 8 bitach masz niepodpisany zakres 0-255. To nie jest dużo miejsca do pracy ... prawdopodobieństwo, że napotkasz ograniczenia bajtów, jest BARDZO wysokie, gdy używasz ich w arytmetyce. Jednak szansa, że zabraknie Ci bitów podczas pracy z intami, długimi, podwójnymi itp. Jest znacznie niższa ... wystarczająco niska, że bardzo rzadko spotykamy się z potrzebą więcej.
Automatyczna konwersja z bajtu na int jest logiczna, ponieważ skala bajtu jest tak mała. Automatyczna konwersja z int na long, float na double itp. Nie jest logiczna, ponieważ liczby te mają znaczną skalę.
źródło
byte - byte
powracaint
ani dlaczego nie rzuca nashort
...byte + byte
zwracaint
, ponieważ 255 + cokolwiek jest większe niż bajt może pomieścić, nie ma sensu, aby jakikolwiek bajt minus jakikolwiek inny bajt zwracał coś innego niż int z punktu widzenia spójności typu zwracanego.byte
odejmowanie zwróciłoby abyte
, a dodanie bajtu zwróciłoby ashort
(byte
+byte
zawsze będzie pasować do ashort
). Gdyby chodziło o spójność, jak mówisz, toshort
nadal wystarczyłoby dla obu operacji, a nieint
. Oczywiście jest wiele przyczyn, nie wszystkie z nich muszą być dobrze przemyślane. Lub podany poniżej powód wydajności może być dokładniejszy.