Wird die Ganzzahl zu häufig als Datentyp verwendet?

9

Verwenden die meisten Anwendungsentwickler vorzeichenbehaftete Ganzzahlen an Stellen, an denen sie wirklich vorzeichenlose Ganzzahlen verwenden möchten? Ich mache es die ganze Zeit, meine Kollegen auch. Ich habe nicht viele andere umfangreiche Codebasen gesehen (außer der Delphi VCL) und Beispiele im Internet verwenden normalerweise Ganzzahlen. Während die VCL-Entwickler ihre eigenen Datentypen verwenden (was der nicht faulste Weg wäre, Variablen zu deklarieren).

Etwas an diesem Code scheint etwas schrecklich zu sein

TStuffRec = record
   recordID : Integer;
   thingID : Integer;
   otherThingID : Integer;
end;

wenn es geschrieben werden könnte als

TStuffRec = record
   recordID : Cardinal;
   thingID : Cardinal;
   otherThingID : Cardinal;
end;

Funktionell funktionieren diese Datensätze fast immer gleich (und funktionieren hoffentlich auch in 64-Bit-Delphi weiterhin gleich). Bei sehr großen Zahlen treten jedoch Konvertierungsprobleme auf.

Die Verwendung von Ints ohne Vorzeichen hat jedoch auch Nachteile. Hauptsächlich weil es ärgerlich ist, beides zu mischen.

Die eigentliche Frage ist, ob dies tatsächlich über Best Practices nachgedacht oder in diese aufgenommen wird. Ist es normalerweise nur Sache des Entwicklers?

Peter Turner
quelle
5
Peter, suchst du nur nach Delphi-spezifischen Antworten?
Adam Lear
3
@Anna Wenn Sie verstehen, wie Delphi-Datentypen funktionieren, erhalten Sie eine hervorragende Antwort. Ich bin mir ziemlich sicher, dass C-Programmierer diese Frage verstehen und beantworten können.
Peter Turner

Antworten:

9

Ein Grund, warum ich in Delphi nicht allzu oft vorzeichenlose Ganzzahltypen verwende, ist, dass sie Probleme verursachen können, wenn sie mit vorzeichenbehafteten Ganzzahlen gemischt werden. Hier ist eine, die mich einmal gebissen hat:

for i := 0 to List.Count - 1 do
  //do something here

Ich hatte ials vorzeichenlose Ganzzahl deklariert (schließlich ist es ein Index in einer Liste, die bei 0 beginnt, es muss nie negativ sein, oder?), Aber wenn List.Count0 war, würde es die Schleife nicht wie erwartet kurzschließen, weil 0 - 1bewertet zu einer wirklich hohen positiven Zahl. Hoppla!

Zwischen den potenziellen Sicherheitsproblemen beim Mischen von vorzeichenbehafteten und vorzeichenlosen Ganzzahlen und den Bereichsproblemen (wenn Sie positive Zahlen benötigen, die größer als sind high(signed whatever), ist es sehr wahrscheinlich, dass Sie auch positive Zahlen benötigen, die größer als high(unsigned whatever)sind, also bewegen Sie sich Bis zur nächstgrößeren Größe, anstatt von vorzeichenbehafteten zu vorzeichenlosen Größen derselben Größe zu wechseln, ist normalerweise die richtige Aktion.) Ich habe wirklich nicht zu viele Verwendungsmöglichkeiten für vorzeichenlose Ganzzahlen gefunden, wenn ich die meisten Daten darstelle.

Mason Wheeler
quelle
2
Etwas verwandt ist eines der Hauptrisiken bei der Verwendung eines Datentyps, der möglicherweise kleiner als erforderlich ist (im Gegensatz zu nur vorzeichenlos oder signiert), dass Sie tatsächlich eine Endlosschleife erhalten, wenn die Ausgangsbedingung größer ist als geplant wie der Zähler immer wieder überläuft. Im Nachhinein klingt es dumm, aber ich habe einmal ein Programm geschrieben, das jeden möglichen Bytewert durchlaufen sollte, und es dauerte ungefähr 15 Minuten, um mich schließlich davon zu überzeugen, dass es nicht möglich war, mit einem Bytezähler zu arbeiten.
Aaronaught
@ Aronaught: Nicht in Delphi. (Zumindest nicht, es sei denn, Sie tun etwas Dummes wie das Deaktivieren der integrierten Überlaufprüfung.) Sie erhalten eine Ausnahme, wenn der Zähler überläuft, anstatt einer Endlosschleife. Es ist immer noch ein Fehler, aber es ist viel einfacher, ihn aufzuspüren.
Mason Wheeler
Wenn du es sagst. Ich habe die Überlaufprüfung in Delphi immer deaktiviert. Nachdem ich endlos mit Fehlalarmen von Dingen wie Hash-Codes und Prüfsummen bombardiert worden war, gab ich dieses "Feature" einfach ganz auf. Aber ich nehme an, Sie haben Recht, es hätte diesen spezifischen Fehler aufgefangen.
Aaronaught
@Aaronaught: Nun ja, Sie möchten es für Dinge wie Hash-Codes und Prüfsummen deaktivieren, die speziell zum Überlaufen und Umwickeln entwickelt wurden. Für Allzweckberechnungen, die nicht zum Überlaufen und Umwickeln ausgelegt sind, ist dies ein wichtiges Sicherheitsmerkmal, und das Ausschalten ähnelt dem Fahren ohne Sicherheitsgurt.
Mason Wheeler
Vielleicht haben Sie es vergessen, aber die Anweisungen zur Überlaufprüfung und zum Compiler waren in älteren Versionen von Delphi unglaublich fehlerhaft. Ich kann mich lebhaft daran erinnern, wie ich mir mehrmals die Haare ausgerissen habe, nachdem der Debugger direkt in der Mitte eines {$ O -} / {$ O +} - Blocks angehalten hat, um fröhlich einen Überlauf zu melden. Nach einer Weile konnte ich es nicht mehr ertragen und deaktivierte es einfach global. Wieder ja, es hätte dieses Problem aufgefangen, aber ich denke immer noch nicht, dass es die Anzahl der falsch positiven Ergebnisse wert ist. Jedem natürlich sein eigenes!
Aaronaught
3

Um ehrlich zu sein, neige ich dazu, Ganzzahlen aus Gewohnheit zu verwenden. Ich habe mich daran gewöhnt, dass sie Bereiche bieten, die für die meisten Situationen groß genug sind und negative Werte (wie -1) zulassen. In der Tat wäre die Verwendung von Bytes / Wörtern / Abkürzungen häufig angemessener. Wenn ich jetzt darüber nachdenke, kann ich mich auf folgende Punkte konzentrieren:

  • Perspektive. Die Tilemap-Größe ist auf 192 x 192 Kacheln begrenzt, daher kann ich Byte zum Adressieren von Kacheln und Schleifen verwenden. Aber wenn die Kartengröße erhöht werden soll, muss ich jede Verwendung durchgehen und sie durch zB ein Wort ersetzen. Wenn ich Off-Map-Objekte zulassen muss, muss ich erneut zu smallint wechseln.

  • Schleifen. Es ist oft so, dass ich eine Schleife "von i: = 0 bis Count-1" schreibe. Was passiert, wenn "i" Byte ist und Count = 0 ist, dass diese Schleife von 0 bis 255 läuft. Nicht, dass ich es möchte.

  • Uniformierung. Es ist einfacher, sich "var i: integer" zu merken und anzuwenden. als in jedem Fall anzuhalten und zu denken "Hm .. hier haben wir es mit 0..120 Bereich zu tun .. Byte .. nein, warte, wir brauchen möglicherweise -1 für nicht initialisierte .. Abkürzung .. warte .. was ist, wenn 128 ist nicht genug .. Arrgh! " oder "Warum ist es an diesem Ort klein, keine Abkürzung?"

  • Kombinieren. Wenn ich zwei oder mehr Klassen miteinander kombinieren muss, verwenden sie möglicherweise unterschiedliche Datentypen für ihre Zwecke. Durch die Verwendung breiterer Typen können unnötige Konvertierungen übersprungen werden.

  • -1. Selbst wenn die Werte im Bereich 0..n-1 liegen, muss ich häufig den Wert "kein Wert / unbekannt / nicht initialisiert / leer" einstellen, was nach gängiger Praxis -1 ist.

Durch die Verwendung von Ganzzahlen können Sie all diese Probleme überspringen, die Optimierung auf niedriger Ebene vergessen, wenn sie nicht benötigt wird, auf eine höhere Ebene wechseln und sich auf realere Probleme konzentrieren.

PS Wann verwende ich andere Typen?

  • Zähler sind niemals negativ und außerhalb ihrer Klasse schreibgeschützt.
  • Aus Gründen der Leistung / des Speichers müssen an bestimmten Stellen kürzere Datentypen verwendet werden.
Kromster
quelle
1

Am besten verwenden Sie einen Datentyp, der den Anforderungen der verwendeten Daten entspricht (erwartete Daten).

C # -Beispiele: Wenn ich nur 0 bis 255 unterstützen müsste, würde ich ein Byte verwenden.

Wenn ich 1.000.000 negative und positive unterstützen musste, dann int.

Größer als 4,2 Milliarden, dann verwenden Sie eine lange.

Durch Auswahl des richtigen Typs verwendet das Programm die optimale Speichermenge, und verschiedene Typen verwenden unterschiedliche Speichermengen.

Hier ist eine C # int-Referenz von MSDN.

int 
 -2,147,483,648 to 2,147,483,647
 Signed 32-bit integer

uint 
 0 to 4,294,967,295
 Unsigned 32-bit integer

long 
 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
 Signed 64-bit integer

ulong 
 0 to 18,446,744,073,709,551,615
 Unsigned 64-bit integer
Jon Raynor
quelle
In C # (oder .net im Allgemeinen) würde long und ulong 128 Bit auf einem 128-Bit-Computer werden? Denn in Delphi Integerbeträgt der Datentyp auf einem 32-Bit-Computer 32 Bit und auf einem 64-Bit-Computer anscheinend 64 Bit.
Peter Turner
1
@ Peter Turner: Nein, in C # intist nur eine Abkürzung für System.Int32, egal auf welcher Maschine der Code ausgeführt wird.
Nikie
@ Nikie, ist es nur so type int System.Int32oder so? Könnte es in einer zukünftigen Version des Frameworks so einfach geändert werden?
Peter Turner
@ Peter Turner / nikie (sizeof (int) .ToString ()); ==> Gibt 4 zurück (sizeof (Int64) .ToString ()); ==> Gibt 8 auf meinem 64-Bit-Windows-Betriebssystem zurück. Als Nikie, Statistik, ist ein Int wirklich gerecht und Int32.
Jon Raynor
1
Zu beachten ist, dass nicht alle Typen der Common Language Specification entsprechen . uintist einer dieser nicht konformen Typen, was bedeutet, dass er nicht in öffentlich zugänglichen APIs verwendet werden sollte, um zu vermeiden, dass die Verwendung dieser API in anderen .NET-Sprachen als der, in der die Bibliothek geschrieben ist, beeinträchtigt wird. Aus diesem Grund auch das .NET-Framework API selbst verwendet intwo uintwürde tun.
Adam Lear
1

Ganzzahlige Typen ohne Vorzeichen sollten nur zur Darstellung von Kardinalzahlen in Sprachen verwendet werden, in denen sie Kardinalzahlen darstellen. Aufgrund der Funktionsweise der Computer, auf denen C ausgeführt wurde, verhielten sich vorzeichenlose Ganzzahltypen wie Mitglieder von algebraischen Mod-2 ^ n-Ringen (was bedeutet, dass überlaufende Berechnungen vorhersehbar "umbrechen" würden), und die Sprache gibt an, dass dies in vielen Fällen der Fall ist erforderlich, um sich als abstrakte algebraische Ringe zu verhalten, selbst wenn ein solches Verhalten nicht mit dem Verhalten von Kardinalzahlen oder mathematischen ganzen Zahlen vereinbar wäre.

Wenn eine Plattform separate Typen für Kardinalzahlen und algebraische Ringe vollständig unterstützt, würde ich vorschlagen, dass Kardinalzahlen mit Kardinalzahlentypen verarbeitet werden (und Dinge, die mit den Ringtypen umbrochen werden müssen). Solche Typen könnten nicht nur Zahlen speichern, die doppelt so groß sind wie signierte Typen, sondern eine Methode, die einen Parameter eines solchen Typs empfängt, müsste nicht prüfen, ob er negativ ist.

Angesichts des relativen Fehlens von Kardinalzahlentypen ist es jedoch im Allgemeinen am besten, einfach Ganzzahlen zu verwenden, um sowohl mathematische Ganzzahlen als auch Kardinalzahlen darzustellen.

Superkatze
quelle