Neuer Datentyp: 48-Bit Int
-
Verdoppler schrieb:
groovemaster schrieb:
Doppler schrieb:
Nimm doch einfach double, das hat 8 Bytes
Erstmal wird nirgendwo festgelegt, dass double 8 Byte Datenbreite hat.
Natürlich. Der IEEE Standard schreibt vor, daß eine Fliesskommazahl "Double Precision" aus 64 Bits besteht.
Wir sind hier aber nicht im IEEE Forum, sondern bei C++. Und da wird die genaue Grösse von double nunmal nicht festgelegt. double muss nur mindestens soviel Präzision wie float haben. Zudem muss ja nicht auf jeder Plattform eine Gleitkommazahl nach IEEE dargestellt werden.
Verdoppler schrieb:
Steigst du nur in ein Auto, wenn schon drei Personen drin sitzen?
Versteh ich nicht. Kannst du mir mal aufgrund meines "beschränkten Verstandes" erklären, was das mit dem Thema zu tun hat?
Verdoppler schrieb:
Es gibt vernünftige Argumente gegen die Nutzung von double, aber davon hast du nicht eins genannt.
Sry, wenn ich dich in deiner Ehre gekränkt hab. Aber wenn du Unsinn redest, dann solltest du auch mit Kritik leben können. Daran solltest du neben deinen Argumenten noch etwas arbeiten.
Du kannst mir die vernünftigen Argumente aber auch gerne nennen, bin immer bereit, was Neues zu lernen.
Und wenn sich bei dir der Frust über die eigene Unerfahrenheit etwas gelegt hat, dann liess meinen Beitrag nochmal durch. Dann werden dir die vernünftigen Argumenten sicher bewusst.Wir glauben an dich, du schaffst das schon.
@Konfusius
Du solltest noch op= implementieren.
Ansonstenvoid main ()
-
Konfusius schrieb:
uint48::uint48 (uint_64 x) { value[0]=((unsigned char*)&x)[0]; value[1]=((unsigned char*)&x)[1]; value[2]=((unsigned char*)&x)[2]; value[3]=((unsigned char*)&x)[3]; value[4]=((unsigned char*)&x)[4]; value[5]=((unsigned char*)&x)[5]; }
wenn schon unportabel mit tausenden casts dann bitte richtig unportabel und etwas effizienter
uint48::uint48 (uint_64 x) { *(unsigned*)value=x; // kein cast auf der rechten seite nötig ((unsigned short*)value)[2]=((unsigned short*)&x)[2]; }
-
Du solltest noch op= implementieren.
ist nicht nötig. der compiler nimmt dann automatisch den default-copy-konstruktor. und da die klasse keine resourcen alloziert genügt der ja völlig.
-
wie das ohne casts gehen soll mußt du mir mal erklären. und wenn schon casts dann besser explizit als implizit. man könnte höchstens monieren, daß ich nicht c++-mäßige static_cast<>'s verwendet habe. aber dann wärs noch unleserlicher geworden und es hätte dich noch mehr gestört.
-
was daran unportabel ist mußt du mir auch nochmal erklären.
-
die sache mit der effizienz ist schon richtig. hätte besser unsigned short value[3] anstatt unsigned char value[6] nehmen sollen. aber ich hab ja auch geschrieben, daß an dem code noch gearbeitet werden muß :p
-
-
Konfusius schrieb:
- wie das ohne casts gehen soll mußt du mir mal erklären. und wenn schon casts dann besser explizit als implizit. man könnte höchstens monieren, daß ich nicht c++-mäßige static_cast<>'s verwendet habe. aber dann wärs noch unleserlicher geworden und es hätte dich noch mehr gestört.
mit >>
value[0]=x; value[1]=x>>CHAR_BIT; // etc...
- was daran unportabel ist mußt du mir auch nochmal erklären.
byteorder - auf big endian maschinen würdest du die niederwertigen 16 bit des ursprünglichen 64bit integers wegwerfen.
- die sache mit der effizienz ist schon richtig. hätte besser unsigned short value[3] anstatt unsigned char value[6] nehmen sollen. aber ich hab ja auch geschrieben, daß an dem code noch gearbeitet werden muß :p
war auch kein allzu ernst zu nehmender beitrag von mir
-
mal ein paar unwesentliche gedanken dazu:
#include<iostream> using namespace std; typedef unsigned __int64 u64; class u48 { private: u64 read() const {//TODO: wilde casts und union zum konvertieren vorgesehen, aber //sollte erstmal auch so gehen. aufpassen, daß casts nicht durch //bad alignment auf ia64 unendlich langsam werden und lauter so //sachen halt. return (u64(data[0])<<0)|(u64(data[1])<<16)|(u64(data[2])<<32); } void write(u64 x) { data[0]=x>>0; data[1]=x>>16; data[2]=x>>32; } unsigned int data[3]; public: u48 (u64 x) { write(x); } operator u64 () const { return read(); } u48& operator+=(u48 const& b) { write(read()+b.read()); return *this; } friend u48 operator+(u48 a,u48 b) { return u48(a)+=b; } //rest sollte easy sein. }; int main () { u48 x=100000000001ull,y=200000000002ull; cout<<"sizeof(u48)="<<sizeof(u48)<<endl; cout<<x<<"+"<<y<<"="<<x+y<<endl; }
-
Konfusius schrieb:
Du solltest noch op= implementieren.
ist nicht nötig. der compiler nimmt dann automatisch den default-copy-konstruktor.
Was denn nun, default- oder copy-ctor?
Schon klar, was du meinst. Aber mit op= könnte es durchaus effizienter sein, wenn der Compiler es nicht schafft, das temporäre Objekt vernünftig wegzuoptimieren.Konfusius schrieb:
man könnte höchstens monieren, daß ich nicht c++-mäßige static_cast<>'s verwendet habe. aber dann wärs noch unleserlicher geworden
Nicht unbedingt.
value[0]=((unsigned char*)&x)[0]; value[0]=reinterpret_cast<unsigned char*>(&x)[0];
Ich finde zB keine Zeile wirklich leserlicher.
volkard schrieb:
unsigned int data[3];
Immer noch DOS am laufen?
-
groovemaster schrieb:
volkard schrieb:
unsigned int data[3];
Immer noch DOS am laufen?
lol
-
gibt es eigentlich eine garantie, dass sizeof(u48) tatsächlich nur 6byte ist (auf den üblichen plattformen) ? immerhin ist es kein POD. diesbzgl. ist mir sowieso nicht ganz klar, was gefordert ist:
a) der wertebereich des typs entspricht genau dem eines 48bit unsigned integers, also 0..2^^48-1
b) der typ beansprucht genau 48bit speicher und padding in arrays ist verboten.falls nur a) gefordert ist, könnte man, wie erwähnt, ohne weiters bitfelder benutzen, was den vorteil hat, dass der compiler damit direkt umgehen kann:
typedef unsigned long long u64; struct u48 { u64 value:48; };
das hat allerdings dieselben speicheranforderungen wie ein 64bit integer, also ist b) nicht erfüllt. zum rechnen muss man jetzt bezug auf value nehmen, oder man baut eben eine ganze klasse mit schönem interface wie oben drumherum - dann ist es aber kein POD mehr, was evtl. ungünstig ist.
-
@camper: Das Alignment ist bei allen guten Compilern einstellbar, somit sollte das keine 64 Bits benötigen, oder? Trotzdem wärs wohl sicherer die Klassenvariante zu benützen.
MfG SideWinder
-
camper schrieb:
gibt es eigentlich eine garantie, dass sizeof(u48) tatsächlich nur 6byte ist (auf den üblichen plattformen) ? immerhin ist es kein POD.
Ja, solange keine virtuellen Funktionen benutzt werden und der Compiler Padding verhindert, sollte es bzgl. der Grösse keine Probleme geben.
camper schrieb:
diesbzgl. ist mir sowieso nicht ganz klar, was gefordert ist:
a) der wertebereich des typs entspricht genau dem eines 48bit unsigned integers, also 0..2^^48-1
b) der typ beansprucht genau 48bit speicher und padding in arrays ist verboten.Also ich hab es so verstanden, dass b) gefordert war. Aber anstatt hier weiter zu raten, sollte Honolulu noch mal genau sagen, was er will. Mehrere Lösungsansätze hat er ja jetzt.
camper schrieb:
dann ist es aber kein POD mehr, was evtl. ungünstig ist.
Wie gesagt, ich sehe darin keine Probleme.
-
'sollte' war mir schon klar - die frage war nur, ob es irgendwelche garantien gibt.
bzgl. padding - alignment das kann man sicher einstellen, ich glaube mich aber dunkel zu erinnern, dass bitfelder immer den bereich des zugrundeliegenden typs benötigen - irgendwie muss der compiler nach all der bitschieberei und -maskiererei ja darauf zugreifen. ich lasse mich aber gern korrigieren
ein problem mit nicht-PODs ist, dass keine statische initilisierung stattfinden kann, das zumindest könnte unschön sein - wobei man ja problemlos auch zwei typen definieren könnte - eine als POD für solche zwecke und eine andere für das normale rechen.
-
camper schrieb:
'sollte' war mir schon klar - die frage war nur, ob es irgendwelche garantien gibt.
Was meinst du mit Garantien? So richtig versteh ich deine Bedenken nicht. Laut Standard, IIRC, werden Arrayelemente immer direkt hintereinander angeordnet, zB
unsigned short data[3];
sind 3 unsigned shorts und kein Bit mehr. Wenn dazu noch struct/class Padding durch den Compiler verhindert wird, wo soll also zusätzlicher Speicher verbraucht werden?
camper schrieb:
bzgl. padding - alignment das kann man sicher einstellen, ich glaube mich aber dunkel zu erinnern, dass bitfelder immer den bereich des zugrundeliegenden typs benötigen - irgendwie muss der compiler nach all der bitschieberei und -maskiererei ja darauf zugreifen. ich lasse mich aber gern korrigieren
Padding bezog sich im Kontext ja auch nicht auf Bitfelder.
camper schrieb:
ein problem mit nicht-PODs ist, dass keine statische initilisierung stattfinden kann, das zumindest könnte unschön sein - wobei man ja problemlos auch zwei typen definieren könnte - eine als POD für solche zwecke und eine andere für das normale rechen.
Mit statische initilisierung meinst du wahrscheinlich so eine initializer-list
uint48 a = {1, 2, 3};
Aber, da denkst du einfach zu umständlich. Ein uint48 (oder wie immer der Typen dann heissen mag) wird genauso gehandelt wie andere Ganzzahltypen auch. Du schreibst zB einfach
uint48 a = 10;
Wieviel und welche Member uint48 hat, hat dich überhaupt nicht zu interessieren.
-
mit statischer initialisierung meine ich statische intialisierung (die in form einer solchen initialisiererliste stattfindet, aber das ist unwesentlich) - welche vor der dynamischen initialisierung stattfindet. falls ich irgendwelche globalen objekte haben, die bei der konstruktion solche 48bit integer brauchen, kann ich sonst nicht sicher sein, dass diese intialisiert sind, wenn sie nicht in der selben ÜE und vor dem betreffenden objekt definiert sind. sonst müsste man noch tausende kniffe ala iostream anwenden - ein bisschen viel aufwand für den zweck.
Wenn dazu noch struct/class Padding durch den Compiler verhindert wird, wo soll also zusätzlicher Speicher verbraucht werden?
padding und alignment sind zwei verschiedene dinge - auch wenn ersteres i.d.r. letzterem dient. die frage ist, gibt es eine garantie, dass der compiler nicht noch irgendwelche versteckten felder benutzt für welchen zweck auch immer - einen gc zum beispiel. bereits das einstellen irgendwelchen alignments oder struct paddings ist ja genau genommen kein standard.
-
camper schrieb:
mit statischer initialisierung meine ich statische intialisierung (die in form einer solchen initialisiererliste stattfindet, aber das ist unwesentlich)
Ich hab dazu ja auch nur ein Beispiel konstruiert. Statische Intialisierung kann auch so aussehen
static foo bar;
Und foo muss hier kein POD sein.
camper schrieb:
falls ich irgendwelche globalen objekte haben, die bei der konstruktion solche 48bit integer brauchen, kann ich sonst nicht sicher sein, dass diese intialisiert sind, wenn sie nicht in der selben ÜE und vor dem betreffenden objekt definiert sind. sonst müsste man noch tausende kniffe ala iostream anwenden - ein bisschen viel aufwand für den zweck.
So richtig versteh ich hier nicht, was du uns damit sagen willst.
Um mal ein weiteres Besipiel zu bringenclass uint48 { //... }; struct unser_globaler_typ_der_bei_der_konstruktion_solche_48bit_integer_braucht { int a; uint48 b; long c; };
Und jetzt willst du davon ein globales Objekt erstellen, richtig?
unser_globaler_typ_der_bei_der_konstruktion_solche_48bit_integer_braucht x;
Wie du vielleicht weisst, werden Objekte mit static storage duration, was unser globales Objekt hat, nullinitialisiert. Willst du nun eine initializer-list benutzen, hast du natürlich ein Problem. Es gibt kein uint48 Literal.
Oder korrekt formuliert, ein non-aggregate (was unser uint48 ja ist) kann in so einer Liste nicht initialisiert werden. Man muss sich dann halt überlegen, ob man sowas braucht. Klar könnte man, wie du schon sagst, einen Typ als aggregate und einen als non-aggregate bereitstellen, so ähnlich wie bei DirectX
struct uint48plain { unsigned short value[3]; }; class uint48 : protected uint48plain { //... };
Halt ich aber für keine schöne Lösung, da somit Implementationsdetails offen gelegt werden. Bei uint48plain mag das ja noch gewollt sein, bei uint48 aber nicht. Zudem wäre das Handling von uint48plain etwas umständlich und nicht so intuitiv wie mit den elementaren Typen.
camper schrieb:
die frage ist, gibt es eine garantie, dass der compiler nicht noch irgendwelche versteckten felder benutzt für welchen zweck auch immer
Ja, die gibt es. Ansonsten würde ich meinen Compiler wegschmeissen. Das einzige Problem, was entstehen könnte, ist die Grösse der addressierbaren Einheiten. Wenn sich also 48 Bit nicht durch ein Vielfaches einer addressierbaren Einheit darstellen lässt. Da x86 kompatible Plattformen mit Vielfachen von 8 arbeiten, gibt es dort zB keine Probleme.
camper schrieb:
einen gc zum beispiel
Standard C++ hat keinen GC.
camper schrieb:
bereits das einstellen irgendwelchen alignments oder struct paddings ist ja genau genommen kein standard
Richtig, das wurde aber bereits erwähnt, das sich dies nur compiler-spezifisch realisieren lässt.
-
groovemaster schrieb:
Ich hab dazu ja auch nur ein Beispiel konstruiert. Statische Intialisierung kann auch so aussehen
static foo bar;
Und foo muss hier kein POD sein.
was soll das denn jetzt? das weisst du besser
das static keyword hat doch nichts mit der initialisierung zu schaffen (nur insofern als static storage voraussetzung für static initilisiation ist).
camper schrieb:
falls ich irgendwelche globalen objekte haben, die bei der konstruktion solche 48bit integer brauchen, kann ich sonst nicht sicher sein, dass diese intialisiert sind, wenn sie nicht in der selben ÜE und vor dem betreffenden objekt definiert sind. sonst müsste man noch tausende kniffe ala iostream anwenden - ein bisschen viel aufwand für den zweck.
So richtig versteh ich hier nicht, was du uns damit sagen willst.
...
... das meinte ich gar nicht, aber ist auch ein guter punkt. die aussage soll ja nur sein, dass eben weder eine POD variante noch eine mit schönem interface, aber eben als nichtPOD, nicht alle bedürfnisse befriedigen kann.
sinnfreies beispiel:
// a.h struct uint48 { /* schönes interface, kein POD ... */ }; extern const uint48 xyz; // a.cpp #include "a.h" const uint48 xyz = 10; // b.cpp #include "a.h" struct X { uint48 x; X() : x( xyz ) { } }; X x;
wird xyz vor oder nach x initialisiert? es ist zwar richtig, dass alle objekte mit static storage beim start zero-initialisiert werden, aber das ist für nicht aggregate völlig bedeutungslos. das objekt lebt dann erst, nachdem der konstruktor ausgeführt wurde.
camper schrieb:
die frage ist, gibt es eine garantie, dass der compiler nicht noch irgendwelche versteckten felder benutzt für welchen zweck auch immer
Ja, die gibt es.
und das steht wo?
camper schrieb:
einen gc zum beispiel
Standard C++ hat keinen GC.
der standard erlaubt ausdrücklich gc.
-
camper schrieb:
groovemaster schrieb:
Ich hab dazu ja auch nur ein Beispiel konstruiert. Statische Intialisierung kann auch so aussehen
static foo bar;
Und foo muss hier kein POD sein.
was soll das denn jetzt?
Das Statement war eher in Verbindung mit deiner Aussage bzgl.
camper schrieb:
ein problem mit nicht-PODs ist, dass keine statische initilisierung stattfinden kann
gedacht.
camper schrieb:
das weisst du besser
das static keyword hat doch nichts mit der initialisierung zu schaffen
Doch, das hat es. Aber wenn's dich stört, dann denk es dir einfach weg und static storage duration dazu. (und lass deine Klugscheisserei aussen vor
)
camper schrieb:
nur insofern als static storage voraussetzung für static initilisiation ist
Das ist zwar richtig, aber eher irrelevant, da man von statischer Initialisierung sowieso nur bei nicht-lokalen Objekten spricht. Viel wichtiger ist die Tatsache, dass es sich um statische Initialisierung bei Nullinitialisierung oder Initialisierung mit konstanten Werten handelt.
camper schrieb:
sinnfreies beispiel:
// a.h struct uint48 { /* schönes interface, kein POD ... */ }; extern const uint48 xyz; // a.cpp #include "a.h" const uint48 xyz = 10; // b.cpp #include "a.h" struct X { uint48 x; X() : x( xyz ) { } }; X x;
wird xyz vor oder nach x initialisiert?
Das ist aber nicht spezifisch für die eigentliche Problematik. Du sprichst hier ein generelles Problem beim Initialisieren von globalen Objekten an.
Auch wenn sich ein uint48 wie ein elementarer Typ benutzen lässt, so bleibt es doch ein non-POD mit all den verbundenen Konsequenzen. So wie es bei anderen Klassen auch der Fall ist. Das dürfte dem Op mittlerweile schon klar geworden sein.camper schrieb:
und das steht wo?
Nunja, ich hoffe, dass du noch nicht anfängst zu hyperventilieren.
Ansonsten bau dir ein static assert mit sizeof.
der standard erlaubt ausdrücklich gc.
Mag sein, diesbzgl. sind mir allerdings keine Quellen bekannt. Genauso wenig, dass die Implementation einfach so irgendwelche "unsichtbaren" Daten in die Klasse packen darf (bis auf Padding, um entsprechendes Alignment zu garantieren). Und wenn schon GC, dann werden benötigte Informationen mit Sicherheit an anderer Stelle gespeichert.
-
groovemaster schrieb:
camper schrieb:
groovemaster schrieb:
Ich hab dazu ja auch nur ein Beispiel konstruiert. Statische Intialisierung kann auch so aussehen
static foo bar;
Und foo muss hier kein POD sein.
was soll das denn jetzt?
Das Statement war eher in Verbindung mit deiner Aussage bzgl.
camper schrieb:
ein problem mit nicht-PODs ist, dass keine statische initilisierung stattfinden kann
gedacht.
camper schrieb:
das weisst du besser
das static keyword hat doch nichts mit der initialisierung zu schaffen
Doch, das hat es.
kapitel und vers?
Aber wenn's dich stört, dann denk es dir einfach weg und static storage duration dazu.
das wäre richtig.
camper schrieb:
nur insofern als static storage voraussetzung für static initilisiation ist
Das ist zwar richtig, aber eher irrelevant, da man von statischer Initialisierung sowieso nur bei nicht-lokalen Objekten spricht. Viel wichtiger ist die Tatsache, dass es sich um statische Initialisierung bei Nullinitialisierung oder Initialisierung mit konstanten Werten handelt.
kapitel 3.6.2 heisst zwar initialization of non-local objects, bezieht sich aber tatsächlich auf objekte mit static storage duration und das bezieht auch locale static objekte mit ein, (3.7. 1 vers 3 und 4). und weiter (6.7) wird jedes lokale objekt mit s.s.d. zero-initialisiert, bevor irgendeine andere init. stattfindet. und zero-init. (und init. mit konstanten ausdrücken) IST statische initialisierung (3.6.2). folgerung: statische initialisierung ist nicht auf nicht-lokale objekte beschränkt.
und ja, ich weiss, was statische initialisierung ist, das war nie die frage. und non-PODs werden eben NICHT statisch initialisiert; der speicher, den sie belegen wird zero-initialisiert, aber das ist wie gesagt, völlig bedeutungslos. auf ein nicht-POD, dessen konstruktor noch nicht ausgeführt wurde, zuzugreifen, ist undefiniertes verhalten.camper schrieb:
sinnfreies beispiel:
wird xyz vor oder nach x initialisiert?
Das ist aber nicht spezifisch für die eigentliche Problematik. Du sprichst hier ein generelles Problem beim Initialisieren von globalen Objekten an.
Auch wenn sich ein uint48 wie ein elementarer Typ benutzen lässt, so bleibt es doch ein non-POD mit all den verbundenen Konsequenzen. So wie es bei anderen Klassen auch der Fall ist. Das dürfte dem Op mittlerweile schon klar geworden sein.genau davon rede ich die ganze zeit. mit PODs hat man dieses problem nicht, sofern sie durch einen konstanten ausdruck initialisiert werden.
camper schrieb:
und das steht wo?
Nunja, ich hoffe, dass du noch nicht anfängst zu hyperventilieren.
Ansonsten bau dir ein static assert mit sizeof.
es war nur eine einfache frage. wenn es dich nervt, solltest du dir überlegen, ob du überhaupt antworten solltest. wenn es eben eine solche garantie gäbe, wäre das schlicht nützlich zu wissen.
Und wenn schon GC, dann werden benötigte Informationen mit Sicherheit an anderer Stelle gespeichert.
'mit sicherheit'.
-
camper schrieb:
kapitel und vers?
Nur mal als Beispiel
void foo() { int a; } void bar() { static int a; }
Beide Funktionen machen exakt das gleiche, bis auf den Unterschied static. Der sorgt dafür, dass aus automatischem statischer Speicher wird, und damit Auswirkungen auf die Initialisierung hat. Mehr dazu findest du zB unter 3.7.1.
camper schrieb:
kapitel 3.6.2 heisst zwar initialization of non-local objects, bezieht sich aber tatsächlich auf objekte mit static storage duration und das bezieht auch locale static objekte mit ein, (3.7. 1 vers 3 und 4). und weiter (6.7) wird jedes lokale objekt mit s.s.d. zero-initialisiert, bevor irgendeine andere init. stattfindet. und zero-init. (und init. mit konstanten ausdrücken) IST statische initialisierung (3.6.2). folgerung: statische initialisierung ist nicht auf nicht-lokale objekte beschränkt.
Nein, das hast du falsch verstanden. Mit nicht-lokal sprach ich nicht vom Scope, sondern vom Speicher selbst. Das betrifft ja nicht nur static Variablen in Funktionen, sondern auch static Member in Klassen.
camper schrieb:
und non-PODs werden eben NICHT statisch initialisiert; der speicher, den sie belegen wird zero-initialisiert, aber das ist wie gesagt, völlig bedeutungslos
Mag sein, dass zero-initialization bei Nicht-PODs nicht als statische Initialisierung bezeichnet wird. Auch wenn du mir noch keine Quellen diesbzgl. genannt hast. Letztendlich ist es nur eine relativ unbedeutende Begriffsdefinition, an die du dich klammerst. Wichtig ist nur, dass zero-initialisiert wird. Deshalb versteh ich immer noch nicht deinen Besserwisser-Einwand gegen die initializer-list, denn die ist das Einzige, was du bei einem Nicht-POD nicht verwenden kannst.
camper schrieb:
auf ein nicht-POD, dessen konstruktor noch nicht ausgeführt wurde, zuzugreifen, ist undefiniertes verhalten
Das ist richtig. Steht aber im Widerspruch wozu?
camper schrieb:
es war nur eine einfache frage.
Und eine einfache Antwort. Ich kenne zumindest keine Textstelle, die explizit erlaubt, irgendwelche zusätzlichen, dem Anwender nicht sichtbaren, Daten in eine Klasse zu packen. Ausnahmen hierbei sind virtuelle Basisklassen, virtuelle Funktionen und Padding (aufgrund Alignment). Deshalb bin ICH mir sicher, dass nur die Daten in die Klasse gepackt werden, die ich explizit angebe. Wenn DU dir nicht sicher bist, dann mach dir, wie bereits erwähnt, ein static assert.
'mit sicherheit'
So ist das. Siehe oben.
-
hier die vorläufige offizielle endversion meiner int48-klasse
int48.h
#ifndef INT48_H #define INT48_H class int48 { unsigned int lo32; // intel format signed short hi16; public: int48 () : lo32(0), hi16(0) {} int48 (__int64 x) : lo32(x), hi16(x>>32) {} operator __int64 () const { return (__int64)lo32 | ((__int64)hi16)<<32; } }; class uint48 { unsigned int lo32; // intel format unsigned short hi16; public: uint48 () : lo32(0), hi16(0) {} uint48 (unsigned __int64 x) : lo32(x), hi16(x>>32) {} operator unsigned __int64 () const { return (unsigned __int64)lo32 | ((unsigned __int64)hi16)<<32; } }; #endif // INT48_H
kompakter gehts nicht mehr.
hier noch der iostream-ausgabe-operator (keine ahnung ob der so jetzt standard-konform ist):
iostream_int64.h
#ifndef IOSTREAM_INT64_H #define IOSTREAM_INT64_H #include<iostream> using namespace std; ostream &operator<< (ostream &os, unsigned __int64 x); ostream &operator<< (ostream &os, __int64 x); #endif // IOSTREAM_INT64_H
iostream_int64.cpp
#include<iostream> #include<cstring> #include "iostream_int64.h" using namespace std; static char *u64toa (unsigned __int64 x, int b, bool uc) { static char buf[21]; char *cp; cp=buf+22; *--cp='\0'; do { *--cp=(uc ? "0123456789ABCDEF" : "0123456789abcdef")[x%b]; x=x/b; } while(x); return cp; } static void write_uint64 (ostream &os, unsigned __int64 x, bool sign) { int base,w; char *sb,*cp; ios::fmtflags f=os.flags(); if((f & (ios::left|ios::right|ios::internal))==0) f|=ios::left; if(f & ios::oct) { base= 8; sb="0"; } else if(f & ios::hex) { base=16; sb="0x"; } else { base=10; sb=""; } cp=u64toa(x,base,(f & ios::uppercase)!=0); w=os.width()-strlen(cp)-strlen(sb); if(sign || (f & ios::showpos)) --w; if(w>0 && (f & ios::left)) while(w--) os.put(os.fill()); if(sign || (f & ios::showpos)) os.put(sign ? '-' : '+'); if(w>0 && (f & ios::internal)) while(w--) os.put(os.fill()); while(*sb) os.put(static_cast<unsigned char>(*sb++)); while(*cp) os.put(static_cast<unsigned char>(*cp++)); if(w>0 && (f & ios::right)) while(w--) os.put(os.fill()); os.width(0); } ostream &operator<< (ostream &os, unsigned __int64 x) { write_uint64(os,x,false); return os; } ostream &operator<< (ostream &os, __int64 x) { write_uint64(os,(x<0 ? -x : x),x<0); return os; }
funktioniert mit VC++6 einwandfrei. für gcc muß man __int64 natürlich durch long long ersetzen.
euere diskussion zum thema kompatibilität halte ich für überflüssig. zumindest der groovemaster-ansatz, über 64-bit-integers zu arbeiten, ist sowieso nicht portabel, da 64-bit-integers nicht standardisiert sind.
es fehlen allerdings immer noch die iostream-eingabe-operatoren für 64-bit-integers. falls sie nicht schon in der standardbibliothek des compilers vorhanden sind. bei VC++6 sind sie es jedenfalls nicht.
so und jetzt könnt ihr meinen code nach allen regeln der kunst verreißen
-
kompatibilität ist nie gänzlich überflüssig. und bis zu diesem zeitpunkt bist du ja auf die zielplattform nicht eingegangen (oder hab ich das übersehen?).
eine kleine anmerkung bzgl. int48:int48 (__int64 x) : lo32(x), hi16(x>>32) {}
das macht möglicherweise nicht das, was du willst, falls du mit überläufen zu tun hast (keine modulo 2^n arithmetik). auf x86 prozessoren ist modulo immer vorzeichen-bewahrend und bietet sich daher an:
int48 (__int64 x) : lo32(x), hi16((x%(1LL<<47))>>32) {}