Warum gibt die Ganzzahldivision in C # eine Ganzzahl und keinen Gleitkommawert zurück?

131

Weiß jemand, warum die Ganzzahldivision in C # eine Ganzzahl und keinen Gleitkommawert zurückgibt? Was ist die Idee dahinter? (Ist es nur ein Erbe von C / C ++?)

In C #:

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

Ergebnis dieses Codes wäre:

'Hello world'

Genau genommen gibt es keine Ganzzahldivision (Division ist per Definition eine Operation, die eine rationale Zahl erzeugt, Ganzzahlen sind eine sehr kleine Teilmenge davon.)

BanditoBunny
quelle
46
weil es integerTeilung ist, nicht floating pointTeilung.
Hunter McMillen
es muss (in VB.Net) auf natürliche mathematische Weise anders implementiert werden, wobei das gesamte Ergebnis der Divisionsoperation eine irrationale Zahl ist.
BanditoBunny
3
Ich denke du meinst rationale Zahlen . Siehe Wikipedia : Das Teilen von zwei ganzen Zahlen kann zu einem Rest führen. Um die Aufteilung des Restes zu vervollständigen, wird das Zahlensystem um Brüche oder rationale Zahlen erweitert, wie sie allgemeiner genannt werden.
Crashmstr
6
Aus diesem Grund bin ich kein Fan von "Kopieren von Syntax" in Sprachen. Ich komme von VB und denke, "C # ist .NET", nicht "C # ist wie C". Mein Fehler, denke ich, aber in diesem Fall bevorzuge ich den VB-Weg. Wenn sie sich die Mühe gemacht haben, einen Compilerfehler zu generieren, wenn sie nicht initialisierte einfache Typen verwenden (Sie erhalten in C nicht einmal eine Warnung), warum Sie dann nicht warnen, wenn Sie einem Float eine Ganzzahldivision zuweisen?
Darda

Antworten:

96

Während es für neue Programmierer üblich ist, diesen Fehler bei der Durchführung einer Ganzzahldivision zu machen, wenn sie tatsächlich die Gleitkommadivision verwenden wollten, ist die Ganzzahldivision in der Praxis eine sehr häufige Operation. Wenn Sie davon ausgehen, dass die Leute es selten verwenden und dass Sie jedes Mal, wenn Sie eine Teilung durchführen, daran denken müssen, auf Gleitkommawerte zu setzen, irren Sie sich.

Zunächst einmal ist die Ganzzahldivision viel schneller. Wenn Sie also nur ein Ergebnis mit ganzen Zahlen benötigen, sollten Sie den effizienteren Algorithmus verwenden.

Zweitens gibt es eine Reihe von Algorithmen, die eine ganzzahlige Division verwenden. Wenn das Ergebnis der Division immer eine Gleitkommazahl wäre, müssten Sie das Ergebnis jedes Mal runden. Ein Beispiel auf der Oberseite meines Kopfes ist das Ändern der Basis einer Zahl. Die Berechnung jeder Ziffer umfasst die ganzzahlige Division einer Zahl zusammen mit dem Rest und nicht die Gleitkommadivision der Zahl.

Aus diesen (und anderen verwandten) Gründen führt die Ganzzahldivision zu einer Ganzzahl. Wenn Sie die Gleitkommadivision von zwei ganzen Zahlen erhalten möchten, müssen Sie nur daran denken, eine in ein double// floatumzuwandeln decimal.

Servieren
quelle
5
In VB.Net .Net haben Architekten eine andere Entscheidung getroffen: / - immer eine Float-Division, \ - Integer-Division, daher ist sie inkonsistent, es sei denn, Sie betrachten C ++ - Legacy;
BanditoBunny
4
Sie können zur Kompilierungszeit bestimmen, ob der /Operator eine Ganzzahl- oder Gleitkommadivision ausführt (es sei denn, Sie verwenden Dynamic). Wenn es für Sie schwierig ist , es herauszufinden, weil Sie so viel in dieser einen Zeile tun, würde ich vorschlagen, diese Zeile in mehrere Zeilen aufzuteilen, damit Sie leichter herausfinden können, ob die Operanden Ganzzahlen oder Gleitkommatypen sind. Zukünftige Leser Ihres Codes werden es wahrscheinlich zu schätzen wissen.
Servy
5
Ich persönlich finde es problematisch, dass ich immer darüber nachdenken muss, welche Variablen ich teile. Ich zähle es als verschwenderische Verwendung meiner Aufmerksamkeit.
BanditoBunny
8
@pelesl Da es eine große Veränderung wäre, das zu tun, für das eine astronomische Anzahl von Programmen kaputt gehen würde, kann ich mit voller Zuversicht sagen, dass dies in C # niemals passieren wird. So etwas muss vom ersten Tag an in einer Sprache oder gar nicht gemacht werden.
Servy
2
@Servy: In C, C ++ und C # gibt es viele ähnliche Dinge. Persönlich denke ich, dass C # eine bessere Sprache wäre, wenn es einen anderen Operator für die Ganzzahldivision gegeben hätte. Um zu vermeiden, dass legitimer Code ein erstaunliches Verhalten hervorruft, war der int/intOperator einfach illegal [mit einer Diagnose, die angibt, dass Code einen Operanden umwandeln oder den verwenden muss anderer Operator, je nachdem welches Verhalten gewünscht wurde]. Wäre eine andere gute Token-Sequenz für die Ganzzahldivision verfügbar, könnte es möglich sein, die Verwendung /für diesen Zweck zu verwerfen , aber ich weiß nicht, was praktisch wäre.
Supercat
77

Siehe C # -Spezifikation . Es gibt drei Arten von Abteilungsoperatoren

  • Ganzzahlige Division
  • Gleitkommadivision
  • Dezimalteilung

In Ihrem Fall haben wir eine Ganzzahldivision mit folgenden Regeln:

Die Division rundet das Ergebnis gegen Null, und der Absolutwert des Ergebnisses ist die größtmögliche Ganzzahl, die kleiner als der Absolutwert des Quotienten der beiden Operanden ist. Das Ergebnis ist Null oder positiv, wenn die beiden Operanden das gleiche Vorzeichen haben, und Null oder negativ, wenn die beiden Operanden entgegengesetzte Vorzeichen haben.

Ich denke, der Grund, warum C # diese Art der Division für ganze Zahlen verwendet (einige Sprachen geben ein schwebendes Ergebnis zurück), ist Hardware - die Division von ganzen Zahlen ist schneller und einfacher.

Sergey Berezovskiy
quelle
Welche Sprachen geben ein schwebendes Ergebnis zurück? @ SergeyBerezovskiy
Ilaria
40

Jeder Datentyp kann jeden Operator überlasten. Wenn sowohl der Zähler als auch der Nenner Ganzzahlen sind, führt der Ganzzahltyp die Divisionsoperation aus und gibt einen Ganzzahltyp zurück. Wenn Sie eine Gleitkommadivision wünschen, müssen Sie eine oder mehrere der Zahlen in Gleitkommatypen umwandeln, bevor Sie sie teilen. Zum Beispiel:

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

oder, wenn Sie Literale verwenden:

float x = 13f / 4f;

Beachten Sie, dass Gleitkommazahlen nicht präzise sind. Wenn Sie Wert auf Präzision legen, verwenden Sie stattdessen den Dezimaltyp.

Steven Doggart
quelle
1
+1 für die Erwähnung, dass nur ein Term float sein muss, um eine Gleitkommadivision durchzuführen.
Xynariz
Offensichtlich ist Ihre Aussage über Präzision das Richtige im Zusammenhang mit dem Lernen und damit es nicht zu kompliziert zu verstehen ist. Da wir bei unserer Arbeit so präzise wie möglich sein müssen, möchte ich noch die Präzision klären: Gemäß IEE 754-1985 KÖNNEN Sie ein genaues Ergebnis erzielen (obwohl dies meistens nicht der Fall ist). Sie können ein genaues Ergebnis erhalten, wenn die Berechnungswerte zuvor genau dargestellt wurden und das Ergebnis - einfach gesagt - eine Summe von Potenzen von 2 ist. Auch wenn es in diesen Sonderfällen nicht empfehlenswert ist, sich auf diese Genauigkeit zu verlassen.
L. Monty
Zusätzliche Präzision: Die Wahrscheinlichkeit, ein genaues Ergebnis zu erhalten, verbessert sich drastisch, da das Ergebnis nahe bei 1 oder -1 liegt. Es könnte ein wenig verwirrend sein, dass diese Wahrscheinlichkeit immer noch 0 bleibt, da es unendlich viele Zahlen und eine endliche Anzahl von Ergebnissen gibt, die genau dargestellt werden können. :)
L. Monty
1
@ L.Monty, danke, dass du das angesprochen hast. Ich habe mehr über Gleitkommazahlen gelernt, seit ich diese Antwort geschrieben habe, und der Punkt, den Sie machen, ist fair. Technisch würde ich immer noch sagen, dass meine Aussage „Gleitkommawerte sind nicht präzise“ akzeptabel ist, in dem Sinne, dass nur weil etwas genau sein kann, es manchmal nicht bedeutet, dass es insgesamt präzise ist. Wie sie sagen, ist eine kaputte Uhr zweimal am Tag richtig, aber ich würde niemals eine Präzisionsinstrument nennen. Ich bin eigentlich ziemlich überrascht , dass der Teil ist , dass Sie mehr als mein Vorschlag gestört , dass der Typ decimal ist präzise.
Steven Doggart
Dezimalstellen sind aus den gleichen Gründen wie Floats ungenau. Es ist nur so, dass Floats Base-2 und Dezimalstellen Base-10 sind. Beispielsweise kann ein Dezimaltyp den genauen Wert von 1/3 nicht genau halten.
Steven Doggart
11

Da Sie kein Suffix verwenden, werden die Literale 13und 4als Ganzzahl interpretiert:

Handbuch :

Wenn die wörtliche kein Suffix hat, hat es die erste dieser Art in dem sein Wert dargestellt werden kann int, uint, long, ulong.

Da Sie also 13eine Ganzzahl deklarieren , wird eine Ganzzahldivision durchgeführt:

Handbuch :

Für eine Operation der Form x / y wird eine Überlastungsauflösung für binäre Operatoren angewendet, um eine bestimmte Operatorimplementierung auszuwählen. Die Operanden werden in die Parametertypen des ausgewählten Operators konvertiert, und der Typ des Ergebnisses ist der Rückgabetyp des Operators.

Die vordefinierten Divisionsoperatoren sind unten aufgeführt. Die Operatoren berechnen alle den Quotienten aus x und y.

Ganzzahlige Teilung:

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

Und so kommt es zu einer Abrundung:

Die Division rundet das Ergebnis gegen Null, und der Absolutwert des Ergebnisses ist die größtmögliche Ganzzahl, die kleiner als der Absolutwert des Quotienten der beiden Operanden ist. Das Ergebnis ist Null oder positiv, wenn die beiden Operanden das gleiche Vorzeichen haben, und Null oder negativ, wenn die beiden Operanden entgegengesetzte Vorzeichen haben.

Wenn Sie Folgendes tun:

int x = 13f / 4f;

Sie erhalten einen Compilerfehler, da eine Gleitkommadivision (der /Operator von 13f) zu einem Gleitkomma führt, das nicht implizit in int umgewandelt werden kann.

Wenn Sie möchten, dass die Division eine Gleitkommadivision ist, müssen Sie das Ergebnis zu einem Float machen:

float x = 13 / 4;

Beachten Sie, dass Sie immer noch Ganzzahlen teilen, die implizit in float umgewandelt werden: Das Ergebnis ist 3.0. Um die Operanden explizit als float zu deklarieren, verwenden Sie das fSuffix ( 13f, 4f).

CodeCaster
quelle
+1 für die Erklärung, dass Sie die Antwort als Float haben können, aber dennoch eine ganzzahlige Division durchführen. Eine andere übliche Methode, um die Gleitkommadivision zu erzwingen, besteht darin, den ersten Term der Division mit zu multiplizieren 1.0.
Xynariz
8

Es ist nur eine grundlegende Operation .

Denken Sie daran, als Sie gelernt haben, sich zu teilen. Am Anfang haben wir gelöst 9/6 = 1 with remainder 3.

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

Der / -Operator in Kombination mit dem% -Operator wird verwendet, um diese Werte abzurufen.

L. Monty
quelle
6

Könnte nützlich sein:

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
quelle
Bitte versuchen Sie einige Erklärungen für Ihre Antwort
hinzuzufügen
Der letzte Ausdruck produzieren wird 2.5, nicht 2.
Lasse V. Karlsen
Ja, falsch geschrieben. Vielen Dank.
eozten
4

Das Ergebnis ist immer vom Typ, der den größeren Bereich des Zählers und des Nenners hat. Die Ausnahmen sind Byte und Short, die int (Int32) erzeugen.

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

Es gibt keine implizite Konvertierung zwischen Gleitkommatypen und dem Dezimaltyp, sodass eine Trennung zwischen ihnen nicht zulässig ist. Sie müssen explizit umwandeln und entscheiden, welche Sie möchten (Decimal hat eine höhere Genauigkeit und einen kleineren Bereich im Vergleich zu Gleitkommatypen).

zm
quelle