Dlaczego dzielenie dwóch int nie daje właściwej wartości, gdy jest przypisane do double?

110

Jak to się dzieje w poniższym fragmencie

int a = 7;
int b = 3;
double c = 0;
c = a / b;

ckończy się na wartości 2 zamiast 2,3333, jak można by się spodziewać. Jeśli ai bsą podwojone, odpowiedź brzmi 2.333. Ale na pewno c skoro już jest podwójna, powinna była działać z liczbami całkowitymi?

Więc dlaczego int/int=doublenie działa?

Jahoe
źródło
Możliwy duplikat wyniku Division jest zawsze zerowy
phuclv

Odpowiedzi:

161

Dzieje się tak, ponieważ używasz wersji dzielenia liczb całkowitych operator/, która zajmuje 2 intsekundy i zwraca int. Aby użyć doublewersji, która zwraca a double, co najmniej jeden z ints musi być jawnie rzutowany na plik double.

c = a/(double)b;
Chad La Guardia
źródło
9
Wolałbym jawnie przekonwertować zarówno ai baby doublepo prostu dla jasności, ale to naprawdę nie ma znaczenia.
John Dibling,
31
Ponieważ pytanie jest otagowane w C ++, wolałbym zobaczyć static_cast <> zamiast rzutowania w C.
Martin York,
16
Osobiście uważam, że rzutowanie w stylu C jest wyraźniejsze (rzutowanie w większości innych popularnych języków odbywa się w stylu C). static_cast<>zawsze wydawało mi się rozwlekłe. W przypadku pierwotnych, nie ma naprawdę żadnego niebezpieczeństwa dostania static_cast<>i reinterpret_cast<>pomieszane.
Chad La Guardia
6
@ Tux-D: Do rzutów arytmetycznych? Wolałbym tego unikać static_casti zamiast tego używać rzutowania w stylu C. Nie ma tu żadnej korzyści z używania rzutowań w stylu C ++ i zaśmiecają kod o wiele bardziej niż rzutowania w stylu C. Rzutowanie arytmetyczne jest dokładnie tym kontekstem, w którym rzuty w stylu C są doskonale odpowiednie i właściwie bardziej odpowiednie niż inne rzuty.
AnT
19
Czasami możesz przechytrzyć ludzi "bez obsady w stylu C", pisząc double(b). Nie zawsze zdają sobie sprawę, że jest to konwersja, ponieważ wygląda to tak samo, jak jawne wywołanie konstruktora.
Steve Jessop,
12

Oto ona:

a) Dzielenie dwóch ints powoduje zawsze dzielenie liczb całkowitych. Więc wynikiem a/bw Twoim przypadku może być tylko plikint .

Jeśli chcesz zachować ai bjako ints, ale w pełni je podzielić, musisz rzucić przynajmniej jedną z nich na podwojenie: (double)a/blub a/(double)blub (double)a/(double)b.

b) cjest double, więc może przyjąć takie intwartości w ASSIGNMENT: the intautomatycznie przekształca się doublei przypisanec .

c) Pamiętaj, że przy przypisywaniu najpierw= obliczane jest wyrażenie po prawej stronie (zgodnie z regułą (a) powyżej i bez uwzględnienia zmiennej po lewej stronie =), a następnie przypisywane do zmiennej po lewej stronie =(zgodnie z ( b) powyżej). Myślę, że to uzupełnia obraz.

nplatis
źródło
11

Z nielicznymi wyjątkami (przychodzi mi do głowy tylko jeden), C ++ określa całe znaczenie wyrażenia (lub wyrażenia podrzędnego) na podstawie samego wyrażenia. Nie ma znaczenia, co zrobisz z wynikami wyrażenia. W twoim przypadku w wyrażeniu a / bnie ma doublew zasięgu wzroku; wszystko jest int. Więc kompilator używa dzielenia liczb całkowitych. Dopiero po uzyskaniu wyniku rozważa, co z nim zrobić i konwertuje go na double.

James Kanze
źródło
3
Jedynym wyjątkiem, o którym mogę pomyśleć, jest wybranie przeciążenia funkcji podczas pobierania wskaźnika - wartość &funcnamezależy od typu, na który ją rzutujesz.
Steve Jessop
2
@Steve Jessop To jedyny wyjątek, który również przychodzi mi do głowy. (Ale biorąc pod uwagę rozmiar i złożoność standardu, nie chciałbym przysięgać, że nie przegapiłem żadnego.)
James Kanze
6

cjest doublezmienną, ale przypisywana jej intwartość jest wartością, ponieważ wynika z dzielenia dwóch ints, co daje „dzielenie całkowite” (porzucenie reszty). Więc to, co dzieje się w linii, c=a/bjest

  1. a/b jest oceniane, tworząc tymczasowy typ int
  2. wartość tymczasowa jest przypisywana cpo konwersji na typ double.

Wartość a/bjest określana bez odniesienia do kontekstu (przypisanie do double).

Fred Foo
źródło
6

Kiedy dzielisz dwie liczby całkowite, wynik będzie liczbą całkowitą, niezależnie od tego, że przechowujesz ją w liczbie podwójnej.

Alok Save
źródło
5

W języku C ++ na wynik podwyrażenia nigdy nie ma wpływu otaczający kontekst (z nielicznymi wyjątkami). To jedna z zasad, których język dokładnie przestrzega. Wyrażenie c = a / bzawiera niezależne wyrażenie podrzędne a / b, które jest interpretowane niezależnie od wszystkiego poza tym podwyrażeniem. Język nie obchodzi, że później przypiszesz wynik do pliku double.a / bjest dzieleniem całkowitym. Cokolwiek innego nie ma znaczenia. Zobaczysz przestrzeganie tej zasady w wielu rogach specyfikacji języka. To trochę jak działa C ++ (i C).

Jednym z przykładów wyjątku, o którym wspomniałem powyżej, jest przypisanie / inicjalizacja wskaźnika funkcji w sytuacjach przeciążenia funkcji

void foo(int);
void foo(double);

void (*p)(double) = &foo; // automatically selects `foo(fouble)`

Jest to jeden kontekst, w którym lewa strona przypisania / inicjalizacji wpływa na zachowanie prawej strony. (Ponadto inicjalizacja odniesienia do tablicy zapobiega zanikowi typu tablicy, co jest kolejnym przykładem podobnego zachowania). We wszystkich innych przypadkach prawa strona całkowicie ignoruje lewą stronę.

Mrówka
źródło
4

/Operatora może być używany do dzielenia liczby całkowitej lub zmiennoprzecinkowej podziału. Dajesz mu dwa operandy całkowite, więc wykonuje dzielenie całkowitoliczbowe, a następnie wynik jest zapisywany jako podwójny.

Vicky
źródło
2

Jest to technicznie zależne od języka, ale prawie wszystkie języki traktują ten temat tak samo. Gdy występuje niezgodność typów między dwoma typami danych w wyrażeniu, większość języków będzie próbować rzutować dane po jednej stronie= aby dopasować dane po drugiej stronie, zgodnie z zestawem wstępnie zdefiniowanych reguł.

Podczas dzielenia dwóch liczb tego samego typu (liczby całkowite, liczby podwójne itp.) Wynik będzie zawsze tego samego typu (więc „int / int” zawsze da w wyniku int).

W tym przypadku masz, double var = integer result który rzuca wynik w postaci liczby całkowitej na podwójny po obliczeniu, w którym to przypadku dane ułamkowe są już utracone. (większość języków wykonuje to rzutowanie, aby zapobiec niedokładnościom typu bez zgłaszania wyjątku lub błędu).

Jeśli chcesz, aby wynik był podwójny, będziesz chciał stworzyć sytuację, w której masz double var = double result

Najłatwiej to zrobić, wymuszając podwojenie wyrażenia po prawej stronie równania:

c = a/(double)b

Dzielenie liczby całkowitej i podwójnej spowoduje rzutowanie liczby całkowitej na podwójną (zwróć uwagę, że podczas obliczeń matematycznych kompilator często „upcast” do najbardziej specyficznego typu danych, aby zapobiec utracie danych).

Po upcastingu askończy się jako dublet, a teraz masz podział na dwie pary. Stworzy to pożądany podział i przypisanie.

PONOWNIE, proszę zauważyć, że jest to specyficzne dla języka (i może nawet być specyficzne dla kompilatora), jednak prawie wszystkie języki (z pewnością wszystkie te, które przychodzą mi do głowy) traktują ten przykład identycznie.

matthewdunnam
źródło
To pytanie jest oznaczone jako [C ++], a standard C ++ dokładnie określa, jak to działa. Nie jestem pewien, co masz na myśli, mówiąc „specyficzny dla języka” i na pewno nie jest specyficzny dla kompilatora, zakładając, że nie są włączone żadne rozszerzenia kompilatora.
John Dibling,
Również niepoprawne jest stwierdzenie, że „podwójna zmienna = wynik całkowity, który rzutuje podwójną zmienną w dół do liczby int”. Double nie jest rzutowane na int. Wynik int jest konwertowany na double.
John Dibling,
Zezwalałem na możliwość rozszerzenia kompilatora (tak naprawdę miałem kiedyś ten problem, w którym moje środowisko „błędnie rzutowało” wyniki i nie mogłem zrozumieć dlaczego). Rezultat jest specyficzny dla języka, ponieważ w niektórych językach nie obowiązują te same zasady rzutowania. Nie uważałem, że był to tag specyficzny dla C ++. Masz rację co do komentarza „podwójna zmienna = wynik całkowity”. Zredagowano, aby to odzwierciedlić. Dziękuję Ci!
matthewdunnam
0

Ważną rzeczą jest to, że jeden z elementów obliczeń powinien być typu float-double. Następnie, aby uzyskać podwójny wynik, musisz rzucić ten element, jak pokazano poniżej:

c = static_cast<double>(a) / b;

lub c = a / static_cast (b);

Lub możesz go utworzyć bezpośrednio:

c = 7.0 / 3;

Zauważ, że jeden z elementów obliczenia musi mieć „.0”, aby wskazać podział typu float-double przez liczbę całkowitą. W przeciwnym razie, mimo że zmienna c będzie podwójna, wynik również będzie równy zero.

TheArquitect
źródło
Jaka jest twoja odpowiedź, że żadna z pozostałych 9 odpowiedzi nie jest jeszcze obecna?
bolov