Niejawne reguły konwersji typów w operatorach C ++

167

Chcę wiedzieć, kiedy powinienem rzucać. Jakie są niejawne reguły konwersji typów w C ++ podczas dodawania, mnożenia itp. Na przykład

int + float = ?
int * float = ?
float * int = ?
int / float = ?
float / int = ?
int / int = ?
int ^ float = ?

i tak dalej...

Czy wyrażenie zawsze będzie oceniane jako dokładniejszy typ? Czy zasady różnią się w przypadku języka Java? Proszę, popraw mnie, jeśli źle sformułowałem to pytanie.

Matt Montag
źródło
15
Należy pamiętać, że ^XOR.
GManNickG,
15
@int ^ float = błąd kompilacji :)
Serge Dundich,

Odpowiedzi:

223

W C ++ operatory (dla typów POD) zawsze działają na obiektach tego samego typu.
Tak więc, jeśli nie są takie same, jeden zostanie awansowany, aby pasował do drugiego.
Typ wyniku operacji jest taki sam jak operandy (po konwersji).

If either is      long          double the other is promoted to      long          double
If either is                    double the other is promoted to                    double
If either is                    float  the other is promoted to                    float
If either is long long unsigned int    the other is promoted to long long unsigned int
If either is long long          int    the other is promoted to long long          int
If either is long      unsigned int    the other is promoted to long      unsigned int
If either is long               int    the other is promoted to long               int
If either is           unsigned int    the other is promoted to           unsigned int
If either is                    int    the other is promoted to                    int
Both operands are promoted to int

Uwaga. Minimalny rozmiar operacji to int. Tak więc short/ charsą promowane intprzed wykonaniem operacji.

We wszystkich twoich wyrażeniach intjest promowany do a floatprzed wykonaniem operacji. Wynikiem operacji jest plik float.

int + float =>  float + float = float
int * float =>  float * float = float
float * int =>  float * float = float
int / float =>  float / float = float
float / int =>  float / float = float
int / int                     = int
int ^ float =>  <compiler error>
Martin York
źródło
1
„Minimalny rozmiar operacji to int.” - To byłoby bardzo dziwne (a co z architekturami, które wydajnie obsługują operacje char / short?) Czy to naprawdę jest w specyfikacji C ++?
Rafał Dowgird
3
@Rafal: Tak. int ma być najbardziej wydajnym typem liczby całkowitej do działania na określonej platformie. char musi zawsze mieć wartość 1, ale short może mieć taki sam rozmiar jak int.
Martin York,
1
@ Rafał: tak, to jest bardzo dziwne i jest w standardzie. W wielu przypadkach opisywana architektura mogłaby używać swojego super wydajnego chartypu. Jeśli wartość char + charjest przypisana do a char, to może po prostu wykonać arytmetykę chari na przykład zawijać. Ale jeśli wynik jest przypisany do int, musi wykonać arytmetykę w typie wystarczająco dużym, aby uzyskać poprawny wynik, gdy jest większy niż CHAR_MAX.
Steve Jessop,
2
Chcę tylko podkreślić fakt, że int jest promowany jako int bez znaku !!! Zmagałem się z błędami przez wiele dni, ponieważ miałem wrażenie, że oba zostaną promowane do wartości int lub long, aby ewentualny wynik negatywny nie spowodował niedomiaru / zawijania.
nitsas
10
Przykład problemu „ int zostaje promowany do typu unsigned int ”: ((int) 4) - ((unsigned int) 5)spowoduje, że 4294967295dla 32-bitowych int i 32-bitowych liczb int bez znaku.
nitsas
33

Operacje arytmetyczne obejmujące floatwyniki w float.

int + float = float
int * float = float
float * int = float
int / float = float
float / int = float
int / int = int

Aby uzyskać bardziej szczegółową odpowiedź. Zobacz, co mówi sekcja §5 / 9 ze standardu C ++

Wiele operatorów binarnych, które oczekują operandów typu arytmetycznego lub wyliczeniowego, powoduje konwersje i zwraca typy wyników w podobny sposób. Celem jest uzyskanie wspólnego typu, który jest jednocześnie typem wyniku .

Ten wzorzec nazywa się zwykłymi konwersjami arytmetycznymi, które są zdefiniowane w następujący sposób:

- Jeśli jeden z operandów jest typu long double, drugi zostanie przekonwertowany na long double.

- W przeciwnym razie, jeśli jeden z operandów jest podwójny, drugi zostanie przekonwertowany na podwójny.

- W przeciwnym razie, jeśli jeden z operandów jest zmiennoprzecinkowy, drugi zostanie przekonwertowany na zmiennoprzecinkowy.

- W przeciwnym razie promocje całkowe (4.5) będą wykonywane na obu operandach. 54)

- Następnie, jeśli jeden z argumentów jest długi bez znaku, drugi zostanie przekonwertowany na długość bez znaku.

- W przeciwnym razie, jeśli jeden operand jest typu long int, a drugi int bez znaku, to jeśli long int może reprezentować wszystkie wartości typu unsigned int, to unsigned int zostanie przekonwertowany na długi int; w przeciwnym razie oba operandy zostaną przekonwertowane na unsigned long int.

- W przeciwnym razie, jeśli jeden z operandów jest długi, drugi zostanie przekonwertowany na długi.

- W przeciwnym razie, jeśli jeden z operandów jest bez znaku, drugi zostanie przekonwertowany na bez znaku.

[Uwaga: w przeciwnym razie jedynym pozostałym przypadkiem jest to, że oba operandy są int]

Nawaz
źródło
3
... pod warunkiem, że drugi typ nie jest doubleani long double.
CB Bailey,
1
@Charles: Zgadza się. Zacytowałem odpowiednią sekcję ze standardu, aby wyjaśnić dalej.
Nawaz,
Czy zatem liczby całkowite można zawsze przekonwertować na zmiennoprzecinkowe bez utraty danych? (np. zerując wykładnik i używając wszystkiego do mantysy)?
Marco A.
1
Ta odpowiedź jest nieaktualna. Zaproponuj aktualizację. W szczególności, long longa unsigned longnie tutaj.
chux - Przywróć Monikę
@MarcoA. 32-bitowy floatnie ma wystarczającej liczby bitów w mantysie (24 bity dla IEEE-754 ) dla 32-bitowego int, więc może wystąpić utrata danych. Wersja 64-bitowa doublepowinna wystarczyć.
Mark Ransom
17

Ponieważ inne odpowiedzi nie mówią o regułach w C ++ 11, oto jedna. Ze standardu C ++ 11 (szkic n3337) §5 / 9 (podkreślono różnicę):

Ten wzorzec nazywa się zwykłymi konwersjami arytmetycznymi , które są zdefiniowane w następujący sposób:

- Jeśli którykolwiek operand jest typu wyliczenia w określonym zakresie, nie są wykonywane żadne konwersje; jeśli drugi operand nie ma tego samego typu, wyrażenie jest źle sformułowane.

- Jeśli jeden z operandów jest typu long double, drugi zostanie przekonwertowany na long double.

- W przeciwnym razie, jeśli jeden z operandów jest podwójny, drugi zostanie zamieniony na podwójny.

- W przeciwnym razie, jeśli jeden z operandów jest zmiennoprzecinkowy, drugi zostanie przekonwertowany na zmiennoprzecinkowy.

- W przeciwnym razie promocje całkowe będą wykonywane na obu operandach. Następnie do promowanych operandów należy zastosować następujące zasady:

- Jeśli oba operandy mają ten sam typ, dalsza konwersja nie jest potrzebna.

- W przeciwnym razie, jeśli oba operandy mają typy liczb całkowitych ze znakiem lub oba mają typy całkowite bez znaku, operand z typem mniejszego stopnia konwersji liczb całkowitych zostanie przekonwertowany na typ operandu o wyższym stopniu.

- W przeciwnym razie, jeśli operand, który ma liczbę całkowitą bez znaku, ma rangę większą lub równą rangi typu drugiego operandu, operand z typem liczby całkowitej ze znakiem należy przekonwertować na typ operandu z typem liczby całkowitej bez znaku.

- W przeciwnym razie, jeśli typ argumentu z typem liczby całkowitej ze znakiem może reprezentować wszystkie wartości typu argumentu z typem liczby całkowitej bez znaku, operand z typem liczby całkowitej bez znaku zostanie przekonwertowany na typ argumentu z typem liczby całkowitej ze znakiem.

- W przeciwnym razie oba operandy będą konwertowane na typ liczby całkowitej bez znaku odpowiadającej typowi argumentu z typem liczby całkowitej ze znakiem.

Zobacz tutaj listę, która jest często aktualizowana.

legends2k
źródło
1
Te zasady były takie same we wszystkich wersjach C ++, z wyjątkiem wyliczeń o określonym zakresie, które zostały oczywiście dodane w C ++ 11
MM
6

Ta odpowiedź jest w dużej mierze skierowana na komentarz @ RafałDowgird:

„Minimalny rozmiar operacji to int.” - To byłoby bardzo dziwne (a co z architekturami, które wydajnie obsługują operacje char / short?) Czy to naprawdę jest w specyfikacji C ++?

Należy pamiętać, że w standardzie C ++ obowiązuje niezwykle ważna zasada „as-if”. Patrz sekcja 1.8: Wykonanie programu:

3) Przepis ten jest czasami nazywany zasadą „jak gdyby”, ponieważ implementacja może zignorować jakiekolwiek wymagania normy, o ile wynik jest taki, jakby wymaganie było przestrzegane, o ile można to określić na podstawie obserwowalnych zachowanie programu.

Kompilator nie może ustawić int8-bitowego rozmiaru, nawet jeśli byłby najszybszy, ponieważ standard wymaga 16-bitowego minimum int.

Dlatego w przypadku teoretycznego komputera z superszybkimi 8-bitowymi operacjami, niejawna awans do intarytmetyki może mieć znaczenie. Jednak w przypadku wielu operacji nie można stwierdzić, czy kompilator faktycznie wykonał operacje z dokładnością an, inta następnie przekonwertował je na a, charaby przechowywać w zmiennej, czy też operacje były przez cały czas wykonywane w postaci znaku.

Weźmy na przykład pod uwagę unsigned char = unsigned char + unsigned char + unsigned char, gdzie dodawanie byłoby przepełnione (przyjmijmy wartość 200 dla każdego). Jeśli awansujesz na int, dostaniesz 600, które następnie zostanie pośrednio rzucone w dół na an unsigned char, co zawinie modulo 256, dając w ten sposób końcowy wynik 88. Jeśli nie zrobiłeś takich promocji, musiałbyś zawijać między pierwszymi dwa dodatki, co zmniejszyłoby problem z 200 + 200 + 200do 144 + 200, czyli 344, co redukuje do 88. Innymi słowy, program nie zna różnicy, więc kompilator może zignorować polecenie wykonania operacji pośrednich, intjeśli operandy mają niższy ranking niż int.

Dotyczy to ogólnie dodawania, odejmowania i mnożenia. Generalnie nie jest to prawdą w przypadku dzielenia lub modułu.

David Stone
źródło
4

Jeśli wykluczysz typy bez znaku, istnieje uporządkowana hierarchia: signed char, short, int, long, long long, float, double, long double. Po pierwsze, wszystko, co znajduje się przed int w powyższym, zostanie przekonwertowane na int. Następnie, w operacji binarnej, typ o niższej randze zostanie przekonwertowany na wyższy, a wyniki będą typem wyższego. (Zauważysz, że z hierarchii za każdym razem, gdy zaangażowany jest typ zmiennoprzecinkowy i typ całkowity, typ całkowity zostanie przekonwertowany na typ zmiennoprzecinkowy).

Unsigned nieco komplikuje sprawę: zaburza ranking, a część rankingu zostaje zdefiniowana jako implementacja. Z tego powodu najlepiej nie mieszać ze znakiem i bez znaku w tym samym wyrażeniu. (Wydaje się, że większość ekspertów C ++ unika unsigned, chyba że w grę wchodzą operacje bitowe. Tak przynajmniej zaleca Stroustrup).

James Kanze
źródło
3
Stroustrup może polecić to, co mu się podoba, ale użycie znaku umożliwiającego oznaczenie intliczby, która nigdy nie musi być ujemna, jest całkowitym zmarnowaniem pełnych 50% dostępnego zakresu. Na pewno nie jestem Stroustrupem, ale używam unsigneddomyślnie i signedtylko wtedy, gdy mam powód.
underscore_d
1
To wszystko dobrze, podkreślenie, aż do dnia, w którym będziesz musiał odjąć. Podstawowym problemem z liczbami bez znaku w C ++ jest to, że kiedy wykonujesz odejmowanie, pozostają one bez znaku. Załóżmy więc, że piszesz funkcję, aby sprawdzić, czy std :: vector jest w porządku. Możesz napisać, bool in_order(vector<T> vec) { for ( int i = 0; i < size() - 1; ++i) { if (vec[i + 1] < vec[i]) return false; } return true;a wtedy zirytowałbyś się, gdyby doszło do awarii dla pustych wektorów, ponieważ size () - 1 zwraca 18446744073709551615.
jorgbrown
3

Moje rozwiązanie do problemu dostał Waszyngton (zła odpowiedź), a potem zmienił jeden intdo long long inti dał AC (zaakceptować) . Wcześniej próbowałem to zrobić long long int += int * int, a po poprawieniu tego na long long int += long long int * int. Google, które wymyśliłem,

1. Konwersje arytmetyczne

Warunki konwersji typu:

Warunki spełnione ---> Konwersja

  • Każdy operand jest typu long double . ---> Inny operand jest konwertowany na typ long double .

  • Warunek poprzedzający nie został spełniony, a jeden z operandów jest typu double . ---> Inny operand jest konwertowany na typ double .

  • Warunki poprzedzające nie zostały spełnione, a jeden z operandów jest typu float . ---> Inny operand jest konwertowany do typu float .

  • Warunki poprzedzające nie zostały spełnione (żaden z operandów nie jest typu zmiennoprzecinkowego). ---> Promocje całkowe są wykonywane na operandach w następujący sposób:

    • Jeśli którykolwiek operand jest typu unsigned long , drugi operand jest konwertowany na typ unsigned long .
    • Jeśli poprzedni warunek nie jest spełniony, a jeden z operandów jest typu long, a drugi typu unsigned int , oba operandy są konwertowane na typ unsigned long .
    • Jeśli poprzednie dwa warunki nie są spełnione, a którykolwiek z operandów jest typu long , inny operand jest konwertowany na typ long .
    • Jeśli poprzednie trzy warunki nie są spełnione, a jeden z operandów jest typu unsigned int , drugi operand jest konwertowany na typ unsigned int .
    • Jeśli żaden z powyższych warunków nie jest spełniony, oba operandy są konwertowane na typ int .

2. Reguły konwersji liczb całkowitych

  • Promocje całkowite:

Typy całkowite mniejsze niż int są promowane, gdy wykonywana jest na nich operacja. Jeśli wszystkie wartości typu oryginalnego mogą być reprezentowane jako int, wartość mniejszego typu jest konwertowana na int; w przeciwnym razie jest konwertowany na bez znaku int. Promocje w postaci liczb całkowitych są stosowane jako część zwykłych konwersji arytmetycznych do niektórych wyrażeń argumentów; operandy jednoargumentowych operatorów +, - i ~; i operandy operatorów zmianowych.

  • Całkowity ranking konwersji:

    • Żadne dwa typy liczb całkowitych ze znakiem nie mogą mieć tej samej rangi, nawet jeśli mają tę samą reprezentację.
    • Pozycja typu liczby całkowitej ze znakiem powinna być większa niż pozycja dowolnego typu liczby całkowitej ze znakiem z mniejszą dokładnością.
    • Stopień long long intbędzie wyższy niż stopień long int, który będzie wyższy niż stopień int, który będzie wyższy niż stopień short int, który będzie wyższy niż stopień signed char.
    • Pozycja dowolnego typu liczby całkowitej bez znaku jest równa randze odpowiedniego typu liczby całkowitej ze znakiem, jeśli istnieje.
    • Pozycja dowolnego standardowego typu liczb całkowitych będzie większa niż pozycja dowolnego typu rozszerzonej liczby całkowitej o tej samej szerokości.
    • Ranga charjest równa randze signed chari unsigned char.
    • Pozycja dowolnego typu rozszerzonej liczby całkowitej ze znakiem w stosunku do innego typu rozszerzonej liczby całkowitej ze znakiem o tej samej dokładności jest zdefiniowana w ramach implementacji, ale nadal podlega innym regułom określania rangi konwersji liczb całkowitych.
    • Dla wszystkich typów całkowitych T1, T2 i T3, jeśli T1 ma wyższą rangę niż T2, a T2 ma wyższą rangę niż T3, to T1 ma wyższą rangę niż T3.
  • Zwykłe konwersje arytmetyczne:

    • Jeśli oba operandy mają ten sam typ, dalsza konwersja nie jest potrzebna.
    • Jeśli oba operandy są tego samego typu liczby całkowitej (ze znakiem lub bez znaku), operand o typie mniejszej liczby całkowitej konwersji jest konwertowany na typ operandu z wyższą rangą.
    • Jeśli operand, który ma typ liczby całkowitej bez znaku, ma rangę większą lub równą randze typu drugiego operandu, operand z typem liczby całkowitej ze znakiem jest konwertowany na typ operandu z typem liczby całkowitej bez znaku.
    • Jeśli typ operandu z typem liczby całkowitej ze znakiem może reprezentować wszystkie wartości typu operandu z typem liczby całkowitej bez znaku, operand z typem liczby całkowitej bez znaku jest konwertowany na typ operandu z typem liczby całkowitej ze znakiem.
    • W przeciwnym razie oba operandy są konwertowane na typ liczby całkowitej bez znaku odpowiadającego typowi operandu z typem liczby całkowitej ze znakiem. Określone operacje mogą dodawać lub modyfikować semantykę zwykłych operacji arytmetycznych.
garakchy
źródło
1

Cały rozdział 4 mówi o konwersjach, ale myślę, że powinieneś być zainteresowany głównie tymi:

4.5 Promocje całkowe [conv.prom]
Wartość r typu char, signed char, unsigned char, short int lub unsigned short int może zostać przekonwertowana na wartość r typu int, jeśli int może reprezentować wszystkie wartości typu źródłowego; w przeciwnym
razie źródłowa wartość r może zostać przekonwertowana na wartość r typu unsigned int.
Wartość r typu wchar_t (3.9.1) lub typ wyliczeniowy (7.2) można przekonwertować na wartość r pierwszego
z następujących typów, które mogą reprezentować wszystkie wartości typu podstawowego: int, unsigned int,
long lub unsigned długie.
Wartość r dla integralnego pola bitowego (9.6) można przekonwertować na wartość r typu int, jeśli int może reprezentować wszystkie
wartości pola bitowego; w przeciwnym razie można go przekonwertować na unsigned int, jeśli unsigned int może zastąpić
ponownie wysłał wszystkie wartości pola bitowego. Jeśli pole bitowe jest jeszcze większe, nie stosuje się do niego żadnej promocji integralnej. Jeśli pole
bitowe ma typ wyliczeniowy, jest traktowane jako każda inna wartość tego typu do celów promocyjnych.
Wartość r typu bool może zostać przekonwertowana na wartość r typu int, gdzie false staje się zerem, a true
staje się jedynką.
Te konwersje nazywane są integralnymi promocjami.

4.6 Promocja zmiennoprzecinkowa [conv.fpprom]
Wartość r typu float może zostać przekonwertowana na wartość r typu double. Wartość pozostaje niezmieniona.
Ta konwersja nazywa się promocją zmiennoprzecinkową.

Dlatego wszystkie konwersje z udziałem float - wynikiem jest float.

Tylko ten obejmujący oba int - wynik to int: int / int = int

BЈовић
źródło
1

Typ wyrażenia, gdy nie obie części są tego samego typu, zostanie przekonwertowany na największą z obu. Problem polega na tym, aby zrozumieć, który z nich jest większy od drugiego (nie ma to nic wspólnego z rozmiarem w bajtach).

W wyrażeniach, w których występuje liczba rzeczywista i liczba całkowita, liczba całkowita zostanie podniesiona do liczby rzeczywistej. Na przykład w int + float typ wyrażenia to float.

Druga różnica jest związana z możliwościami typu. Na przykład wyrażenie zawierające int i long int da wynik typu long int.

Baltasarq
źródło
2
To nie jest prawda. Na niektórych platformach a longjest „większe” niż a, floatale jaki jest typ long+ float?
CB Bailey,
1
-1: Co masz na myśli mówiąc „największy” ? Czy float jest większy niż int? Lub odwrotnie ?
Paul R
2
Dziękuję za twoje komentarze. Tak, tutaj rozmiar w bajtach nie jest w ogóle interesujący. Jak się okazuje, oczywiście zaznaczenie największego kursywą nie wystarczy, aby wyjaśnić odpowiedź. Zresztą nie ma sensu wyjaśniać tego głębiej, skoro teraz są inne, bardzo dokładne odpowiedzi.
Baltasarq,
-2

Caveat!

Konwersje następują od lewej do prawej.

Spróbuj tego:

int i = 3, j = 2;
double k = 33;
cout << k * j / i << endl; // prints 22
cout << j / i * k << endl; // prints 0
Habib
źródło
8
Nie wynika to z konwersji, ale z powodu pierwszeństwa operatorów. j + i * kprzyniesie 101.
gartenriese