Dlaczego dzielenie liczb całkowitych w języku C # zwraca liczbę całkowitą, a nie liczbę zmiennoprzecinkową?

138

Czy ktoś wie, dlaczego dzielenie liczb całkowitych w C # zwraca liczbę całkowitą, a nie liczbę zmiennoprzecinkową? Jaka jest idea? (Czy to tylko dziedzictwo C / C ++?)

W C #:

float x = 13 / 4;   
//== operator is overridden here to use epsilon compare
if (x == 3.0)
   print 'Hello world';

Wynik tego kodu byłby:

'Hello world'

Ściśle mówiąc, nie ma czegoś takiego jak dzielenie liczb całkowitych (dzielenie z definicji jest operacją, która daje liczbę wymierną, liczby całkowite to bardzo mały podzbiór).

BanditoBunny
źródło
49
ponieważ jest to integerpodział, a nie floating pointpodział.
Hunter McMillen
musi (w VB.Net) to jest realizowane inaczej w naturalny sposób matematyczny, gdzie cały wynik operacji dzielenia jest liczbą niewymierną.
BanditoBunny
3
Myślę, że masz na myśli racjonalne liczby . Zobacz wikipedię : Dzielenie dwóch liczb całkowitych może spowodować powstanie reszty. Aby zakończyć dzielenie reszty, system liczbowy został rozszerzony o ułamki lub liczby wymierne, jak są one bardziej nazywane.
crashmstr
6
To jest powód, dla którego nie jestem fanem „kopiowania składni” w językach. Pochodzę z języka VB myśląc „C # to .NET”, a nie „C # jest jak C”. Chyba mój błąd, ale w tym przypadku wolę sposób VB. Jeśli napotkali problem z wygenerowaniem błędu kompilatora podczas korzystania z niezainicjowanych typów prostych (nie dostajesz nawet ostrzeżenia w C), to dlaczego nie ostrzec Cię, gdy przypisujesz dzielenie liczb całkowitych do zmiennej zmiennoprzecinkowej?
darda,

Odpowiedzi:

102

Podczas gdy nowi programiści często popełniają ten błąd, wykonując dzielenie liczb całkowitych, gdy w rzeczywistości zamierzali używać dzielenia zmiennoprzecinkowego, w praktyce dzielenie liczb całkowitych jest bardzo powszechną operacją. Jeśli zakładasz, że ludzie rzadko go używają i że za każdym razem, gdy robisz dzielenie, zawsze musisz pamiętać o rzucaniu na zmienne punkty, to się mylisz.

Po pierwsze, dzielenie liczb całkowitych jest nieco szybsze, więc jeśli potrzebujesz tylko wyniku w postaci liczby całkowitej, chciałbyś użyć bardziej wydajnego algorytmu.

Po drugie, istnieje wiele algorytmów wykorzystujących dzielenie całkowitoliczbowe i jeśli wynikiem dzielenia byłaby zawsze liczba zmiennoprzecinkowa, musiałbyś za każdym razem zaokrąglić wynik. Jednym z przykładów z czubka mojej głowy jest zmiana podstawy liczby. Obliczanie każdej cyfry polega na dzieleniu liczby całkowitej wraz z resztą, a nie na dzieleniu liczby zmiennoprzecinkowej.

Z tych (i innych powiązanych) powodów dzielenie liczb całkowitych daje w wyniku liczbę całkowitą. Jeśli chcesz uzyskać dzielenie zmiennoprzecinkowe dwóch liczb całkowitych, musisz po prostu pamiętać o rzutowaniu jednej na double/ float/ decimal.

Servy
źródło
5
W VB.Net .Net architekci podjęli inną decyzję: / - zawsze dzielenie typu float, \ - dzielenie liczb całkowitych, więc jest to trochę niespójne, chyba że weźmie się pod uwagę dziedzictwo C ++;
BanditoBunny
4
Możesz określić w czasie kompilacji, czy /operator będzie wykonywał dzielenie liczb całkowitych, czy zmiennoprzecinkowych (chyba że używasz dynamicznego). Jeśli trudno ci to rozgryźć, ponieważ robisz tak dużo w tym jednym wierszu, sugerowałbym podzielenie tego wiersza na kilka wierszy, aby łatwiej było ustalić, czy operandy są liczbami całkowitymi, czy zmiennoprzecinkowymi. Przyszli czytelnicy Twojego kodu prawdopodobnie to docenią.
Servy
5
Osobiście uważam za problematyczne, że zawsze muszę myśleć, jakie zmienne dzielę, uważam to za marnotrawstwo mojej uwagi.
BanditoBunny
8
@pelesl Ponieważ zrobienie tego, dla którego astronomiczna liczba programów byłaby złamana, byłaby ogromną przełomową zmianą, mogę z całą pewnością powiedzieć, że nigdy nie nastąpi to w C #. To jest coś, co trzeba zrobić od pierwszego dnia w danym języku lub wcale.
Servy
2
@Servy: Jest wiele takich rzeczy w C, C ++ i C #. Osobiście uważam, że C # byłby lepszym językiem, gdyby istniał inny operator do dzielenia liczb całkowitych i aby uniknąć sytuacji, w której legalny kod daje zadziwiające zachowanie, int/intoperator był po prostu nielegalny [z diagnostyką określającą, że kod musi rzutować operand lub używać inny operator, w zależności od tego, jakie zachowanie było pożądane]. Gdyby istniała inna dobra sekwencja znaczników do dzielenia liczb całkowitych, można by zrezygnować z używania /do tego celu, ale nie wiem, co byłoby praktyczne.
supercat
79

Zobacz specyfikację języka C # . Istnieją trzy typy operatorów dzielenia

  • Dzielenie liczb całkowitych
  • Dzielenie zmiennoprzecinkowe
  • Dzielenie dziesiętne

W Twoim przypadku mamy podział typu Integer z następującymi zasadami:

Dzielenie zaokrągla wynik w kierunku zera, a wartością bezwzględną wyniku jest największa możliwa liczba całkowita, która jest mniejsza niż wartość bezwzględna ilorazu dwóch operandów. Wynik jest równy zero lub dodatni, gdy dwa operandy mają ten sam znak i zero lub ujemny, gdy dwa operandy mają przeciwne znaki.

Myślę, że powodem, dla którego C # używa tego typu dzielenia liczb całkowitych (niektóre języki zwracają wynik zmiennoprzecinkowy) jest sprzęt - dzielenie liczb całkowitych jest szybsze i prostsze.

Sergey Berezovskiy
źródło
Które języki zwracają pływające wyniki? @SergeyBerezovskiy
Ilaria
42

Każdy typ danych może przeciążać każdego operatora. Jeśli zarówno licznik, jak i mianownik są liczbami całkowitymi, typ liczby całkowitej wykona operację dzielenia i zwróci typ całkowity. Jeśli chcesz podzielić zmiennoprzecinkowe, musisz rzutować jedną lub więcej liczb na typy zmiennoprzecinkowe przed ich podzieleniem. Na przykład:

int x = 13;
int y = 4;
float x = (float)y / (float)z;

lub, jeśli używasz literałów:

float x = 13f / 4f;

Pamiętaj, że zmiennoprzecinkowe nie są dokładne. Jeśli zależy Ci na dokładności, użyj zamiast tego czegoś takiego jak typ dziesiętny.

Steven Doggart
źródło
1
+1 za wzmiankę, że tylko jeden wyraz musi być zmiennoprzecinkowy, aby dokonać podziału zmiennoprzecinkowego.
Xynariz
Oczywiście twoje stwierdzenie o precyzji jest słuszne w kontekście uczenia się i sprawiania, że ​​zrozumienie go nie jest skomplikowane. Ponieważ w naszej pracy musimy być tak precyzyjni, jak to tylko możliwe, nadal chcę wyjaśnić dokładność: zgodnie z IEE 754-1985 MOŻESZ uzyskać dokładny wynik (chociaż w większości tak nie jest). Dokładny wynik można uzyskać, gdy wartości obliczeń zostały wcześniej dokładnie przedstawione, a wynik jest - mówiąc najprościej - sumą potęg 2. Nawet jeśli poleganie na tej precyzji w tych szczególnych przypadkach nie jest najlepszą praktyką.
L. Monty,
Dodatek do dokładności: Możliwość uzyskania dokładnego wyniku drastycznie się poprawia, ponieważ wynik jest bliski 1 lub -1. Może być trochę mylące, że ta zdolność nadal pozostaje 0, ponieważ istnieją nieskończone liczby i skończona liczba wyników, które można dokładnie przedstawić. :)
L. Monty
1
@ L.Monty, dzięki za poruszenie tego. Dowiedziałem się więcej o punktach zmiennoprzecinkowych od czasu napisania tej odpowiedzi i wskazany przez ciebie punkt jest sprawiedliwy. Technicznie rzecz biorąc, nadal powiedziałbym, że moje stwierdzenie „zmiennoprzecinkowe nie są dokładne” jest akceptowalne w tym sensie, że tylko dlatego, że coś może być dokładne, czasami nie oznacza, że ​​jako całość jest precyzyjne. Jak mówią, zepsuty zegar ma rację dwa razy dziennie, ale nigdy nie nazwałbym żadnego precyzyjnym instrumentem. Właściwie jestem raczej zaskoczony, że ta część przeszkadzała ci bardziej niż moja sugestia, że ​​typ dziesiętny jest dokładny.
Steven Doggart
Ułamki dziesiętne są nieprecyzyjne z tych samych powodów, dla których są zmienne; po prostu liczba zmiennoprzecinkowa to podstawa-2, a liczba dziesiętna to podstawa-10. Na przykład typ dziesiętny nie może dokładnie zawierać dokładnej wartości 1/3.
Steven Doggart
11

Ponieważ nie używasz żadnego sufiksu, literały 13i 4są interpretowane jako liczby całkowite:

Podręcznik :

Jeśli dosłowne ma sufiks, ma pierwszy z tych typów, w której jego wartość może być reprezentowane: int, uint, long, ulong.

Tak więc, ponieważ deklarujesz 13jako liczbę całkowitą, zostanie wykonany podział całkowity:

Podręcznik :

W przypadku operacji w postaci x / y w celu wybrania określonej implementacji operatora jest stosowane rozpoznawanie przeciążenia operatora binarnego. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest typem zwracanym operatora.

Wstępnie zdefiniowane operatory dzielenia są wymienione poniżej. Wszystkie operatory obliczają iloraz x i y.

Dzielenie całkowite:

int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);

I tak następuje zaokrąglenie w dół:

Dzielenie zaokrągla wynik w kierunku zera, a wartością bezwzględną wyniku jest największa możliwa liczba całkowita, która jest mniejsza niż wartość bezwzględna ilorazu dwóch operandów. Wynik jest równy zero lub dodatni, gdy dwa operandy mają ten sam znak i zero lub ujemny, gdy dwa operandy mają przeciwne znaki.

Jeśli wykonasz następujące czynności:

int x = 13f / 4f;

Otrzymasz błąd kompilatora, ponieważ dzielenie zmiennoprzecinkowe ( /operator 13f) powoduje powstanie liczby zmiennoprzecinkowej, której nie można niejawnie rzutować na wartość int.

Jeśli chcesz, aby dzielenie było dzieleniem zmiennoprzecinkowym, musisz sprawić, że wynik będzie zmiennoprzecinkowy:

float x = 13 / 4;

Zauważ, że nadal będziesz dzielił liczby całkowite, które niejawnie zostaną rzutowane na zmiennoprzecinkowe: wynik będzie 3.0. Aby jawnie zadeklarować operandy jako zmiennoprzecinkowe, używając fsufiksu ( 13f, 4f).

CodeCaster
źródło
+1 za wyjaśnienie, że możesz otrzymać odpowiedź jako liczbę zmiennoprzecinkową, ale nadal wykonywać dzielenie liczb całkowitych. Ponadto innym popularnym sposobem wymuszania dzielenia zmiennoprzecinkowego jest pomnożenie pierwszego członu dzielenia przez 1.0.
Xynariz
9

To tylko podstawowa operacja .

Pamiętaj, kiedy nauczyłeś się dzielić. Na początku rozwiązaliśmy 9/6 = 1 with remainder 3.

9 / 6 == 1  //true
9 % 6 == 3 // true

/ -Operator w połączeniu z% -operator są używane do pobierania tych wartości.

L. Monty
źródło
7

Może się przydać:

double a = 5.0/2.0;   
Console.WriteLine (a);      // 2.5

double b = 5/2;   
Console.WriteLine (b);      // 2

int c = 5/2;   
Console.WriteLine (c);      // 2

double d = 5f/2f;   
Console.WriteLine (d);      // 2.5
eozten
źródło
Spróbuj dodać kilka wyjaśnień do swojej odpowiedzi
NetStarter
Ostatnie wyrażenie da 2.5, nie 2.
Lasse V. Karlsen
Tak, błędna pisownia. Dzięki.
eozten
7

Wynik zawsze będzie typu, który ma większy zakres licznika i mianownika. Wyjątki są bajtowe i krótkie, które dają int (Int32).

var a = (byte)5 / (byte)2;  // 2 (Int32)
var b = (short)5 / (byte)2; // 2 (Int32)
var c = 5 / 2;              // 2 (Int32)
var d = 5 / 2U;             // 2 (UInt32)
var e = 5L / 2U;            // 2 (Int64)
var f = 5L / 2UL;           // 2 (UInt64)
var g = 5F / 2UL;           // 2.5 (Single/float)
var h = 5F / 2D;            // 2.5 (Double)
var i = 5.0 / 2F;           // 2.5 (Double)
var j = 5M / 2;             // 2.5 (Decimal)
var k = 5M / 2F;            // Not allowed

Nie ma niejawnej konwersji między typami zmiennoprzecinkowymi a typami dziesiętnymi, więc dzielenie między nimi jest niedozwolone. Musisz jawnie rzutować i zdecydować, który z nich chcesz (Decimal ma większą precyzję i mniejszy zakres w porównaniu do typów zmiennoprzecinkowych).

zm
źródło