UTF-8 Gleichheitsoperator Bug?

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

Hallo Leute,

FPC-Version 3.1.1
ich trau mich ja schon gar nicht mehr posten, zum Thema UTF-8

Folgendes Problem beschäftigt mich gerade:
Zwei UTF-8-Strings, der eine heißt s1 der andere s2. Beide haben den Inhalt "wärme" (kleingeschrieben).

Code: Alles auswählen

Überwachte Ausdrücke (string bzw. "Speicherauszug"):
s1                     wärme
s1                     77 c3 a4 72 6d 65 00 85 00 00 65 00 20 00 2e 00 2e 00 2e 00 00 00 00 74 65 72...
 
s2                     'wärme'
s2                     77 c3 a4 72 6d 65 00 00 00 00 00 00 00 00 00 20 85 00 65 00 75 00 67 00 75 00...


Ein Vergleich mit dem <>-Operator liefert allerdings true. =-Operatur liefert false. Hmm.

Hab's nun zeichenweise probiert, und es zeigt sich, dass fer Vergleich beim "ä" fehlschlägt.

Code: Alles auswählen

 
   var
     j:integer;
     s1,s2:string;
     gleich:boolean;
     a,b:string;     
.....
    for j:=1 to UTF8Length(s1) do
    begin
     a:=UTF8Copy(s1,j,1);
     b:=UTF8Copy(s2,j,1);
     if a<>b then
     gleich:=false;
    end;
    if gleich=false then
    ShowMessage('gleich=false');
    if s1=s2 then
    ShowMessage('= funktioniert');


Am Breakpoint gleich:=false, beim zweiten Mal, wenn a='ä' und b='ä' sind, zeigt sich folgender Speicherauszug:

Code: Alles auswählen

Überwachte Ausdrücke (string bzw. "Speicherauszug"):
 
a                     ä
a                     c3 a4 00 ba 0d f0 ad ba 0d f0 ad ba 0d f0 ad ba 0d f0 ad ba 0d f0  ad ba 0d f0...
 
b                     ä
b                     c3 a4 00 c2 a4 72 6d 65 00 00 65 00 20 00 26 20 00 00 2e 00 00 00 00 74 65 72...


Kann es vielleicht sein, dass der =/<>-Operatur unabsichtlich z.B. ein Byte mehr hinter dem String mitvergleicht, welches normalerweise zufällig 0 ist, oder so?!

Hat jemand eine Idee?

LG, Helmut
Zuletzt geändert von HHick123 am Mi 9. Mär 2016, 20:56, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von wp_xyz »

Ich weiß nicht, was du machst, aber bei mir funktioniert's (nur ein Formular mit 3 Buttons):

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
const
  s1 = 'wärme';
var
  s2: string;
begin
  s2 := 'wärme';
  if s1 = s2 then ShowMessage('=: gleich') else ShowMessage('=: verschieden');
  if s1 <> s2 then ShowMessage('<>: verschieden') else ShowMessage('<>: gleich');
end;
 
procedure TForm1.Button2Click(Sender: TObject);
var
  s1, s2: String;
begin
  s1 := 'w' + 'ä' + 'r' + 'm' + 'e';
  s2 := 'wärme';
  if s1 = s2 then ShowMessage('=: gleich') else ShowMessage('=: verschieden');
  if s1 <> s2 then ShowMessage('<>: verschieden') else ShowMessage('<>: gleich');
end;
 
procedure TForm1.Button3Click(Sender: TObject);
const
  s1 = 'wärme';
  s2 = 'wärme';
var
  j: Integer;
  a, b: String;
  gleich: Boolean;
begin
  gleich := true;   // das fehlt bei dir !!!
  for j:=1 to UTF8Length(s1) do
  begin
     a:=UTF8Copy(s1,j,1);
     b:=UTF8Copy(s2,j,1);
     if a<>b then
       gleich:=false;
  end;
  if gleich=false then
    ShowMessage('gleich=false');
  if s1=s2 then
    ShowMessage('= funktioniert');
end;

Wahrscheinlich liegt's an der fehlenden Initialisierung von "gleich".

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

Hi, gleich ist bei mir natürlich auch mit true initialisiert. Daran liegt's leider nicht ;-)

Ich könnte mir vorstellen, dass der Unterschied darin besteht, dass Du die Strings im Code direkt setzt z.B. s2:='wärme' und ich nicht. Wenn ich s1 und s2 auf 'wärme' setze funktioniert der Vergleich bei mir auch. Allerdings bastle sie relativ kompliziert zusammen, dabei mach' ich vermutlich irgendetwas falsch, sodass meine Strings kein wohlgeformtes UTF-8 sind, vermute ich.

Aber in den überwachten Ausdrücken erscheinen die beiden doch als wärme. Allerdings einer mit einfachen Anführungszeichen (diese sind allerdings nicht im String), aber ich würde daraus schliessen, dass da etwas "anders" ist.
Kann man vielleicht aus dem Speicherauszug irgendetwas sehen?

Eine Sache noch: Mein Programm vergleicht zehntausende Strings und in der Regel klappt es auch (ich denke es klappt in der Regel auch mit Umlauten). D.h. ich hätte den Fehler auch um ein Haar nicht bemerkt. Was ist also gerade an diesen beiden Strings "wärme" so besonderes?

LG, Helmut

Mathias
Beiträge: 6164
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von Mathias »

Was kommt da raus ?

Code: Alles auswählen

  WriteLn(s1);
  WriteLn(Length(s1));
  WriteLn(s2);
  WriteLn(Length(s2))
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von wp_xyz »

HHick123 hat geschrieben: Allerdings bastle sie relativ kompliziert zusammen, dabei mach' ich vermutlich irgendetwas falsch, sodass meine Strings kein wohlgeformtes UTF-8 sind, vermute ich.

Ich auch - das wär ja ein Ding, wenn fpc bei so einer grundlegenden Operation scheitern würde. Versuche doch, dieses Verfahren soweit zusammenzustreichen, dann du es als kleines compilierbares Projekt hochladen kannst. In der Regel findest du den Fehler dabei selber, bzw. wir können hier dir dann besser helfen statt zu raten.

Achja noch: Was verstehst du unter "Speicherauszug"?

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Re: UTF-8 Gleichheitsoperator: kein Bug !

Beitrag von mschnell »

HHick123 hat geschrieben:Kann es vielleicht sein, dass der =/<>-Operatur unabsichtlich z.B. ein Byte mehr hinter dem String mitvergleicht, welches normalerweise zufällig 0 ist, oder so?!

Hat jemand eine Idee?

Vergleich zwischen Unicode Texten ist eine ausgesprochen komplizierte Angelegenheit.

Abgesehen von "Trivialitäten" wie Groß-Kleinschreibung (in verschidenen Ländern werden möglicherweise andere Zeichen als die "Groß" - Version desselben kleinen Zeichens angesehen), kann manchmal dasselbe Druckbare Zeichen in Unicode auf zig unterschiedliche Versionen Codiert werden. Sind die dann gleich oder nicht ?

Das ist kein Bug von Lazarus.

-Michael

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von Socke »

wp_xyz hat geschrieben:Vergleich zwischen Unicode Texten ist eine ausgesprochen komplizierte Angelegenheit.

Abgesehen von "Trivialitäten" wie Groß-Kleinschreibung (in verschidenen Ländern werden möglicherweise andere Zeichen als die "Groß" - Version desselben kleinen Zeichens angesehen), kann manchmal dasselbe Druckbare Zeichen in Unicode auf zig unterschiedliche Versionen Codiert werden. Sind die dann gleich oder nicht ?

Das ist kein Bug von Lazarus.

Im Allgemeinen hast du Recht; hier können ggf. Datenbanken helfen, da diese umfangreiche Collations (Interpretationen von Bytefolgen als Text) eingebaut haben.
Im hier verhandelten Fall sind aber die Speicherinhalte byteweise identisch.

@Helmut: kannst du ein vollständiges, lauffähiges Beispielprogramm erstellen?
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

Ja gerne, ich hab' das Programm bis auf's wesentliche abgespeckt (anbei).

Im Grunde genommen lese ich aus einer Ansi-codierten Datei (im zip-File anbei) strings ein, ca. so:

Code: Alles auswählen

 
type TDictionaryEntry =
record
 GermanLong:string;
 GermanShort:string;
 EnglishLong:string;
 EnglishShort:string;
end;
 
type
 TDictionary=array of TDictionaryEntry;
 
var
 dictionary:TDictionary;
 f:textfile;
 
 
procedure Initialize;
var
 sa,sb,sc,sd:string;
 l:TListEnumerator;
begin
 setlength(dictionary,0);
 assignfile(f,'KKS.csv');
 reset(f);
 while not(eof(f)) do
 begin
  readln(f,sa);        //Stelle A
  sb:=AnsiToUTF8(sa);  //Stelle B
 
  l.Init(sb,';');
  sc:=l.NextItem;      //Stelle C
 
  sd:=AnsiToUTF8(sc);  //Stelle D
 
  setlength(dictionary,length(dictionary)+1);
  dictionary[length(dictionary)-1].GermanLong:=sd;
 
 end;
 closefile(f);
end;       
 


Was mir gleich mal verdächtig vorkommt: An der Stelle B scheint der String, wie erwartet, UTF-8 zu sein.
An der Stelle C jedoch hab' ich - wieder erwarten - die UTF-8-Codierung verloren. Wieder ANSI. Warum verstehe ich nicht (kann nur an meinen Funktionen Init oder NextItem liegen).
Naja so stelle ich an der Stelle D die UTF-8 Codierung wieder her. Der String wird dann in einem dynamischen Array gespeichert.

Tja und dann:

Code: Alles auswählen

 
function Test(germanLong:string;inEnglish:boolean;isPrefix:boolean):string;
var
 s1:string;
 s2:string;
begin
 Result:='';
  s1:=UTF8LowerCase(dictionary[0].GermanLong);
  s2:='wärme';
  if s1=s2
  then ShowMessage('gleich')
  else ShowMessage('ungleich');
end;   


Ergebnis: ungleich

LG Helmut

Edit: Dieses Beispiel hatte ich mit Codetyphon 5.5 kompiliert. Für Codetyphon 5.7 (gerade neu heruntergeladen) zeigt sich ein ganz anderes Verhalten: An der Stelle B ist sb nun kein UTF-8 mehr. An der Stelle D kommt auch kein UTF-8 heraus. Der Vergleich von "w?rme" und "wärme" ergibt dann auch "ungleich". Allerdings auf andere Art zustandegekommen. Vielleicht war ja auch Irdendetwas (Compiler-Switch oder ähnliches) in meiner CT5.5-Installation verstellt.
Dateianhänge
20160309.zip
Beispielprogramm
(1.76 MiB) 55-mal heruntergeladen
Zuletzt geändert von HHick123 am Fr 11. Mär 2016, 14:11, insgesamt 5-mal geändert.

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

P.S.:
@wp_xyz: Damit meinte ich die Folge von Bytes, die den String bilden, bzw. auch die Bytes danach...

@Mathias:
Bezogen auf mein oben hochgeladenes Beispielprogramm:

Code: Alles auswählen

 
function Test(germanLong:string;inEnglish:boolean;isPrefix:boolean):string;
var
 s1:string;
 s2:string;
begin
 Result:='';
  s1:=UTF8LowerCase(dictionary[0].GermanLong);
  s2:='wärme';
  ShowMessage(s1);
  ShowMessage(s2);
  ShowMessage(inttostr(UTF8Length(s1)));
  ShowMessage(inttostr(UTF8Length(s2)));
  ShowMessage(inttostr(Length(s1)));
  ShowMessage(inttostr(Length(s2)));
  if s1=s2
  then ShowMessage('gleich')
  else ShowMessage('ungleich');
end;


liefert:

wärme
wärme
5
5
6
6
ungleich

Edit:
Obiges war mit CT5.5 kompiliert. Mit Codetyphon 5.7 ergibt sich:
w<?>rme ... ein ganz seltames "?", beim C64 hätte man das ein "Reverse-Zeichen" genannt.
wärme
5
5
7
6
ungleich
Zuletzt geändert von HHick123 am Fr 11. Mär 2016, 20:43, insgesamt 6-mal geändert.

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von wp_xyz »

Seit fpc 3.0 ist AnsiToUTF8 ohne Funktion, verwende stattdessen WinCPToUTF8 (oder CP1252ToUtf8 aus der Unit lconvencoding). Dann geht's.

@Michl: du bist ja der Spezialist für die neuen Strings hier - vielleicht gibt es noch eine Methode wie man sich durch eine geschickte Deklaration der Strings den expliziten Aufruf der Umwandlung sparen kann.

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von Michl »

Auch so geht es:

Code: Alles auswählen

procedure Initialize;
var
 rs: RawByteString;
 sb, sc:string;
 l: TListEnumerator;
begin
 setlength(dictionary,0);
 assignfile(f,'C:\04 Test Gleich\KKS.csv');
 reset(f);
 while not(eof(f)) do
 begin
  readln(f, rs);       // im RawByteString ist ein ohne Codepage hinterlegter String
  SetCodePage(rs, 1252, False)//Da die Datei mit CP 1252 kodiert ist, diese definieren (ohne Konvertierung)
  sb := rs;            // in sb steckt jetzt der String UTF8 kodiert, durch die Stringmagic vom FPC 3.0.0
 
  l.Init(sb,';');
  sc:=l.NextItem;
 
  setlength(dictionary,length(dictionary)+1);
  dictionary[length(dictionary)-1].GermanLong:=sc;
 end;
 closefile(f);
end;
 

Wobei mich schon interessiert hätte, wie der erste Vergleich zustande kam (evtl. war hinter den Bytes einfach eine andere Codepage hinterlegt)

PS: Bitte beim nächsten Minimalbeispiel die Exe und das Verzeichnis Lib möglichst nicht beifügen, das spart Zeit und Platz.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

Ah, ok...
Ich denke, jetzt kommen wir der Sache näher...

Ich fürchte, jetzt werd' ich gleich gesteinigt: Ich glaube, es liegt daran, dass ich FPC 3.1.1 verwendet hab...

Mit Lazarus/FPC 3.0.0 (soeben installiert) funktioniert z.B. der modifizierte Code aus dem vorangegangenen Posting von Michl!

Ev. wär's interessant herauszufinden, was bei 3.1.1 'broken' ist.

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von Michl »

Gesteinigt wirst du nicht, musst nur zehn Liegestütze zur Strafe machen :mrgreen:

Nein im Ernst, bei mir funktioniert obiger Code auch mit FPC 3.1.1. Keine Ahnung, was da bei dir anders ist. Ein minimiertes Beispiel mit dem Fehler wäre interessant. Bzw. welche Ausgabe hätte

Code: Alles auswählen

  WriteLn(StringCodePage(s1));
  WriteLn(StringCodePage(s2));
gegeben?

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

Hallo,
es hat mir nun keine Ruhe gelassen und ich hab mit dem neuen Code (anbei) etwas vergleichende Verhaltensforschung betrieben:

Code: Alles auswählen

 
procedure Initialize;
var
 rs:rawbytestring;
 sb,sc:string;
 l:TListEnumerator;
begin
 setlength(dictionary,0);
 assignfile(f,'KKS.csv');
 reset(f);
 while not(eof(f)) do
 begin
  readln(f,rs);
  SetCodePage(rs,1252,false);
  sb:=rs;
  l.Init(sb,';');                                   //Breakpoint 1
  sc:=l.NextItem;
  setlength(dictionary,length(dictionary)+1);       //Breakpoint 2
  dictionary[length(dictionary)-1].GermanLong:=sc;
 
 end;
 closefile(f);
end;
 
function Test(germanLong:string;inEnglish:boolean;isPrefix:boolean):string;
var
 s1:string;
 s2:string;
begin
 Result:='';
  s1:=UTF8LowerCase(dictionary[0].GermanLong);
  s2:='wärme';                                   //Breakpoint 3
  ShowMessage(s1);                               //Breakpoint 4
  ShowMessage(s2);
  ShowMessage(inttostr(UTF8Length(s1)));
  ShowMessage(inttostr(UTF8Length(s2)));
  ShowMessage(inttostr(Length(s1)));
  ShowMessage(inttostr(Length(s2)));
  if s1=s2
  then ShowMessage('gleich')
  else ShowMessage('ungleich');
end;


Setup A: Lazarus 1.6/3.0.0:
Breakpoint1: sb ist Ansi (ä mit e4 codiert) W?rme;W;;p;HEAT;HT;;p'
Breakpoint2: sc ist UTF8 (ä mit c3 a4 codiert) Wärme
Breakpoint3: s1 ist UTF8 (ä mit c3 a4 codiert) wärme
Breakpoint4: s2 ist UTF8 (ä mit c3 a4 codiert) wärme

Ausgabe:
wärme
wärme
5
5
6
6
gleich


Setup B: Codetyphon 5.5/3.1.1:
Breakpoint1: sb ist Ansi (ä mit e4 codiert) W?rme;W;;p;HEAT;HT;;p'
Breakpoint2: sc ist Ansi (ä mit e4 codiert) W?rme
Breakpoint3: s1 ist Ansi (ä mit e4 codiert) w?rme
Breakpoint4: s2 ist UTF8 (ä mit c3 a4 codiert) wärme

Ausgabe:
w?rme
wärme
5
5
5
6
ungleich

Der Unterschied besteht anscheinend darin, dass Codetyphon 5.5/3.1.1 bei Ansi bleibt, dann allerdings den String s2 als UTF-8 codiert, wodurch "ungleich" herauskommt.
Tja und das hat nun anscheinend ursptünglich dazu geführt, dass ich versucht hab', den String explizit UTF-8 zu codieren (wobei ich AnsiToUTF8 verwendet hab'- ob das die Methode der Wahl sein sollte, ist eben auch die Frage), was dann zu dem seltsamen Fehler aus meinem Ursprungsposting führt, wo zwei UTF-8 strings, die anscheinend gleich sind, doch zu "ungleich" führen...

LG Helmut

Edit:
Setup C: Codetyphon 5.7/3.1.1 ergibt nun das gleiche Verhalten wie Setup A: Lazarus 1.6:

Breakpoint1: sb ist Ansi (ä mit e4 codiert) W?rme;W;;p;HEAT;HT;;p'
Breakpoint2: sc ist UTF8 (ä mit c3 a4 codiert) Wärme
Breakpoint3: s1 ist UTF8(ä mit c3 a4 codiert) wärme
Breakpoint4: s2 ist UTF8 (ä mit c3 a4 codiert) wärme

Ausgabe:
wärme
wärme
5
5
6
6
gleich
Dateianhänge
20160310.zip
(204.99 KiB) 42-mal heruntergeladen
Zuletzt geändert von HHick123 am Fr 11. Mär 2016, 14:11, insgesamt 3-mal geändert.

HHick123
Beiträge: 21
Registriert: Mo 10. Nov 2014, 00:28

Re: UTF-8 Gleichheitsoperator Bug?

Beitrag von HHick123 »

@Michl: Hab' gerade gesehen, Du hast kurz vor mir gepostet. Bei welchem s1, s2 soll ich da StringCodePage auswerten? Meinst Du mein ursprüngliches Posting?

Antworten