Ist die wissenschaftliche Notation für ganzzahlige Konstanten in C sicher?

Lesezeit: 7 Minuten

Seit einiger Zeit stelle ich große Potenzen von 10 in Konstanten in wissenschaftlicher Notation dar, nur damit ich die Nullen nicht zählen muss. z.B

#define DELAY_USEC 1e6

Ein Kollege wies darauf hin, dass dies nicht sicher ist, da es sich nicht um eine Ganzzahl handelt und nicht garantiert immer gleich 1000000 ist exakt. Die Dokumentation scheint dies zu bestätigen, aber ich frage mich, ob es in der Praxis wahr ist. Gibt es eine Möglichkeit, eine Zehnerpotenz-Ganzzahl mit einer Abkürzung sicher zu deklarieren? Ist es sicher, es einfach in ein int in der Definition umzuwandeln?

  • Sie können die Zehnerpotenz als Ganzzahl notieren und davon ausgehen, dass sie eins ist (z. B. durch Namenskonvention) und sie bei der Ausführung des Codes einfach an eine echte Zehnerpotenz übergeben, das ist sicher.

    – Jan

    24. Juni 2014 um 14:50 Uhr

  • Sie wandeln es nicht in ein int in der Definition um. Wenn Sie aus Gründen der Typsicherheit eine ganzzahlige Konstante wünschen und diese der Kürze halber abkürzen, können Sie ein hexadezimales Literal verwenden.

    – StoryTeller – Unslander Monica

    24. Juni 2014 um 14:52 Uhr

  • Ich denke, weil Sie, da Sie einen Float definieren, keine Möglichkeit haben, zu wissen, ob es genau so sein wird, da Gleitkommazahlen eine begrenzte Genauigkeit haben.

    – Ben

    24. Juni 2014 um 14:52 Uhr

  • Dies ist keine Antwort auf Ihre Frage, aber denken Sie daran, dass Sie Mathematik verwenden können, wenn Sie Konstanten wie definieren static const int SEC_TO_MILLI = 1000; static const int SEC_TO_MICRO = 1000 * SEC_TO_MILLI; Dies hilft mir oft, komplizierte, fehleranfällige Zahlenliterale zu vermeiden.

    – Japreis

    24. Juni 2014 um 14:54 Uhr


  • Ein weiteres Problem dabei ist, dass jeder Ausdruck, zu dem er gehört, mit „doppelter“ Genauigkeit ausgewertet wird, was wahrscheinlich nicht das ist, was Sie wollen.

    – Lindydancer

    24. Juni 2014 um 14:58 Uhr

Benutzeravatar von Mike Seymour
Mike Seymour

Theoretisch nein. Keine der Sprachen gibt an, wie Gleitkommawerte dargestellt werden oder welche Werte genau dargestellt werden können. (UPDATE: anscheinend empfiehlt C11 eine Darstellung. C++ und ältere C-Dialekte tun dies nicht).

In der Praxis ja, für einen recht großen Wertebereich. Jede Implementierung, auf die Sie wahrscheinlich stoßen werden, verwendet a 64-Bit-IEEE-Darstellung zum double. Dies kann jeden ganzzahligen Wert bis 2 darstellen53 (ungefähr 9×10fünfzehn) exakt. Es kann sicherlich alles darstellen, was durch einen 32-Bit-Ganzzahltyp darstellbar ist.

  • C11 5.2.4.2.2 legt einige Einschränkungen fest. zB ein double muss jede ganze Zahl mit 10 Dezimalstellen aufnehmen können.

    – Mafso

    24. Juni 2014 um 15:01 Uhr

  • @mafso: Interessant, das wusste ich nicht. Das bedeutet, dass jeder moderne C-Compiler jeden 32-Bit-Ganzzahlwert genau darstellen sollte. In C++ gibt es (noch) keine solche Einschränkung, es sei denn, ich habe sie übersehen.

    – Mike Seymour

    24. Juni 2014 um 15:22 Uhr

  • Ich weiß nichts über C++, aber C gibt tatsächlich an, dass Gleitkommawerte nach Möglichkeit IEEE 754 verwenden sollen (C11 Annex F). Eine konforme Implementierung soll dies tun #define Flags, um die richtige Unterstützung anzuzeigen, sodass diese Informationen dem Programmierer zur Verfügung stehen.

    – Leuschenko

    24. Juni 2014 um 15:27 Uhr


  • @Leushenko: Danke, meine C-Kenntnisse sind etwas alt; Ich wusste nicht, dass das heutzutage vorgeschrieben war.

    – Mike Seymour

    24. Juni 2014 um 15:29 Uhr

  • Tatsächlich können Sie für positive Potenzen von 10 problemlos große Werte bis 1e22 verwenden: So überraschend es auch sein mag, sie sind immer noch exakt im 53-Bit-Signifikanz- und Fließkommaformat darstellbar (versuchen Sie einfach, sie mit einer anständigen Implementierung von printf). Das ist weil log2(5^22)<52und 1e22==5^22*2^22.

    – Ruslan

    28. Mai 2018 um 19:57 Uhr


Benutzeravatar von Paul Evans
Paul Evans

Sie möchten benutzerdefinierte Literale verwenden:

constexpr long long operator "" _k(long long l) {
    return l * 1000;
}

constexpr long long operator "" _m(long long l) {
    return l * 1000 * 1000;
}

dann kannst du einfach machen:

long long delay = 1_m;
long long wait = 45_k;

  • Aber Sie werden sie anrufen wollen _k und _m, da wörtliche Namen ohne führende Unterstriche reserviert sind. Vielleicht möchten Sie auch _M statt _m passend zum SI-Präfix.

    – Mike Seymour

    24. Juni 2014 um 15:02 Uhr

  • @MikeSeymour du hast recht, Antwort entsprechend geändert

    – Paul Evans

    24. Juni 2014 um 15:15 Uhr

  • @MikeSeymour – ist ein Unterstrich gefolgt von Großbuchstaben nicht eine reservierte Kennung? (Nicht wörtlich reserviert, aber allgemein für die Implementierung reserviert.)

    – Martin B

    24. Juni 2014 um 20:43 Uhr

  • @Mike – hmmm … aber für “normale” reservierte Symbole darf die Implementierung ein Makro _M definieren (da das reserviert ist) und so möchten Vermasseln Sie den Benutzercode. Das scheint also seltsam für Benutzerdef.

    – Martin B

    25. Juni 2014 um 7:20 Uhr

  • @MartinBa: Ja, du hast recht; _M wäre nicht erlaubt. Das hätte ich nicht vorschlagen sollen.

    – Mike Seymour

    25. Juni 2014 um 10:04 Uhr

Benutzeravatar von tmyklebu
tmyklebu

Sie fragen speziell nach Zehnerpotenzen. 1e6 wird genau eine Million sein. Sie können bis zu gehen 1e22 ohne dass etwas schlimmes passiert. Beachten Sie jedoch, dass sowohl in C++ als auch in C 1e6 ist ein double Konstante und nicht eine ganzzahlige Konstante.

Negative Zehnerpotenzen sind eine andere Geschichte. 1e-1 ist ungenau, wie alle niederen Mächte.

  • C garantiert dies nicht, Bit IEEE tut es

    – Steve Cox

    24. Juni 2014 um 14:53 Uhr

  • Nicht alle niederen Mächte: 5e-1 ist genau.

    – James Kanze

    24. Juni 2014 um 14:55 Uhr

  • @JamesKanze: Es ist auch keine Zehnerpotenz. (Ich habe den Text nur korrigiert, um zu verdeutlichen, dass ich von Zehnerpotenzen spreche.)

    – tmyklebu

    24. Juni 2014 um 14:56 Uhr


  • Woher kommt der Wert von 1e22 komme aus? Nach meiner Berechnung ist die kleinste nicht darstellbare ganze Zahl 2^53+1etwa 1e16.

    – Mike Seymour

    24. Juni 2014 um 15:13 Uhr


  • @tmyklebu: Fair genug, ich habe diese Einschränkung nicht bemerkt. Es könnte sich lohnen, die Antwort zu klären, falls ich nicht der einzige bin, der sie ohne den vollständigen Kontext liest.

    – Mike Seymour

    24. Juni 2014 um 15:18 Uhr

Es scheint, dass gcc nimmt eine in wissenschaftlicher Notation definierte Konstante als Fließkommazahl an, sofern sie nicht gecastet wird.

Ein einfacher C-Code zeigt dies:

#include <stdio.h>

#define DELAY_USEC_FP  1e6
#define DELAY_USEC_INT (unsigned int) 1e6

int main()
{
    printf("DELAY_USEC_FP: %f\n", DELAY_USEC_FP);
    printf("DELAY_USEC_INT: %u\n",  DELAY_USEC_INT);
    return 0;
}

Auf einem x86-64-Rechner gcc generiert diesen Assemblercode ($ gcc -S define.c):

[...]
; 0x4696837146684686336 = 1e6 in double-precision FP IEEE-754 format
movabsq $4696837146684686336, %rax
[...]
call    printf
movl    $1000000, %esi
[...]
call    printf
movl    $0, %eax

Wie gesagt hier10e15 und 10e22 sind die maximale Potenz von zehn Zahlen, die eine exakte Darstellung im Gleitkommaformat mit einfacher bzw. doppelter Genauigkeit haben.

Größere Zehnerpotenzen können nicht mit 32-Bit- oder 64-Bit-Ganzzahltypen dargestellt werden.

Sie werden nie Rundungsfehler bei etwas weniger als bekommen INT_MAXda die Spezifikation für double legt beiseite 52 Bits für Sie zu verwenden. Ihre “Bruchkomponente” ist nur Ihre ganze Zahl, und Ihr “Exponent” ist 1, und Gleitkommazahlen haben damit keine Probleme.

  • Das ist nicht ganz das, was vor sich geht, da Sie dadurch eine nicht normalisierte Gleitkommazahl erhalten. double hat ein implizites Bit, daher können Sie positive ganze Zahlen nicht auf diese Weise darstellen. (Stattdessen erhalten Sie das normalisierte Äquivalent dessen, was Sie gesagt haben.)

    – tmyklebu

    24. Juni 2014 um 14:55 Uhr

  • Wie meinst du das? Der Wert im “Exponenten”-Bitbereich der Zahl wird nicht wirklich 1 sein, aber ich glaube, dass das Ergebnis so funktioniert, dass der “Bruch” nur Ihre Zahl enthält. Solange Sie unter 2 ^ 32 sind (was ich für das OP nehme, da er wahrscheinlich vorsichtiger wäre, wenn er größere Konstanten verwenden müsste), sollte das gelten.

    – Patrick Collins

    24. Juni 2014 um 15:01 Uhr

  • Der Signifikand enthält alles bis auf das erste Bit Ihrer Zahl. Das höherwertige Bit ist das implizite Bit.

    – tmyklebu

    24. Juni 2014 um 15:01 Uhr


Benutzeravatar von Chemseddine
Chemseddine

Es ist wirklich nicht sicher, weil der Compiler es als Fließkommazahl betrachtet, daher ist die Genauigkeit auf 53 Bit statt 64 Bit von Ganzzahlen (long int) begrenzt. Weitere Informationen zur Darstellung von Fließkommazahlen finden Sie hier

http://en.wikipedia.org/wiki/Floating_point

  • Das ist nicht ganz das, was vor sich geht, da Sie dadurch eine nicht normalisierte Gleitkommazahl erhalten. double hat ein implizites Bit, daher können Sie positive ganze Zahlen nicht auf diese Weise darstellen. (Stattdessen erhalten Sie das normalisierte Äquivalent dessen, was Sie gesagt haben.)

    – tmyklebu

    24. Juni 2014 um 14:55 Uhr

  • Wie meinst du das? Der Wert im “Exponenten”-Bitbereich der Zahl wird nicht wirklich 1 sein, aber ich glaube, dass das Ergebnis so funktioniert, dass der “Bruch” nur Ihre Zahl enthält. Solange Sie unter 2 ^ 32 sind (was ich für das OP nehme, da er wahrscheinlich vorsichtiger wäre, wenn er größere Konstanten verwenden müsste), sollte das gelten.

    – Patrick Collins

    24. Juni 2014 um 15:01 Uhr

  • Der Signifikand enthält alles bis auf das erste Bit Ihrer Zahl. Das höherwertige Bit ist das implizite Bit.

    – tmyklebu

    24. Juni 2014 um 15:01 Uhr


1408260cookie-checkIst die wissenschaftliche Notation für ganzzahlige Konstanten in C sicher?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy