| Autor |
Beitrag |
Seven of Nine
      
Beiträge: 132
Erhaltene Danke: 1
Win XP, Win Vista HomePro
Delphi 2009
|
Verfasst: Fr 07.11.08 20:25
Immer mal wieder...
bekomme ich von den Benutzern meiner Software einen Fehlerbericht mit dem ich häufig nichts anzufangen wusste.
Heute hatte ich das Glück herauszufinden woran einer meiner sporadisch auftretenden fehler begründet lag.
-> Die von meine Programm geschriebenen Ini.Dateien konnten NACH einem Upadate oder Änderung der Windows Einstellungen nicht mehr sauber interpretiert werden.
Kurze fehlersuche... Aha deswegn hatte ich den Fehler früher nie gefunden. Es liegt häufig an den geänderten Einstellungen für "DecimalSeparator" und "Thousandseparator"
Ich weiß es gbt heirzu schon einige Threads. So richtig glücklich wurde ich aber mit keinem.
Ich suchte nach einem einem mgl. kurzem einfache Code der diese Fehler nur "eliminiert".
Ziel: Das "Einlesen" per StrToFloat fehertoleranter zu machen
Anbei mein Code dazu (Funktion)
evtl. kann's ja mal jemand gebrauchen. Einfach anstelle der StandradFnktion einbinden/verwenden
lG Martin
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
| Function MyStrToFloat(AString:String):Real; Var I,AnInt,ErrCode,ACount,Pos : Integer; begin ACount := 0; Pos := Length(AString); repeat If (AString[Pos] = '.') or (AString[Pos] = ',') then begin Inc(ACount); If (ACount > 1) then delete(Astring,Pos,1); end; Dec(Pos); until (Pos = 0);
For I:= 1 to length(AString) do begin Val(AString[I],AnInt,ErrCode); If (ErrCode > 0) then AString[I] :=DecimalSeparator; end;
Result := StrToFloat(AString); end; |
Moderiert von Kha: Delphi-Tags hinzugefügt
|
|
Hidden
      
Beiträge: 2242
Erhaltene Danke: 55
Win10
VS Code, Delphi 2010 Prof.
|
Verfasst: Fr 07.11.08 20:54
Hi,
Schreib' dir doch einfach eine eigene Funktion, in der du vor einem StrToFloat ein StrReplace einfügst
Delphi-Quelltext 1: 2: 3: 4:
| function MyStrToFloat(const S: String): Extended; begin result := StrToFloat(StrReplace(StrReplace(S, ',', DecimalSeperator, []), '.', DecimalSeperator, [])); end; |
Schöner wäre natürlich ein const Array[Boolean] of Char = (',', '.'). Dann könntest du ein StrReplace mit Seperators[DecimalSeperator = ','], DecimalSeperator machen. Bin mir aber spontan nicht sicher, ob decimalSeperator theoretisch auch andereWerte als , und . enthalten kann.
Wenn du das ausschließen kannst:
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| function MyStrToFloat(const S: String): Extended; const Seperators: Array[Boolean] of char = (',', '.'); begin result := StrToFloat(StrReplace(S, Seperators[DecimalSeperator = ','], DecimalSeperator)); end; |
Ob jetzt der erste oder der zweite Wert im Array[Boolean] false oder true war, weiß ich nichtmehr. Musst du dann anpassen
mfG,
_________________ Centaur spears can block many spells, but no one tries to block if they see that the spell is a certain shade of green. For this purpose it is useful to know some green stunning hexes. (HPMoR)
|
|
Seven of Nine 
      
Beiträge: 132
Erhaltene Danke: 1
Win XP, Win Vista HomePro
Delphi 2009
|
Verfasst: Sa 08.11.08 06:11
Danke für den Hinweis Daniel,
ich finde aber trotzdem das meine Funktion genau so Sinn macht. Gründe:
a) Meines Wissens kann der DecimalSeparator auch andere Werte als "." und "," haben. Genau deswegen bin ich den Weg gegangen ale zeichen des übergebenen Strings die "Nicht Zahlen" sind (per Überprüfung mit val Funktion) durch den gerade gültigen DecimalSeparator zu ersetzen. DAuert evtl eine ms länger in der Codeausführung, sollte aber dafür immer stimmen
b) TausenderSeparatoren können (wenn auch selten) vorkommen. Dies wird durch den ersten teil der Funktion ausgeschlossen.
Der Code ist zwar etwas unübersichtlich, löscht einfach, von rechten Teil des übergebenen Strings ausgehend, alle evtl vorhandenen TausenderSeparatoren. "Quick and drity" aber effizient
lG Martin
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Sa 08.11.08 09:22
So einfach ist das nicht. Z.b. sind bei uns im Vergleich zur USA Dezimalpunkt und Tausendtrenner vertauscht. Wenn man also formatierte Zahlen bekommt, weiss man nicht unbedingt, was sie bedeuten.
Aber eine kleine Heuristik hilft: Tausendertrennzeichen unterteilen eine Ziffernfolge in Dreiergruppen ('1.234.567,890').
Sei S eine Zahl, die mit unbekannten Trennzeichen formatiert wurde ("1,234,56" ist also nicht erlaubt) und L die Länge.
Sei ferner P die Position des ersten Sonderzeichens (keine Ziffer) und C dieses Zeichen.
Nun schaue ich mir das Zeichen vier Stellen weiter rechts an:
1. Es ist das gleiche Zeichen: C ist der Tausendtrenner. Wiederhole die Hewuristik mit P = P+4.
2. Es ist eine Ziffer. C ist der Dezimalpunkt. Der Tausendtrenner ist unbestimmt.
3. Es ist ein anderes Zeichen: C ist der Tausendtrenner und das andere Zeichen der Dezimalpunkt.
3. Es gibt dort kein Zeichen (P+4>L). Dann ist C ein Dezimalpunkt, wenn L<5. Der Tausendtrenner ist unbestimmt.
3.1 Wenn L = 5 (z.B. '1.234') wissen wir nicht, was C ist.
Wenn wir nun eine Menge von Zahlenstrings in einem unbestimmten Format haben, müssen wir nur einen String der Länge > 5 nehmen, und ihn durch die o.g. Heuristik jagen: Voila, wir wissen nun genau, was der Dezimalpunkt, und was der Tausendtrenner ist. Vorausgesetzt, diese Zeichen kommen in den Zahlenstrings vor.
Wir legen uns jedoch die Karten, wenn wir nur Zahlen der Form '1.234' haben. Hier ist das numerische Äquivalent einfach nicht zu bestimmen.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
| Function MyStrToFloat(NumString: string): Double; Var i : Integer; cThousandSep, cDecimalSep: Char
procedure _Analyze; var C, C1: Char; L, P: Integer;
function _NextNonDigit(iStart: Integer): Integer; begin Result := iStart+1; while (Result <= L) and (NumString[Result] in ['0'..'9']) do Inc(Result); end;
begin cThousandSep := '?'; cDecimalSep := '?'; L := Length(NumString); P := 0; P := _NextNonDigit(P); if P > L then Exit; C := NumString[P];
Inc(P, 4); if P > L then begin if L < 5 then cDecimalSep := C; Exit; end; repeat C1 := NumString[P]; if C1 = C then begin cThousandSep := C; P := _NextNonDigit(P); end else if C1 in ['0'..'9'] then begin cDecimalSep := C; Break; end else begin cDecimalSep := C1; cThousandSep := C; Break; end; until P > L; end;
begin _Analyze; If (cDecimalSep='?') and (cThousandSep='?') Then Raise Exception.Create('Zahl kann nicht eindeutig konvertiert werden'); NumString := StringReplace(NumString,cThousandSep,'',[rfReplaceAll]); NumString := StringReplace(NumString,cDecimalSep,DecimalSeparator,[rfReplaceAll]); Result := StrToFloatDef(NumString,-1); end; |
_________________ Na denn, dann. Bis dann, denn.
Zuletzt bearbeitet von alzaimar am So 09.11.08 11:50, insgesamt 1-mal bearbeitet
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: Sa 08.11.08 11:01
Wenn du an einer sauberen Lösung interessiert bist, speichere die Daten entweder in einem klar definierten Format ab oder speichere das Format (Trennzeichen) der Zahlen mit.
Die automatische Erkennung oder die tolerante Konvertierung halte ich für die falsche Lösung.
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Sa 08.11.08 17:10
Klar, das ist die beste Lösung, Aber was tun, wenn es ein Fremdformat ist oder die Daten aus Unbekanntistan kommen?
_________________ Na denn, dann. Bis dann, denn.
|
|
delfiphan
      
Beiträge: 2684
Erhaltene Danke: 32
|
Verfasst: Sa 08.11.08 21:11
In diesem Fall geht es ja um seine eigene Software und nicht um Daten aus Unbekanntistan. Aber klar, wenn die Software schon ausgeliefert wurde, tja dann geht's wohl nicht anders
Die Autoerkennung funktioniert wie du's selbst schon gesagt hast nicht in allen Fällen. Aber immerhin kann man das Format entweder rausfinden oder sagen, dass das Format unbestimmbar ist. Besser als gar nichts. Für eine Übergangslösung, einverstanden. Übrigens sehe ich gerade bei den Einstellungen z.B. für "Hindi (India)" folgendes Format: 12,34,56,789.00 oder bei "Inuktitut (Syllabics, Canada)": 123456,789.00
Aber wie ich schon sagte würde ich die Ini-Datei für die Zukunft anpassen.
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Sa 08.11.08 23:22
delfiphan hat folgendes geschrieben : | | Besser als gar nichts. |
Und schlechter als jede richtige Lösung. Und deshalb... delfiphan hat folgendes geschrieben : | | Aber wie ich schon sagte würde ich die Ini-Datei für die Zukunft anpassen. |
Indem ganz auf Tausendertrennzeichen verzichtet wird und ein fixer Dezimaltrenner verwendet wird. Oder einfach XML nehmen.
Übrigens klappt meine Heuristik auch bei den Inuit. Nur Inder haben's schwer.
_________________ Na denn, dann. Bis dann, denn.
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: So 09.11.08 01:14
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: So 09.11.08 01:46
_________________ Na denn, dann. Bis dann, denn.
|
|
Aya
      
Beiträge: 1964
Erhaltene Danke: 15
MacOSX 10.6.7
Xcode / C++
|
Verfasst: So 09.11.08 03:44
Hi,
ich habe kein Delphi zur hand um das als code zu testen (und auch schon sehr lang kein Delphi mehr programmiert), deswegen kann ich nicht garantieren das der code unten fehlerfrei funktioniert:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| function strToFloat42(Text: String) var pre, post: String; foundSep: Boolean; i: Integer; begin foundSep:=false; for i:=Length(Text) downto 1 do begin if Text[i] in ['0'..'9'] then begin if foundSep then pre:=Text[i] + pre else post:=Text[i] + post; end else foundSep:=true; end; result:=pre + '.' + post; end; |
Was ich hier tue ist einfach den String von hinten nach vorn durchzugehen. Das erste zeichen das keine zahl ist nehme ich als DezimalSeparator an und ignoriere alle weiteren nicht-Zahlen.
Beispiele:
123,45 würde zu 123.45
1.2.3,45 würde zu 123.45
12#34 würde zu 12.34
123bla45 würde zu 123.45
Aya
_________________ Aya
I aim for my endless dreams and I know they will come true!
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: So 09.11.08 11:20
Was ist mit 123.456.789? oder mit '1.234'?
_________________ Na denn, dann. Bis dann, denn.
|
|
Lannes
      
Beiträge: 2352
Erhaltene Danke: 4
Win XP, 95, 3.11, IE6
D3 Prof, D4 Standard, D2005 PE, TurboDelphi, Lazarus, D2010
|
Verfasst: So 09.11.08 11:29
Hallo,
'1.234' kann ohne weitere Informationen nie eindeutig interpretiert werden.
Der Punkt kann ein DecimalSeparator oder ThousandSeparator sein.
_________________ MfG Lannes
(Nichts ist nicht Nichts) and ('' <> nil ) and (Pointer('') = nil ) and (@('') <> nil )
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: So 09.11.08 11:48
Lannes hat folgendes geschrieben : | | '1.234' kann ohne weitere Informationen nie eindeutig interpretiert werden. |
Richtig, aber o.g. Code interpretiert ihn u.U. falsch. Mein Code hingegen trifft implizit genau diese Ausage ('Nicht eindeutig'), das die Separatoren nicht bestimmt werden.
_________________ Na denn, dann. Bis dann, denn.
|
|
|