Fortran Zahlenformat mit C++ einlesen?
-
Hallo zusammen,
ich muss eine ASCII-Datei auslesen. In dieser Datei stehen Zeilen wie diese:
GRID 21864 -.4604962.-10 -1.55256Dabei ist GRID ein Schlüsselwort, 21864 eine ID und der letzte Zahlenblock sind 3 Koordinaten. Die Datei basiert glaube ich noch auf Fortran. Jede Koordinate hat 8Stellen. (mit Dezimalpunkt und Vorzeichen usw.) Ich habe bis jetzt die Zeilen mit
sscanf(str.c_str(),"%4s %d %8f %8f %8f",&str4,&ID,&x1,&x2,&x3);ausgelesen. Nun steht in der Zeile oben aber 2.-10 was 2e-10 darstellen soll. Mein Code liest hier aber 2.0 und -10 ein. Kann man ja auch fast nachvollziehen. Gibt es eine Möglichkeit dies richtig einzulesen? Ich habe schon lange gesucht und nichts gefunden. Bei printf kann man ja die minimale und maximale Anzahl an zu schreibenden Daten angeben, bei sscanf aber nur die maximale wenn ich richtig gelesen habe. Ich wäre über eure Hilfe sehr dankbar.
LG Gebb
-
Erstmal solltest du C++-Streams lernen. Dazu solltest du lernen, lieber die ganze Datei neu zu schreiben, denn ich kann da keine "Drei" Koordinaten erkennen...
std::ifstream stream("DataName.txt"); stream >> stdstring >> intID >> vector3D;Wobei du vllt. eine Klasse vector3D erstellst (für Koordinaten), oder gleich eine Klasse für eine Entity definierst (mit Codename, ID, vector3D).
Und schreib besser die ganze Datei nochmal neu.
-
Ich nutze schon die Streams nur konnte ich sie für meine Zwecke nicht so nutzen wie ich es gerne hätte.
Um die Datei neu zu schreiben müsste ich sie auch erst einmal adäquat auslesen können, denn die Datei wird mir so von einer (kommerziellen) Software ausgegeben, d.h. ich habe keine Möglichkeitkeit diese Formatierung zu ändern.
Die Koordinaten in diesem Beispiel wären-.460496(-0.46049699), 2.-10 (2e-10) und -1.55256. Wie bereits erwähnt sind dies 3 Felder der Größe 8. Das Problem, dass man bei vollen Feldern kein Leerzeichen hat habe ich mit der maximalen Stellenanzahl beim Einlesen in den Griff gekriegt. Jedoch habe ich keine Ahnung wie ich die 2.-10 einlesen kann.
-
Wenn du die Datei nicht neu schreiben lassen kannst, würde ich evtl. eine eigene Klasse mit eingenem Einlese-Operator schreiben, etwa wie folgt:
class FortranDouble { double d; public: operator double() { return d; } friend istream& operator>>(istream& is, FortranDouble& fd) { // 8 Zeichen einlesen, parsen return is; } }; int main() { string grid; int id; std::array<double, 3> ds; ifstream if("dieDatei"); if >> grid; if >> id; for (unsigned i = 0; i < 3; ++i) { FortranDouble fd; if >> fd; ds[i] = fd; } }Hingeschludert, ungetestet, nur um ne Idee zu geben, wie es laufen könnte.
-
ja danke, habe ich auch gemacht. Dies sieht dann so aus
istringstream istr; istringstream istr1; istringstream istr2; istringstream istr3; getline(input2,str); str3=str; string coord1; string coord2; string coord3; str.erase(0,24); for (int i=0;i<8;i++) coord1.append(1,str[i]); str.erase(0,8); for (int i=0;i<8;i++) coord2.append(1,str[i]); str.erase(0,8); for (int i=0;i<str.size();i++) coord3.append(1,str[i]); size_t found1=coord1.find_first_of("-",0); if(found1==0) { found1=coord1.find_first_of("-",1); if(found1==string::npos) ; else coord1.insert(found1,"e"); } else if(found1>0) { if(found1==string::npos) ; else coord1.insert(found1,"e"); } size_t found2=coord2.find_first_of("-",0); if(found2==0) { found2=coord2.find_first_of("-",1); if(found2==string::npos) ; else coord2.insert(found2,"e"); } else if(found2>0) { if(found2==string::npos) ; else coord2.insert(found2,"e"); } size_t found3=coord3.find_first_of("-",0); if(found3==0) { found3=coord3.find_first_of("-",1); if(found3==string::npos) ; else coord3.insert(found3,"e"); } else if(found3>0) { if(found3==string::npos) ; else coord3.insert(found3,"e"); } istr1.str(coord1); istr1>>x1; istr2.str(coord2); istr2>>x2; istr3.str(coord3); istr3>>x3; istr.str(str3); istr>>str2; istr>>ID;Das ist nur der wichtige Teil, falls ein paar Deklarationen fehlen bitte ich dies zu entschuldigen. Ich hoffe es hilft vielleicht jem der das selbe Problem hat. Ist zwar vielleicht nicht sehr elegant aber es funktioniert. Es wird dabei auch zwischen mit und ohne führenden Minuszeichen und Minus hinter dem Dezimalpunkt unterschieden. Im zweitem Fall wird an der Stelle des Minus ein e für den Exponentialteil eingefügt. Die oben dargestellte Zeile kann somit wie gewünscht ausgelesen werden.
LG Gebb
-
Gebb schrieb:
ich muss eine ASCII-Datei auslesen. In dieser Datei stehen Zeilen wie diese:
GRID 21864 -.4604962.-10 -1.55256Dabei ist GRID ein Schlüsselwort, 21864 eine ID und der letzte Zahlenblock sind 3 Koordinaten. Die Datei basiert glaube ich noch auf Fortran. Jede Koordinate hat 8Stellen.
Hallo Gebb,
wie schon gesagt, ist das mit Hausmitteln nicht möglich, insbesondere auch deshalb nicht, weil die Zahlen ohne Trennzeichen (Zwischenraum) hinter einander stehen. Man kann aber den Code, der eine Zahl liest gegen ein Stück selbst geschriebenen Code austauschen - das ist die sogenannte Facette num_get. In vorliegendem Fall die Methode, die einen double-Wert liest. Und weil die Zahlen hinter einander stehen, muss der Facette auch vorher mitgeteilt werden, wo die erste Zahl aufhört und die zweite anfängt - also wie lang die Zahl ist, ich verwende dazu den Manipulator std::setw, deren Wert ich später mit width auslese.
Das ist wahrlich kein Anfänger-Code, aber im Grunde die einzige Möglichkeit, wie man das sauber hin bekommt.
#include <fstream> #include <iostream> #include <locale> // isspace, u.a. #include <iomanip> // setw #include <cmath> // pow #include <string> // -- liest eine linksbündige Zahl, mit einer definierten Länge ein. // Der (optionale) Exponent steht als Integer (ohne 'E') unmittelbar // hinter der Mantisse. // Die Anzahl der Zeichen, die die Zahl einnimmt, MUSS vorher // mit setw(Breite) besetzt werden. // Beispiel: setw(8) '2.-11 ' = 2.E-11 class FortranNumGet : public std::num_get< char > { protected: virtual iter_type do_get( iter_type src, iter_type to, std::ios_base& ios_, std::ios_base::iostate& err, double& v ) const { const std::locale myLoc = ios_.getloc(); int width = int(ios_.width(0)); if( src == to ) { err |= std::ios_base::failbit | std::ios_base::eofbit; return src; } if( width == 0 ) { err |= std::ios_base::failbit; return src; } bool plus = true; char c = *src; // -- Vorzeichen lesen if( c == '-' ) { ++src; --width; plus = false; } else if( c == '+' ) { ++src; --width; } bool readdigit = false; v = 0.; // -- Vor- und Nachkommastellen bis 'Space' oder width==0 const char punkt = std::use_facet< std::numpunct< char > >( myLoc ).decimal_point(); double nk = 0.; for( ; width > 0 && src != to; --width, ++src ) { c = *src; if( c >= '0' && c <= '9' ) { readdigit = true; (v *= 10) += int(c - '0'); nk *= 10; } else if( c == punkt ) { if( nk != 0. ) { err |= std::ios_base::failbit; // Dezimaltrenner doppelt return src; } nk = 1.; } else break; } if( !readdigit ) { err |= std::ios_base::failbit; return src; } if( nk != 0. ) v /= nk; if( !plus ) v = -v; // -- ggf. Exponenent lesen (ohne 'E'!) if( width > 0 && src != to && (c == '-' || c == '+')) { plus = (c == '+'); --width; ++src; // Vorzeichen schlucken readdigit = false; int expo = 0; for( ; width > 0 && src != to; --width, ++src ) { c = *src; if( c >= '0' && c <= '9' ) { readdigit = true; (expo *= 10) += int(c - '0'); } else break; } if( !readdigit ) { err |= std::ios_base::failbit; // hinter dem Exponentenvorzeichen kommt keine Ziffer return src; } v *= std::pow( 10., plus? expo: -expo ); } // -- Füllcharacter überlesen for( ; width > 0 && src != to && isspace( *src, myLoc ); --width, ++src ) ; if( width > 0 && src != to ) err |= std::ios_base::failbit; // unerwartetes Zeichen gefunden return src; } }; // ----------------------- struct Eintrag { int ID; // eine ID double x, y, z; // 3 Koordinaten }; // -- Eintrag Lesen und Schreiben std::istream& operator>>( std::istream& in, Eintrag& e ) { using std::setw; return in >> e.ID >> setw(8) >> e.x >> setw(8) >> e.y >> setw(8) >> e.z; } std::ostream& operator<<( std::ostream& out, const Eintrag& e ) { return out << e.ID << " {" << e.x << " " << e.y << " " << e.z << "}"; } int main() { using namespace std; ifstream in("input.txt"); if( !in.is_open() ) { cerr << "kann Datei nicht oeffnen" << endl; return -2; } in.imbue( locale( in.getloc(), new FortranNumGet ) ); for( string schluessel; in >> schluessel; ) { if( schluessel == "GRID" ) { Eintrag e; if( in >> e ) cout << "Gelesen: " << e << endl; } else { cout << "unbekanntes Schluesselwort: " << schluessel << endl; break; } } return 0; }Steht in der Datei 'input.txt' die eine von Dir beschrieben Zeile, so liefert obiges Programm folgenden Output:
Gelesen: 21864 {-0.460496 2e-010 -1.55256}Gruß
Werner
-
Danke Werner,
ja da hast recht, dass ist echt kein Anfänger-Code.
Selbst wenn ich mich nicht mehr als Anfänger bezeichnen würde
Aber funktioniert 
LG Gebb