Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Gleitkommadivision...? (https://www.delphipraxis.net/29862-gleitkommadivision.html)

Meflin 15. Sep 2004 17:36


Gleitkommadivision...?
 
Hi,
ich habe zahlen in der art:
Code:
16502,62
wenn ich die zahl durch 10 teile, sollte die meines erachtens so aussehen:
Code:
1650,262
wenn ich die allerdings mit dem gleitkomma / teile, kommt das raus:
Code:
0,002...
:shock:
div kann man da ja nicht verwenden!
was ist denn da los?

*MFG*

Chewie 15. Sep 2004 17:41

Re: Gleitkommadivision...?
 
Zeig mal deinen Code, das kann ich mir nämlich nicht so recht vorstellen...

c113plpbr 15. Sep 2004 18:11

Re: Gleitkommadivision...?
 
Vielleicht isses ja noch so nen dummer delphi-bug, wie dieser:
Wenn man
Code:
var
  r : real;

  r := 1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1;
  ShowMessage(FloatToStr(r));
das hier sich mal anschaut ... müsste doch logischerweise 0 dabei rauskommen, oder? delphi lässt aber -6,7762635780344E-20 rauskommen ... :roll: ... komischerweise ... :gruebel:

ciao, Philipp

Meflin 15. Sep 2004 18:19

Re: Gleitkommadivision...?
 
ok hier ein bisschen code:
Delphi-Quellcode:
function GetPrinterRes(const pobj: TPrinter; Horz: Boolean): integer;
var
   Index: Integer;
begin
   if Horz then
      Index:=LOGPIXELSX
   else
      Index:=LOGPIXELSY;
   Result:=GetDeviceCaps(pobj.Handle, Index);
end;

function ConvertPixelstoMMX(const Value:Integer):Double;
begin
   Result := Value / GetPrinterRes(Printer, True) * mmPerInch;
end;

...

hlplabel.Caption := hlplst.Strings[x];
length := length + roundto(ConvertPixelsToMMX(hlplabel.Width), -2);
length := length / 10;
pllength.Caption := floattostr(length) + ' m';

ibp 15. Sep 2004 18:26

Re: Gleitkommadivision...?
 
hast du mal durch 10.0 geteilt?

Chewie 15. Sep 2004 18:27

Re: Gleitkommadivision...?
 
Zitat:

Zitat von c113plpbr
Vielleicht isses ja noch so nen dummer delphi-bug, wie dieser:
Wenn man
Code:
var
  r : real;

  r := 1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1;
  ShowMessage(FloatToStr(r));
das hier sich mal anschaut ... müsste doch logischerweise 0 dabei rauskommen, oder? delphi lässt aber -6,7762635780344E-20 rauskommen ... :roll: ... komischerweise ... :gruebel:

ciao, Philipp

Sowas ist kein "Delphi-Bug", sondern tritt auch in anderen Programmiersprachen auf, da es in der Natur von Fließkommadarstellungen liegt.

@Meflin: Geh mal im Einzelschrittmodus durch und überprüf den Wert von Length, da muss der Knackpunkt liegen, nicht an der Division durch 10!

Meflin 15. Sep 2004 18:38

Re: Gleitkommadivision...?
 
das habe ich schon getan, der wert "spinnt" erst nach der teilung!

Jelly 15. Sep 2004 19:37

Re: Gleitkommadivision...?
 
Das Problem liegt in der Natur der Fließkommazahlen... Nimm einfach mal als Beispiel den Datentyp Double. Der hat intern eine Speicherstruktur von 8 byte. Mit diesen 8 byte, entsprechend 64 bit, kannst du also maximal 2^64=1,8447*10^19 verschiedene Werte speichern. In der Mathematik lernst du aber natürlich, daß es unendlich viele Elemente aus R gibt... Somit steht also auch klar, daß du im Rechner NIE alle reellen Zahlen darstellen kannst, sondern immer nur mit einer gewissen Genauigkeit... Und diese Ungenauigkeit die du in deinem Rechenbeispiel kriegst, liegt hierdrin begründet.

Gruß

Meflin 15. Sep 2004 20:17

Re: Gleitkommadivision...?
 
hm versteh ich nicht wirklich :stupid: die zahl ist ja nicht besonders groß :gruebel: aber die ungenauigkeit dafür riesig. wie könnte cih dann das problem umgehen?

Chewie 15. Sep 2004 20:18

Re: Gleitkommadivision...?
 
Zitat:

Zitat von Jelly
Das Problem liegt in der Natur der Fließkommazahlen... Nimm einfach mal als Beispiel den Datentyp Double. Der hat intern eine Speicherstruktur von 8 byte. Mit diesen 8 byte, entsprechend 64 bit, kannst du also maximal 2^64=1,8447*10^19 verschiedene Werte speichern. In der Mathematik lernst du aber natürlich, daß es unendlich viele Elemente aus R gibt... Somit steht also auch klar, daß du im Rechner NIE alle reellen Zahlen darstellen kannst, sondern immer nur mit einer gewissen Genauigkeit... Und diese Ungenauigkeit die du in deinem Rechenbeispiel kriegst, liegt hierdrin begründet.

Gruß

Schon, aber aus 1650 wird nicht 0,02....
Oder waren die Werte aus dem ersten Post falsch? Grundsäzlich gilt auf jeden Fall (da es Fließkomma- und nicht Festkommazahlen sind: je kleiner der Betrag der Zahl, desto kleiner die Abstände zwischen den darstellbaren Zahlen.

Meflin 16. Sep 2004 14:01

Re: Gleitkommadivision...?
 
das gibts doch nicht :? die teilung durch 10.0 löst das ganze btw auch nicht!

Chewie 16. Sep 2004 15:03

Re: Gleitkommadivision...?
 
Die 10 wird vom Compiler ohnehin in 10.0 ungewandelt.

Meflin 16. Sep 2004 15:39

Re: Gleitkommadivision...?
 
das kanns doch garnicht geben... es ist fast der ganze source des programms, der restliche hat mit den dort verwendeten variablen nichts zutun. weis keiner mehr weiter? :cry:

shmia 16. Sep 2004 16:29

Re: Gleitkommadivision...?
 
Zitat:

Zitat von Meflin
das kanns doch garnicht geben... es ist fast der ganze source des programms, der restliche hat mit den dort verwendeten variablen nichts zutun. weis keiner mehr weiter? :cry:

Du darfst natürlich nicht die Funktion GetPrinterRes benützen, ohne dass du vorher den Drucker mit
BeginDoc geöffnet hast.
Die Umrechnung von Pixels -> mm benötigt immer eine Auflösung. Willst du mit der Druckerauflösung oder
mit der Bildschirmauflösung (72 oder 96 DPI) arbeiten ?

Wenn ich einen Wert in mm (Millimeter) durch 10.0 teile, dann bin ich bei cm (Zentimeter).
Bei dir steht aber m (Meter); warum ???

Der Datentyp der Variablen length muss Double oder Extended sein!
Du solltest die Variable umbenennen, damit es nicht zu einem Namenskonflikt mit der RTL-Funktion
Length() kommt!

jfheins 16. Sep 2004 17:08

Re: Gleitkommadivision...?
 
Code:
ThisPost.location := nil;
:wall:

Meflin 16. Sep 2004 20:26

Re: Gleitkommadivision...?
 
Zitat:

Zitat von shmia
Du darfst natürlich nicht die Funktion GetPrinterRes benützen, ohne dass du vorher den Drucker mit
BeginDoc geöffnet hast.

Oh, das musst du mir mal näher erklären.
Zitat:

Zitat von shmia
Die Umrechnung von Pixels -> mm benötigt immer eine Auflösung. Willst du mit der Druckerauflösung oder
mit der Bildschirmauflösung (72 oder 96 DPI) arbeiten ?

egal, aber lieber mit der druckerauflösung
Zitat:

Zitat von shmia
Wenn ich einen Wert in mm (Millimeter) durch 10.0 teile, dann bin ich bei cm (Zentimeter).
Bei dir steht aber m (Meter); warum ???

ganz einfach: normalerweise steht in der app auch / (10 * 100), dann stimmen die meter. wurde nur aus testzwecken entfernt!
Zitat:

Zitat von shmia
Der Datentyp der Variablen length muss Double oder Extended sein!
Du solltest die Variable umbenennen, damit es nicht zu einem Namenskonflikt mit der RTL-Funktion
Length() kommt!

mal ausprobieren...

Meflin 18. Sep 2004 11:48

Re: Gleitkommadivision...?
 
length ist umbenannt, hat nix gebracht

Meflin 19. Sep 2004 09:38

Re: Gleitkommadivision...?
 
^^ push ^^

Meflin 23. Sep 2004 16:14

Re: Gleitkommadivision...?
 
:?

Meflin 24. Sep 2004 16:30

Re: Gleitkommadivision...?
 
:( nochmal push...

dizzy 24. Sep 2004 18:40

Re: Gleitkommadivision...?
 
Test mit Delphi 7 Ent. mit folgendem Code:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: Extended;
begin
  a := StrToFloat(Edit1.Text);
  b := StrToFloat(Edit2.Text);
  c := a/b;
  Edit3.Text := FloatToStr(c);
end;
Ich habe für a, b und c alle Gleitkommatypen durch (incl. Currency), und ich hatte nur bei Single eine kleine Abweichung weit im Nachkommaanteil.

Daher ist das Verhalten für mich nicht reproduzierbar - sorry :spin:.


gruss,
Fabian

ibp 29. Sep 2004 13:16

Re: Gleitkommadivision...?
 
Liste der Anhänge anzeigen (Anzahl: 1)
um das thema nochmals aufzugreifen, habe ein gleihgeartetes problem
siehe anhang!

db: ib hat damit aber glaube ich nix zu tun eher mit strtofloat konvertierung


z.b. wird bei float feldern manchmal aus 0,2 = 0,20000000002980232
oder aus 0,1=0,100000000014910116

woher kann dieses 14910116 kommen vom code her jedenfalls nicht, da bin ich mir ganz sicher und wenn man sich das genauer ansieht, dann ist 2*14910116=2980232 usw.

hat jemand auch schon mal diese phänomen gehabt?

RBredereck 29. Sep 2004 13:37

Re: Gleitkommadivision...?
 
Ich hatte mal ein ähnliches Problem und konnte es lösen indem ich einen anderen Float-Datentyp gewählt habe. (Damals Currency, dann war 0.2 auch 0.2....)

Ich vermute, dass das daran lag, dass ich mit verschiedenen Datentypen gerechnet habe. (z.B. Extended= Double / Real)

Es sollten wirklich alle Variablen möglichst gleichen Typs sein und wenn nicht, dann am besten in Einzelschritten umwandeln. Ich weiß nicht ob euch das was bringt, aber bei mir ging das dann...

PS: achso.. am besten niemals Real verwenden... das gibts soweit ich weiß nur aus Kompatiblitätsgründen...

Edit:
@idp

Wie weist du deinen Datensätzen die Werte zu? ich hatte schonmal genau das selbe Problem... War allerdings eine Access-Datenbank. Poste am besten mal nen Source, oder werden die Tabellen über eine DataGrid-Komponente gefüllt?

ibp 29. Sep 2004 13:56

Re: Gleitkommadivision...?
 
sie werden nicht über ein grid erstellt, sondern per query

Delphi-Quellcode:
function TInkaRecord.UpdRec(aPKey:TInkaKey):boolean;
var
   i:integer;
   s:string;
   inkaField:TInkaRecField;
   allowtyp:TinkaFieldSet;
begin
   allowTyp:=[inkaFtPKey,
                  inkaFtRef,
                 inkaFtText,
              inkaFtInt,
              inkaFtFloat,
           inkaFtMemo,
           inkaFtBool,
                 inkaFtDate,
                 inkaFtStart,
                 inkaFtViewer];
   // insert Record
   query.SQL.Clear;
   query.sql.add('update '+tableRef.tname);

   // set fieldname
   s:='set '+tableRef.prefix+'_parkey=:parkey';
   for i:=0 to fieldCount-1 do begin
      if not (TInkaRecField(fieldList.Objects[i]).typ in allowTyp) then continue;
      s:=s+','+fieldList.Strings[i]+'=:'+fieldList.Strings[i];
   end;
   query.sql.add(s);
   query.sql.add('where '+tableRef.prefix+'_pkey=:pkey');
   query.ParamByName('parkey').asString:=recParKey;
   query.ParamByName('pkey').asString:=aPKey;

   // parametrisieren
   for i:=0 to fieldCount-1 do
      begin
     if not (TInkaRecField(fieldList.Objects[i]).typ in allowTyp) then continue;
     inkaField:=TInkaRecField(fieldList.Objects[i]);
     inkaField.setToQuery(query,fieldList.Strings[i]);
   end;

   result:=true;
      try
        query.ExecSQL;
      except
        on e:Exception do
        begin
          showmessage('Fehler beim aktualisieren des Datensatzes!'+#13
                      +query.SQL.Text+#13
                      +e.Message);
          result:=false;
        end; // on
      end; // try..except
end;

RBredereck 29. Sep 2004 14:30

Re: Gleitkommadivision...?
 
Sehr viel weiter hilft der QT auch nicht.. Aber zumindest verwendest du Parameter (komischerweise tun das viele nicht).

Von welchem Typ ist "query"? TIbQuery, TAdoQuery?

Sind
Delphi-Quellcode:
   inkaField:TInkaRecField;
   allowtyp:TinkaFieldSet;
selbst definiert oder wo kommen die her?

Die Problem liegt bestimmt beim parametrisieren aber das scheint "inkaField" zu machen.

Sind die Abweichungen wenn du den Datentyp des Parameters zu Testzwecken zwischen ftFloat, ftCurrency und ftBCD variierst gleich? Bzw welche Datentypen stehen in dem inkaField und.. Sind die Werte dort auch schon ungenau?

PS: Warum benötigt Oracle-Installation eigentlich so extrem viel Zeit?

dizzy 29. Sep 2004 15:06

Re: Gleitkommadivision...?
 
Diese minimalen Abweichungen sind vollkommen normal, und haben nichts mit einem Bug zu tun. Das Thema hatten wir hier auch schon des öfteren :roll: ;).
Ursache: Die Art und Weise nach der ein Float-Typ gebildet wird - und zwar nicht nur in Delphi, sondern überall.

Um das evtl. mal abschließend zu klären hier mal der Aufbau einer 32-Bit Gleitkommazahl (also einfache Genauigkeit = "Single")
Code:
Binärer Aufschrieb: 32 Bits

00000000000000000000000000000000
|\_____/\______________________/
|   |           mantisse
| exponent
|
Vorzeichenbit

Die dezimale Darstellung errechnet sich nach folgender Formel:

x = (-1)^Vorzeichenbit * 2^(exponent - T) * 1.mantisse

wobei T = 127 (bei Single; bei Double ist T = 1023)

angenommen wir wollen die Zahl 0.2 so darstellen, so ergäbe sich:

Vorzeichenbit = 0
exponent = 124  { 2^(124 - 127) = 2^(-3) = 0.125 }
mantisse = 0.6 { und das wird unser Problemkind }

Warum ist die Matisse das Problem? Weil sie [b]binär[/b] dargestellt werden muss! Und das ist:
0.6(d) = 0.1001100110011001100110011001100110011001100110011001100110011.....(b)
=> Also [b]periodisch[/b]! Aber die Matisse bietet uns keinen Platz für eine Periode, also wird irgendwo abgeschnitten: nämlich nach 24 Bits.
Also gilt binär:
Vorzeichenbit = 0
exponent = 1111100
mantisse = 100110011001100110011001

Gesamte Zahl binär: 01111100100110011001100110011001
(In Hex: 7C999999)

Jetzt wollen wir mal zurück rechnen:
Vorzeichenbit ist = 0, also positives Vorzeichen.
Den Exponenten haben wir oben schon mal, 2^(-3) = 0.125
Jetzt die Mantisse. Nach dem Komma rechnent sich die binäre Zahl folgendermaßen um:
erstes Bit*(2^(-1)) plus
zweites Bit*(2^(-2)) plus
drittes Bit*(2^(-3)) usw.
(von [b]links[/b] angefangen, nicht wie bei ganzen Zahlen von rechts!)
Für unsere Mantisse ergibt sich daher:
[size=9](Jetzt wirds unschön *g*)[/size]
2^(-1)+2^(-4)+2^(-5)+2^(-8)+2^(-9)+2^(-12)+2^(-13)+2^(-16)+2^(-17)+2^(-20)+2^(-21)+2^(-24)

Die Summanden ausgerechnet:
0.5 +
0.0625 +
0.03125 +
0.00390625 +
0.001953125
und ab hier verlässt mich der Taschenrechner mit seiner Anzeige, daher sei nur gesagt, dass noch einige weiter winzige Werte aufaddiert werden, bis zu einem Endergebnis von: ca. 0.5999999[b]64[/b]
Und das fett gedruckte ist nun der Fehler der beim Rechnen mit Floatzahlen nunmal prinzipbedingt auftreten kann. Ob, und wie stark dieser ist hängt davon ab, wie gut sich die nötige Mantisse binär umgerechnet in die dafür vorgesehenen Bits passen.

Um unsere 0.2 zu ende zu rechnen:
Wir haben jetzt alle nötigen Werte, und die Formel lautet: (allerdings nur ungefähr, da mein TR ja nur so wenige Stellen hat ;))
2^(-3) * 1.599999964
= 0.125 * 1.599999964
= 0.19999995 (cirka)
Und schwupps ist unsere 0.2 etwas kleiner geworden, und zwar ohne dass wir etwas dagegen tun könnten!

Dass die 0.2 im obigen Beitrag größer, und nicht wie hier kleiner geworden ist, liegt wahrscheinlich daran, dass nicht Single als Datentyp benutzt wurde. Je nach Typ verändert sich die Darstellbarkeit der Werte drastisch!
Ich hoffe es war die Mühe wert, und es wurde klar warum Float-Typen immer nur begrenzt akkurat sein können.
Allerdings erklärt dieser Umstand das ursprüngliche Problem hier nicht! Dafür ist der Fehler zu groß, und muss andere Ursachen haben.

Gruss,
Fabian

\\edit: Korrektur: Der Exponent hat bei mir hier 7 binäre Stellen - er hat aber 8! Dafür hat die Mantisse nicht 24, sondern 23 Stellen... denkt's euch bidde zurecht :)

ibp 29. Sep 2004 16:14

Re: Gleitkommadivision...?
 
@RBredereck

Zitat:

Zitat von RBredereck
Sehr viel weiter hilft der QT auch nicht.. Aber zumindest verwendest du Parameter (komischerweise tun das viele nicht).

:thumb:

Zitat:

Von welchem Typ ist "query"? TIbQuery, TAdoQuery?
TQuery

Zitat:

Sind
Delphi-Quellcode:
   inkaField:TInkaRecField;
   allowtyp:TinkaFieldSet;
selbst definiert oder wo kommen die her?
ja

Zitat:

Die Problem liegt bestimmt beim parametrisieren aber das scheint "inkaField" zu machen.

Sind die Abweichungen wenn du den Datentyp des Parameters zu Testzwecken zwischen ftFloat, ftCurrency und ftBCD variierst gleich? Bzw welche Datentypen stehen in dem inkaField und.. Sind die Werte dort auch schon ungenau?
nein nicht beim parametrisiren und inkafield ist eine klasse und hat nur tabelleninformationen, die werte sind nur in der db ungenau oder bei reports, da stören sie auch extrem!

@dizzy
Zitat:

Diese minimalen Abweichungen sind vollkommen normal, und haben nichts mit einem Bug zu tun. Das Thema hatten wir hier auch schon des öfteren .
Ursache: Die Art und Weise nach der ein Float-Typ gebildet wird - und zwar nicht nur in Delphi, sondern überall.
so etwas habe ich mir auch schon gedacht, aber minimal finde ich den fehler nicht gerade, es kommt doch immer darauf an für was die werte stehen!
wie kann man das dann umgehen? mit welchen typ oder gibt es einen korrekturfaktor?

dizzy 29. Sep 2004 17:56

Re: Gleitkommadivision...?
 
Zitat:

Zitat von ibp
wie kann man das dann umgehen? mit welchen typ oder gibt es einen korrekturfaktor?

Einen Korrekturfaktor kann es imho nicht geben, da nach der Umwandlung ja nicht mehr eindeutig ist welche Zahl ursprünglich hineingesteckt wurde. So würde (vermute ich mal) 0.2 und 0.20000001 zur selben Zahl bei Single-Typen führen, und du bekommst ja nie wieder heraus, welche Zahl nun wirklich genommen wurde. Das ganze ist ein konzeptionelles Problem, und lässt sich (faktisch) garnicht lösen. Der Fehler lässt sich lediglich minimieren: Durch mehr Bits :?
Also ist die Top-Genauigkeit (mit generischen Typen) Double bzw. Extended. Wenn du es 100%ig brauchst, dann böte sich evtl. eine Library an die die Daten anders hinterlegt (z.B. als String). (Kenne spontan keine für Floats.)

Eine andere Sache ist: Wie genau brauchst du's denn? Reichen evtl. nicht so 10 Stellen nach dem Komma? Dann kann man mit Runden noch was reissen :D.


btw: Mit der o.g. genormten Darstellung von Floats ist übrigends die Null unmöglich. Da wird dann aber in der FPU ein Flag geführt.

RBredereck 29. Sep 2004 18:30

Re: Gleitkommadivision...?
 
Zitat:

nein nicht beim parametrisiren und inkafield ist eine klasse und hat nur tabelleninformationen, die werte sind nur in der db ungenau oder bei reports, da stören sie auch extrem!
Nagut... bei mir war das so:

Das Problem ergab sich als ich folgende Einstellungen hatte:

Datentyp in der Datenbank: Float
Datentyp den ich der Datenbank zugewiesen habe: Currency

Ich stellte das dann wie folgt um:

Datentyp in der Datenbank: Money (Access)
Datentyp in den ich geweise: Currency

Ich kann es mir nur so erklären, dass die Datenbank einen Float mit großer Genauigkeit erwartet hat, meine Werte jedoch diese nicht besaßen und es deshalb zu den Problemen kam. Jetzt haben beide die selbe Genauigkeit und es funktioniert.

Es kann natürlich auch eine ganz andere Ursache gewesen sein, die ich dadurch aber umgehen konnte.

Evtl kannst Du auch über SQL die Werte nachträglich Runden (also gerundet(Wert*10^n)*10^-n - weiß jetzt aber nicht den genauen Befehl... geht aber auf jeden Fall... Du musst dann nur Wissen ob das vertretbar ist (meinetwegen ab der 6. Stelle zu Runden) und ob du mit einer solchen unsicheren Lösung leben kannst.

Solltest du das Problem lösen (egal wie) poste es bitte.. also viel Glück noch...


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:26 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz