Autor Beitrag
Seven of Nine
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 132
Erhaltene Danke: 1

Win XP, Win Vista HomePro
Delphi 2009
BeitragVerfasst: 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

ausblenden 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
  //Delete existing ThousandSeperators first
  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);

  //Change to DecimalSeparator of current Windows-Version
  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 user profile iconKha: Delphi-Tags hinzugefügt
Hidden
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 2242
Erhaltene Danke: 55

Win10
VS Code, Delphi 2010 Prof.
BeitragVerfasst: Fr 07.11.08 20:54 
Hi,

Schreib' dir doch einfach eine eigene Funktion, in der du vor einem StrToFloat ein StrReplace einfügst ;)

ausblenden 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:

ausblenden 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 132
Erhaltene Danke: 1

Win XP, Win Vista HomePro
Delphi 2009
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: 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.

ausblenden volle Höhe 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:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2684
Erhaltene Danke: 32



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Sa 08.11.08 23:22 
user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
Besser als gar nichts.
Und schlechter als jede richtige Lösung. Und deshalb...
user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: So 09.11.08 01:14 
user profile iconalzaimar hat folgendes geschrieben Zum zitierten Posting springen:
Indem ganz auf Tausendertrennzeichen verzichtet wird und ein fixer Dezimaltrenner verwendet wird.

Genau. Mach ich immer... Am besten übrigens amerikanisch mit Punkt, dann kann man das Komma noch für Aufzählungen nehmen.

user profile iconalzaimar hat folgendes geschrieben Zum zitierten Posting springen:
Oder einfach XML nehmen.

Was genau hat der Container mit dem Format zu tun?

_________________
"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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: So 09.11.08 01:46 
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconalzaimar hat folgendes geschrieben Zum zitierten Posting springen:
Oder einfach XML nehmen.

Was genau hat der Container mit dem Format zu tun?

XML ist -wohl definiert- zickig und erlaubt nur ein Format in einer Reihenfolge mit einem Zeichensatz. Das gibt es keine Mißverständnisse. Nur Frust, bis es funktioniert. :zwinker: Aber dann is Ruhe im Karton.

_________________
Na denn, dann. Bis dann, denn.
Aya
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1964
Erhaltene Danke: 15

MacOSX 10.6.7
Xcode / C++
BeitragVerfasst: 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:

ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: So 09.11.08 11:20 
Was ist mit 123.456.789? oder mit '1.234'?

_________________
Na denn, dann. Bis dann, denn.
Lannes
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2352
Erhaltene Danke: 4

Win XP, 95, 3.11, IE6
D3 Prof, D4 Standard, D2005 PE, TurboDelphi, Lazarus, D2010
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: So 09.11.08 11:48 
user profile iconLannes hat folgendes geschrieben Zum zitierten Posting springen:
'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.