Ich schreibe einen C ++ - Parser in Rust für ein altes ASCII-Datenformat neu. Reelle Zahlenwerte in diesem Format dürfen in jedem von Fortran erkannten Format gespeichert werden. Leider erkennt Fortran einige Formate, die von Rust (oder den meisten anderen Sprachen) nicht erkannt werden. Beispielsweise könnte der Wert 101.01 als dargestellt werden

  • 101.01
  • 1.0101E2
  • 101.01e0
  • 101.01D0
  • 101.01d0
  • 101,01 + 0
  • 1010.1-1

Die ersten drei werden alle von Rust nativ anerkannt. Die restlichen vier sind eine Herausforderung. In C ++ verwenden wir die folgende Routine, um diese Werte zu analysieren:

double parse(const std::string& s){
  char* p;
  const double significand = strtod(&s[0], &p);
  const long exponent = (*p == '\0') ? 
                          0 : isalpha(*p) ?
                            strtol(p+1, nullptr) :
                              strtol(p, nullptr);
  return significand * pow(10, exponent);
}

Nach Durchsicht der Rust-Dokumentation scheint es nicht so zu sein, dass die Standardbibliothek eine teilweise Zeichenfolgenanalyse im Sinne von strtod und strtol bietet. Ich möchte aus Leistungsgründen vermeiden, mehrere Durchgänge über die Zeichenfolge zu machen oder reguläre Ausdrücke zu verwenden.

3
apmccartney 26 Dez. 2015 im 23:55

2 Antworten

Beste Antwort

Dies wäre ein Kommentar zu Veedracs Antwort gewesen, aber für einen Kommentar wurde es etwas lang.

Wie Veedrac erklärt, ist das genaue Parsen von Floats schwierig . Die Implementierung in der Standardbibliothek ist absolut genau und ziemlich gut optimiert. Insbesondere ist es für die meisten Eingaben, bei denen der naive Algorithmus funktioniert, nicht viel langsamer als der naive ungenaue Algorithmus. Du solltest es benutzen. Vollständiger Haftungsausschluss: Ich habe es geschrieben.

Ich bin mit Veedrac nicht einverstanden, wie Sie vorgehen müssen, wenn Sie diesen Code wiederverwenden möchten. Es ist eine schlechte Idee, es aus der Standardbibliothek herauszureißen. Es ist riesig, ungefähr 2,5k Codezeilen, und es ändert sich immer noch / wird gelegentlich verbessert - obwohl selten und meistens auf sehr geringfügige Weise. Aber eines Tages werde ich die Zeit finden, den langsamen Weg neu zu schreiben, um besser und schneller zu werden, versprochen. Wenn Sie diesen Code herausreißen, müssen Sie das Modul core::num::dec2flt nehmen und das Submodul parse ändern, um andere Exponenten zu erkennen. Dann profitieren Sie natürlich nicht automatisch von zukünftigen Verbesserungen, was schade ist, wenn Sie an Leistung interessiert sind.

Am sinnvollsten wäre es, die anderen Formate in das von Rust unterstützte Format zu übersetzen. Wenn es sich um ein d, D oder ein nacktes + handelt, können Sie es einfach durch ein e ersetzen und an einen String weitergeben. Nur im Fall 1010.1-1 müssen Sie ein e einfügen und den Exponententeil der Zeichenfolge verschieben. Dies sollte nicht viel Leistung kosten. Float-Strings sind kurz (höchstens 20 Bytes, oft viel weniger) und die eigentliche Konvertierungsarbeit erledigt einen guten Teil der Arbeit pro Byte. Dies gilt auch für Ihren C ++ - Code, da strtod in glibc genau ist auch. Zumindest versucht es, den darauf basierenden Ad-hoc-Algorithmus nicht zu reparieren. Auf jeden Fall versucht es.

Eine andere Möglichkeit besteht darin, FFI zu verwenden, um Cs strtod aufzurufen. Verwenden Sie die libc crate und rufen Sie libc::strtod auf. Dies erfordert einige Verzerrungen, um von &str zu rohen Zeigern auf c_char zu übersetzen, und es wird innere 0 Bytes schlecht behandeln, aber der Code, den Sie zeigen, ist ohnehin nicht schrecklich robust. Damit können Sie Ihren Algorithmus mit identischer Leistung, Semantik und (Un-) Genauigkeit in Rust übersetzen.

4
26 Dez. 2015 im 23:45

Ihr Beispiel in C ++ liefert keine perfekt genauen Ergebnisse, aber Rusts Float-Analyse soll absolut genau sein und hat daher eine langsamere Analyse, als Sie möglicherweise benötigen.

Wenn Sie die ungefähre Analyse manuell implementieren, wird sie wahrscheinlich schneller als jede andere verfügbare Technik ausgeführt. Ein schneller Test, den ich lokal durchgeführt habe, legt nahe, dass Sie leicht einen Faktor 5 über die Leistung der parse -Methode der Standardbibliothek erhalten können.

Wenn Sie lieber genau analysieren möchten, reicht Ihr C ++ - Code nicht aus. Eine Voranalyse (z. B. mit Regex) ist wahrscheinlich der einfachste Weg, dies zu tun. Alternativ können Sie den Code aus der Standardbibliothek herausreißen und ändern.

2
Veedrac 27 Dez. 2015 im 04:02