Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   runden von 0.0099999997765 (https://www.delphipraxis.net/70987-runden-von-0-0099999997765-a.html)

stoxx 7. Jun 2006 16:34


runden von 0.0099999997765
 
ich hatte immer das Problem, dass ich eine Zahl runden wollte, aber ihre anzahl der Nachkommastellen nicht kannte.
somit sahen die Floattostr() Ausgaben immer etwas nebulös aus, oder aber die Zahl war zu stark gerundet.
Ich hab hier mal eine Funktion gebastelt.
Könntet ihr diese mal auf Herz und Nieren prüfen bitte ? Ich bin mir nicht sicher, ob ich ein Detail übersehen haben könnte.
Vielleicht fällt ja jemanden noch etwas ein um die Geschwindigkeit zu optimieren.
Wäre vielleicht etwas für die Codelib.

Man erhält für:

115,859997410327 - 115,86
100,99 - 100,99
100 - 100
101 - 101
109 - 109
100,4 - 100,4
100 - 100
126,61999511719 - 126,62
0,0099999997765 - 0,01
115,089999999776 - 115,09
99,99999997765 - 100
99,99999997765 - 100
0,99999997765 - 1
99,09999997765 - 99,1
99,0199999997765 - 99,02
99,0100001245678 - 99,01
115,860000610352 - 115,86
115,859997410327 - 115,86
115,859974103278 - 115,86
115,85074103278 - 115,85074103278
0,00999999977648258 - 0,01
115,790000915527 - 115,79

Ausgabe mit:

Delphi-Quellcode:
  d := 126.61999511719;
  testmemo.Lines.Add(floattostr(d) + ' - ' + floattostr(xRound(d)));


Delphi-Quellcode:
/==============================================================================
// function xRound
// rundet und findet selbständig die Anzahl der Nachkommastellen
//==============================================================================
Function xRound (aValue : Extended) : extended;
Const
  cMaxDigits = 12;
  cCount = 2; // anzahl des auftretens von Ziffer 9 oder 0  (z.B. 99 oder 00)
var Count9 : Integer; // der count von 99 oder 00 hintereinander
    Count0 : Integer;
    Check9 : Boolean; // 99 war da
    check0 : boolean; // 00 war da
    Zahl : Int64;
    Ziffer : Integer;
    NachkommaStellen : integer;
    Teiler : Extended;

Begin

  Count9 := 0;
  count0 := 0;
  check0 := false;
  check9 := false;
  Nachkommastellen := cMaxDigits;
  Zahl := round(Power(10,cMaxDigits)  * Frac (aValue)); /// 0.0099999997765 => 99999997765

  While (Zahl>0) do begin
      Ziffer := Zahl mod 10;
      if Ziffer = 0 then inc(Count0)
          else if Ziffer = 9 then inc(Count9) else begin
              count0 := 0;
              count9 := 0;
          end; // else

      if not check9 and (count0 >= cCount) then check0 := true;   // nicht auf check0 stellen, wenn schon check9
      if not check0 and (count9 >= cCount) then check9 := true;

      if check9 then if (Ziffer <> 9) then break;
      if check0 then if (Ziffer <> 0) then break;

      Zahl := Zahl div 10; // von hinten abschneiden
      dec(Nachkommastellen);
  end; // While Zahl > =

  if not (check0 or check9) then nachkommastellen := cMaxDigits;
  Teiler := power(10, Nachkommastellen);
  result := round(aValue * Teiler) / Teiler;

End; // xRound

//==============================================================================

Martin K 7. Jun 2006 16:37

Re: runden von 0.0099999997765
 
Zitat:

Zitat von stoxx
ich hatte immer das Problem, dass ich eine Zahl runden wollte, aber ihre anzahl der Nachkommastellen nicht kannte.
somit sahen die Floattostr() Ausgaben immer etwas nebulös aus, oder aber die Zahl war zu stark gerundet.

Hast du dir schonmal RoundTo in der Delphi-Hilfe angeschaut?

stoxx 7. Jun 2006 16:46

Re: runden von 0.0099999997765
 
Zitat:

Hast du dir schonmal RoundTo in der Delphi-Hilfe angeschaut?
hmm .. hast Du Dir mein Posting überhaupt durchgelesen ?

Martin K 7. Jun 2006 16:51

Re: runden von 0.0099999997765
 
Naja, ich verstehe nicht so ganz, warum du etwas selber basteln willst, was es bereits schon gibt.
Wenn ich mir deine Beispiele so anschaue (bis auf 115,85074103278 - 115,85074103278 da sehe ich irgendwie die Logik nicht drin),
ist das identich mit RoundTo(aValue, -2).

stoxx 7. Jun 2006 16:59

Re: runden von 0.0099999997765
 
Zitat:

Zitat von Martin K
Naja, ich verstehe nicht so ganz, warum du etwas selber basteln willst, was es bereits schon gibt.
Wenn ich mir deine Beispiele so anschaue (bis auf 115,85074103278 - 115,85074103278 da sehe ich irgendwie die Logik nicht drin),
ist das identich mit RoundTo(aValue, -2).

115,85074103278 ist ja gerade das Ausnahmebeispiel (Keine doppelte Neun oder Null vorhanden. Da hatte mein Code erst auf 116 gerundet, ist natürlich falsch.
RoundTo kann ich nicht nehmen, da ich ja die 2 gar nicht kenne, sondern nur die Zahl ansich habe.
Ist auch nicht vorwiegend für Formatierungen gedacht, sondern zum rechnen mit Floatzahlen.
Ausserdem kannst Du mit RoundTo nicht auf 0,5 runden (brauch ich aber)
5000
5000,5
5001
5001,5

Martin K 7. Jun 2006 17:03

Re: runden von 0.0099999997765
 
Achso, jetzt verstehe ich auch das Problem erst :oops:
Du willst also nicht, dass 00 oder 99 hinterm Komma vorkommt bzw. wenn es vorkommt soll an dieser Stelle gerundet werden...

alzaimar 7. Jun 2006 18:28

Re: runden von 0.0099999997765
 
Erstmal eins vorneweg: Gute Funktion, stoxx!

Aber:
Ich weiss nicht, wozu ich das gebrauchen könnte. Im wirklichen Leben verwendet man eigentlich ausschließlich feste Nachkommastellen bzw. feste Stellen.

Wenn ich z.B. Messreihen darstelle, werde ich wohl kaum 123.4353513131345 V anzeigen. Weil das einfach falsch ist, selbst wenn der Voltometer diesen Wert zurück liefert. Tatsache ist aber, das das Messgerät z.B. nur auf +/- 0.01% genau messen kann. Das ergibt 5 Stellen. Eine Spannung von 100-999 Volt kann ich also mit 2 Nachkommastellen angeben... Kurzum: Im physikalischen Bereich wird i.A. mit einer festen Stellenanzahl gearbeitet.

In der Mathematik arbeitet man mit der Exponentialschreibweise, also z.B. 1,234 E+05, mit anderen Worten, auch eine feste Stellenanzahl, nur die Darstellung unterscheidet sich.

In visuellen Bereich wird man dagegen eine Darstellung mit festen Dezimalstellen vorziehen, also z.B. 123,400

Aus gutem Grund überlässt man es dem Programmierer, eine *sinnvolle* Darstellung zu verwenden.

Ein wirklich realitätsnahes Praxisbeispiel ist die Anzeige bei irgendwelchen Web-Umfragen. Wenn z.B. 13 Leute teilgenommen haben, und 6 haben sich für blau entschieden, dann steht dort häufig blau: 46,153%. Diese Darstellung impliziert jedoch, das mindestens 100000 Personen teilgenommen haben, denn es sind ja 46,153 und nicht 46,154 %.

Eine Funktion, die mir eine Zahl so rundet, das es 'irgendwie ansprechend' aussieht, ist eine lustige (und gut gemachte!) Angelegenheit, birgt aber das echte Risiko, das der Dilettantismus noch weiter um sich greift.

Eine Funktion, die eine Zahl mit einer beliebigen Granularität rundet, wäre z.B.:
Delphi-Quellcode:
Function GranularityRound (aNumber, aGranularity : Extended) : Extended;
Begin
  Result := Trunc (aNumber / aGranularity + 0.5) * aGranularity;
End;
// granularityRound (1.2343434, 0.02) = 1.24
// granularityRound (1.2343434, 0.1 ) = 1.2
// granularityRound (123.43434, 5   ) = 125
Die Funktion sorgt nur dafür, das die Zahl ohne Rest durch die Granularität teilbar ist.

Ich will die Leistung von stoxx wirklich nicht schmälern, sondern nur meinen vergreisten Zeigefinger heben, und darauf hindeuten, wie wichtig eine *sinnvolle* Darstellung ist.

Genug gelabert, danke für die Aufmerksamkeit.

:cheers:

stoxx 7. Jun 2006 19:31

Re: runden von 0.0099999997765
 
Zitat:

Aber:
Ich weiss nicht, wozu ich das gebrauchen könnte. Im wirklichen Leben verwendet man eigentlich ausschließlich feste Nachkommastellen bzw. feste Stellen.

Hi !

na vielleicht bin ich auch der einzigste Mensch auf Erden, der diese Funktion benötigt ;-)
Kann durchaus sein.
Im Wertpapierbereich haben Aktien gewöhnlich 2 Nachkommastellen, Futures und andere Produkte haben je nachdem auch 4 oder 6 Nachkommastellen.
und es gibt minimale Preisbewegungen in 0,25er oder in 0,5er oder in ganzen Schritten oder wie auch immer.
Ich arbeite mit einer API an eine Brokersofware. Diese liefert auch die minimale Schrittweite, arbeitet aber dummerweise mit Single Werten. Da werden aber dummerweise Zahlen von 0.009999999765 übermittelt für 0,01.
Diese 0,01 brauch ich aber um (ausgerechnete) Preise korrekt zu runden.

Fall b) bei berechnungen mit Gleitkommazahlen treten selbst bei einfachen additionen sehr schnell unerwünschte Fehler auf, die sich äußerst schnell fortpflanzen. Nach einigen Berechnungsschritten runde ich die Werte. Gut wenn man weiß, mit wievielen Nachkommastellen man das tun soll :-)

alzaimar 8. Jun 2006 19:21

Re: runden von 0.0099999997765
 
Hi stoxx. Verwende statt Single lieber Currency oder Extended-Werte. Am besten wäre BCD. Das Problem ist, das sich i.a. Dezimalbrüche nicht genau durch das Floatingpoint-Format darstellen lassen. Daher gibt es Schutzstellen, d.h. Du nimmst billigend die Rundungsfehler in Kauf (bitte nicht mit Single). Wenn Du nur addierst oder substrahierst, dann werden diese Fehler im Bereich von maximal 10^-8 bleiben, also weit jenseits der von Dir gewünschtne Genaugigkeit von 10^-4. Insofern sollte es reichen, die Darstellung auf feste Nachkommastellen zu runden. Versuch doch mal meine Funktion, ich glaub sogar,es gibt sowas schon in der Math-Unit.

Wie gesagt, Deine Funktion hat mir gefallen. Vielleicht wird sie mal von 2 Personen verwendet: Nämlich auch von mir. :mrgreen:

Amateurprofi 24. Jun 2006 15:35

Re: runden von 0.0099999997765
 
Zitat:

Zitat von stoxx
Um nach einem Hin und Herwandeln wieder wirklich auf (Bit)-Gleichheit prüfen zu können, verwende ich meine xRound Funktion.
Dann ist 0,47 wieder wirklich gleich 0,47. Und man kann sich "abs(a-b) < Toleranz" sparen.
http://www.delphipraxis.net/internal...037&highlight=

Die sieht schon vom Quelltext sehr optimierungsfähig aus. Darf ich Dich da nochmal bemühen ?
Hast Du da auch noch eine wunderschöne Idee ?
Vielen Dank nochmal !!

Hallo Stoxx,
ich hab mir mal ein paar Gedanken dazu gemacht, und das Resultat ist weiter unten.
Um die vielen Divs und Mods zu vermeiden, hab ich das ganze in Assembler geschrieben. Die Suche nach doppelten Nullen / Neunen lasse ich in den 128 Bit breiten XMM-Registern erledigen - ohne Schleifen, ifs und thens....
Soweit ich weiß unterstützt AMD die verwendeten Befehle auch.
Bei den von Dir aufgeführten Beispielen bringt meine Funktion identische Resultate.
Die Funktion, ich habe sie (in Anlehnung an XRound) YRound genannt, hat die Parameter
v:extended - entspricht Deinem avalue
mindigs:integer - soviel Stellen nach dem Dezimalpunkt bleiben mindestens
maxdigs:integer - soviel Stellen nach dem Dezimalpunkt bleiben höchstens
fromleft:boolean - wenn True, dann wird die Suche nach doppelten Nullen / Neunen von links nach rechts durchgeführt, sonst, wie bei Dir, von rechts nach links.
Anders als Deine Version arbeitet sie auch mit negativen Zahlen.
mindigs und maxdigs müssen im Bereich 0..18 liegen, was nicht geprüft wird, ggfs. also in einer Exception endet.

Delphi-Quellcode:
FUNCTION YRound(v:extended; mindigs,maxdigs:integer; fromleft:boolean=false):extended;
const
   e18:int64=1000000000000000000;
   p10:array[0..18] of int64=
      (1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,
       10000000000,100000000000,1000000000000,10000000000000,100000000000000,
       1000000000000000,10000000000000000,100000000000000000,
       1000000000000000000);
   nine:array[0..1] of int64=
      ($9999999999999999,$9999999999999999);
   mask:array[0..19] of integer=
      ($000,$000,$100,$100,$180,$180,$1C0,$1C0,$1E0,$1E0,
       $1F0,$1F0,$1F8,$1F8,$1FC,$1FC,$1FE,$1FE,$1FF,$1FF);
asm
// --> Stack = V
// --> EAX  = mindigs
// --> EDX  = maxdigs
// --> ECX  = fromleft
// <-- Gerundetes V
//----------------------------------------------------------------------

            jmp     @Entry

// Subroutine zum Ermitteln der Nachkommastellen
@GetDigs:  // BCD-Zahl in XMM0 und XMM1
            movdqu  xmm0,[esp+4]     // +4 wegen Returnadresse
            movdqa  xmm1,xmm0
            // Bytes mit '00' vergleichen und Digits ermitteln
            pcmpeqb xmm0,xmm2
            pmovmskb ebx,xmm0          // Ergebnisse in EBX
            call    @GetDigs1
            // Bytes mit '99' vergleichen und Digits ermitteln
            pcmpeqb xmm1,xmm3
            pmovmskb ebx,xmm1          // Ergebnisse in EBX
@GetDigs1: and     ebx,edi          // nur gültige Bits zulassen
            or      ecx,ecx          // fromleft
            js      @GetDigs2         // ja
            bsf     ebx,ebx          // niedrigstes 1 Bit in EBX
            jnz     @GetDigs3
            ret
@GetDigs2: bsr     ebx,ebx          // höchstes 1 Bit in EBX
            jz      @GetDigs4         // alle 0
@GetDigs3: sub     ebx,8
            neg     ebx
            add     ebx,ebx          // =Nachkommastellen
            add     bx,cx            // +1 wenn aus verschobener BCD
            cmp     ebx,edx          // weniger als bisher ?
            jae     @GetDigs4         // nein
            mov     edx,ebx          // kleineren Wert merken
@GetDigs4: ret
//----------------------------------------------------------------------
            // Register retten
@Entry:    push    ebx
            push    edi
            push    esi
            // 16 Bytes auf Stack reservieren
            sub     esp,16
            // Nachkommateil als BCD-Zahl an [ESP] speichern
            fld     v                // st0=v
            fabs                      // st0=Abs(v);
            fld     st(0)            // st1=st0=v
            fld     st(0)            // st2=s1=st0=v
            fnstcw  word [esp]       // cw retten
            fnstcw  word [esp+2]
            fwait
            or      word [esp],$0F00  // Richtung 0 runden
            fldcw   word [esp]
            frndint                   // st2=st1=v, st0=Trunc(v)
            fwait
            fldcw   word [esp+2]     // altes cw laden
            fsubp   st(1),st(0)      // st1=v, st0=Frac(v)
            fild    e18
            fmulp                     // st1=v, st0=Frac(v)*1e18
            frndint                   // st1=v, st0=Round(Frac(v)*1e18)
            fbstp   TByte [esp]      // [ESP]=18stellige BCD-Zahl,st0=v
            // ---------------------------------------------------------------
            // An ESP steht jetzt Round(Frac(v)*1e18) als 18 stellige BCD-Zahl
            // In St(0) steht noch V für späteres Runden
            // ---------------------------------------------------------------
            // XMM2 mit Nullen und XMM2 mi Neunen füllen
            pxor    xmm2,xmm2
            movdqu  xmm3,nine
            // Digits ermitteln auf Basis der BCD-Zahl
            ror     ecx,1               // ECX Bit 0 in Bit 31
            lea     edi,mask
            mov     esi,[edi+edx*4]  // Bitmaske wenn verschobene Zahl geprüft wird
            mov     edi,[edi+edx*4+4] // Bitmaske wenn BCD-Zahl geprüft wird
            call    @GetDigs
            // BCD-Zahl um 1 Nibble nach oben verschieben
            mov     ebx,[esp+4]
            shld    [esp+8],ebx,4
            mov     ebx,[esp]
            shld    [esp+4],ebx,4
            shl     [esp],4
            // Digits ermitteln auf Basis der verschobenen BCD-Zahl
            mov     edi,esi          // Bitmaske für verschobene BCD-Zahl
            add     ecx,1
            call    @GetDigs
            // Prüfen ob mindigs unterschritten, ggfs. korriggieren
            cmp     edx,eax          // Nachkommastellen < mindigits ?
            jae     @1                // nein
            mov     edx,eax          // mindigits verwenden
@1:        // Und jetzt v runden auf EDX Nachkommastellen
            // Pascal : Teiler := power(10, Nachkommastellen);
            //           result := round(aValue * Teiler) / Teiler;
            // v schlummert noch in st0
            fild    QWord [p10+edx*8] // st1=v, st0=10^EDX
            fmulp   st(1),st(0)      // st0=v*10^EDX
            frndint                   // st0=Round(v*10^EDX)
            fild    QWord [p10+edx*8] // st1=Round(v*10^EDX), st0=10^EDX
            fdivp   st(1),st(0)      // st0=result=Round(v*10^EDX)/10^EDX
            bt      Word [ebp+16],15  // war v negativ
            jnc     @2                // nein
            fchs                      // st0=-st0
@2:        // Stack freigeben
            add     esp,16
            // Register auf alten Stand bringen
            pop     esi
            pop     edi
            pop     ebx
end;

Der Test der von Dir aufgeführten Beispiele bringt identische Ergebnisse, mal abgesehen von dem Zeitbedarf.
Die vorletzte Spalte zeigt die CPU-Ticks für Deine Version, die letzte Spalte die Ticks für die obige Version.

Code:
                                                              CPU-Ticks
  Zahl               XRound             YRound             XRound YRound
  ----------------    ------              ------              -----  ------
  115.859997410327    115.86              115.86              7772    1096
  100.99              100.99              100.99              7440     924
  100                 100                 100                 1392    1040
  101                 101                 101                 1396     916
  109                 109                 109                 1520     908
  100.4               100.4               100.4               5588     916
  100                 100                 100                 1560     916
  126.61999511719     126.62              126.62              7360    1064
    0.0099999997765     0.01                0.01              4496    1036
  115.089999999776    115.09              115.09              5692    1016
   99.99999997765     100                 100                 6892    1012
   99.99999997765     100                 100                 6536    1052
    0.99999997765       1                   1                 6488     932
   99.09999997765      99.1                99.1               5248     956
   99.0199999997765    99.02               99.02              4160     920
   99.0100001245678    99.01               99.01              3976    1008
  115.860000610352    115.86              115.86              6516     920
  115.859997410327    115.86              115.86              6424     964
  115.859974103278    115.86              115.86              6148     984
  115.85074103278     115.85074103278     115.85074103278     7056    1004
    0.00999999977648    0.01                0.01              4500    1084
  115.790000915527    115.79              115.79              6800     920
Ich habe nicht ausführlich getestet - würde ich vor Verwendung dringend empfehlen.

rory 24. Jun 2006 15:58

Re: runden von 0.0099999997765
 
Kurze Antwort, hochgradig inkompetent:

Die >2 Nachkommastellen lasse ich von Excel *mit Excel* runden.

Alles Andere führt zu Reklamationen bei den Kunden.

mfg

stoxx 24. Jun 2006 21:11

Re: runden von 0.0099999997765
 
Zitat:

Hallo Stoxx,
ich hab mir mal ein paar Gedanken dazu gemacht, und das Resultat ist weiter unten.
... ach Du grüne Neune ... bist Du wahnsinnig ? :duck:

Du solltest doch keine Diplomarbeit draus machen :gruebel:
Was soll ich da sagen, vielen Dank !!!
Aber warum soll meine Funktion nicht mit negativen Zahlen arbeiten ? hab ich was übersehen, funktioniert doch ?!
Dummweise reichen meine ASM Kenntnisse nicht aus, und ich kann die Funktion nur blind übernehmen. Testen ist da so eine Sache.
Vielen Dank !

Amateurprofi 24. Jun 2006 21:29

Re: runden von 0.0099999997765
 
Zitat:

Zitat von rory
Kurze Antwort, hochgradig inkompetent:

Die >2 Nachkommastellen lasse ich von Excel *mit Excel* runden.

Alles Andere führt zu Reklamationen bei den Kunden.

mfg

@Rory
Das Schlimme an manchen Menschen ist, daß sie so viele Dinge wissen, die so nicht sind.
Vielleicht solltest Du Dir den ganzen Thread mal richtig durchlesen. Eventuell verstehst Du dann, warum Stoxx seine Rundungsfunktion geschrieben hat - und warum ich das Ganze etwas schneller gemacht habe.

Mal nebenbei : Mit welchem Zahlenformat willst Du denn eine Rundung, wie Stoxx sie benötigt, erreichen ?

bit4bit 25. Jun 2006 14:37

Re: runden von 0.0099999997765
 
Hallo allerseits,

@Amateurprofi

Du zitierst stoxx mit "Um nach einem Hin und Herwandeln ..."
Ich kann das nirgendwo in diesem Thread finden, wo hast Du das her ?

@stoxx

Frage zu Deiner Broker API:
Erhältst Du zu jedem Kurswert immer auch die minimale Schrittweite oder kannst Du die sonstwie zuverlässig bestimmen bevor Du mit den Kurswerten irgendwelche Berechnungen durchführst?
Sind die Anzahl der Nachkommastellen bei allen Werten unbekannt, oder nur die der minimalen Schrittweiten?

stoxx 25. Jun 2006 14:55

Re: runden von 0.0099999997765
 
Hallo Bit4bit, das hin und herwandeln bezog sich auf diesen Thread: (sind übrigens zwei ganz verschiedenen Anwendungsfälle)

http://www.delphipraxis.net/internal...077&highlight=

Zitat:

Frage zu Deiner Broker API:
Erhältst Du zu jedem Kurswert immer auch die minimale Schrittweite oder kannst Du die sonstwie zuverlässig bestimmen bevor Du mit den Kurswerten irgendwelche Berechnungen durchführst?
Sind die Anzahl der Nachkommastellen bei allen Werten unbekannt, oder nur die der minimalen Schrittweiten?
man bekommt die minimale Schrittweite. z.B. 0,5 Schritte. Dieser Wert wird als Single übermittelt.
0,4999999878 steht dann drin.
Aus der größe Zahl könnte man sicherlich bestimmen, dass eine Nachkommastelle sinnvoll wäre.
Ich brauche aber genau die 0,5 um zum beispiel einen Wert von 5300,398978 auf 5300,5 runden zu können. Und eben nicht auf 5300,4 (was mit dem Wissen möglich gewesen wäre, dass man auf eine Nachkommastelle runden möchte)
Wenn man dann einen Trade an die Börse schickt wird die Order mit einem Preis von 5300,4 abgewiesen- kein gültiger Preis.
Somit brauchte ich eine Funktion, die mir aus 0,4999987 -> 0,5 herstellt.
(wenn man mit 0,49999 anfängt zu runden ergibt das ganz unschöne Zahlen ;-)

falls noch weiteres Interesse an der besagten API besteht ;-)

http://www.interactivebrokers.de

stoxx 25. Jun 2006 14:57

Re: runden von 0.0099999997765
 
gelöscht

bit4bit 25. Jun 2006 16:16

Re: runden von 0.0099999997765
 
Hi stoxx , hab noch zwei Fragen:

Ist die Anzahl der Nachkommastellen für jeden Kurswert bekannt? (Ich vermute mal : Ja)

Welche Werte können denn überhaupt bei den minimalen Schrittweiten vorkommen, gibt es dort
vielleicht eine Bildungsregel?

Welche API benutzt Du denn, hab nix für Delphi gefunden ?

stoxx 25. Jun 2006 16:39

Re: runden von 0.0099999997765
 
Hi Bit, was hast Du denn vor, dass Du so genau fragst ?

Zitat:

> Ist die Anzahl der Nachkommastellen für jeden Kurswert bekannt? (Ich vermute mal : Ja)
nein, keine Nachkommastellen, die minimalen Preismovements. Bei einer normalen Aktie wäre das z.b. 0,01 (wie auch die Preise im Supermarkt ;-)
Zitat:

> Welche Werte können denn überhaupt bei den minimalen Schrittweiten vorkommen?
es gibt minimale Preismovements von z.B. 1, aber auch 0,25 oder 0,5 oder 0,000001 oder andere

Zitat:

Welche API benutzt Du denn, hab nix für Delphi gefunden ?
ich benutze nun zufällig einen den Quelltext von jemanden, der sich die Arbeit gemacht hat, die mitgelieferten Sourcen der API in C++ nach Delphi zu portieren.
Üblicherweise würde man aber die völlig gleichwertige ActiveX Schnittstelle benutzen.
Hast Du schonmal mit ActiveX gearbeitet ? Die API musst Du einfach als Typbibliothek importieren und danach steht Dir die API im Objectinspektor zur Verfügung.

vlg stoxx

bit4bit 26. Jun 2006 03:18

Re: runden von 0.0099999997765
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi stoxx, ich hab nix vor, finde Dein Problem aber interessant!

Wenn man im Dezimalsystem abrunden will, teilt man einfach durch die entsprechende Zehnerpotenz und entfernt alle Nachkommastellen.

Wenn man aufrunden will, addiert man vorher einen Wert von 9/10 der Zehnerpotenz.

Das kaufmännische Runden unterscheidet sich hier nur dadurch, dass vor der Division nicht 9/10 sondern die Hälfte der Zehnerpotenz addiert wird.

Möchte man für die Rundung jetzt Schrittweiten (S) wie 0,25 oder 0,5 verwenden, so formt man die zu rundende Zahl vor der Rundung durch eine Division durch S um, so dass wieder mit Schrittweiten von 10 gearbeitet werden kann. Dann rundet man wie oben beschrieben (Aufrunden, Abrunden oder kaufmännisch runden) und macht die Umformung durch die Multiplikation mit S wieder rückgängig.

Kommen Werte aus der API, so können sie mit dieser Methode erst einmal "in Form" gebracht werden. Dabei ist es natürlich notwendig, dass die minimale Schrittweite bekannt ist.

Für die Übergabe an die API müssen die Werte dann eventuell wieder durch 1.000.000 geteilt werden.


Jetzt endlich zu Deinem ursprünglichen Problem:

Ich denke mal, dass Du als minimale Schrittweite jeweils eine Zahl übergeben bekommst, die eigentlich nicht kleiner sein kann als 0,000001 und wohl nicht mehr als 4 signifikante Ziffern enthält, oder ?
Die Darstellungsgenauigkeit einer Single müsste also eigentlich vollkommen ausreichend sein um diese Zahl darstellen zu können.

Wenn eine solche Zahl in ihrer dezimalen Darstellung eine Reihe von Neunen enthält heißt das, dass die interne (binäre Darstellung) knapp unterhalb des tatsächlichen (korrekten) Wertes liegt.

Entscheidend für die Korrektur dieser Zahl auf einen "vernünftigen" Wert ist folgende Überlegung:

Die Stellen vor dem Komma benötigen zu ihrer binären Darstellung eine bestimmte Anzahl von Bits. Zahlen zwischen 64 und 127 belegen zum Beispiel alle 7 Bits, die Zahlen zwischen 128 und 255 benötigen 8 Bits usw. Diese Bits gehen für die Darstellung des Dezimalbruches verloren.

Da eine Single Floating Point Variable in Delphi für die interne Darstellung der Mantisse 23 Bit benutzt, kann man sich die Anzahl der für den Binärbruch verfügbaren Bits jeweils ausrechnen.

Nur Zahlen die kleiner als 8 sind können hier noch mit 6 Nachkommastellen dargestellt werden.
Für 4 Nachkommastellen muss die Zahl kleiner als 512 sein. Zahlen kleiner als 65536 erlauben noch 2 Nachkommastellen.

Man kann jetzt in Abhängigkeit der Größe einer Zahl entscheiden ob auf 2, 4 oder 6 Nachkomma-stellen aufgerundet werden muss. Dafür sind nur wenige Vergleiche notwendig.

Alle anderen Werte, die Du durch die API übergeben bekommst, dürften dann also Kurswerte oder so was darstellen. Hier kennst Du ja die minimale Schrittweite und kannst die Werte wie oben beschrieben anpassen.

Hab das ganze mal mit einigen Beispielen versehen und noch ausführlicher beschrieben als Anhang beigefügt.

stoxx 4. Jul 2006 18:18

Re: runden von 0.0099999997765
 
Hallo bit4bit,

ich habe Deinen Beitrag erst jetzt irgendwie gelesen, Du hast Dir viele Gedanken darum gemacht.

Zitat:

Man kann jetzt in Abhängigkeit der Größe einer Zahl entscheiden ob auf 2, 4 oder 6 Nachkomma-stellen aufgerundet werden muss. Dafür sind nur wenige Vergleiche notwendig
so ähnlich gehe ich ja bei codieren meiner Zahlen vor. Du darfst das Problem des Rundens nicht vermischen mit dem Problem der Speicherung der Zahlen.

Wenn Du Dir den folgenden Beitrag anschaust:

http://www.delphipraxis.net/internal...077&highlight=

die Zahlen werden in 32 Bit in "meinem" speziellen Format abgespeichert ( in einem Integer) , bieten aber dennoch mehr Anzahl signifikanter Stellen, als ein Single Wert, weil ich für die Kodierung des "Exponenten" nicht 8 Bit wie bei einem Single Wert, sondern nur 2 Bit verwende.

0XX0 0000 0000 0000 0000 0000 0000 0011

in XX ( Bit 29 und Bit 30) werden die Nachkommstellen codiert. 4 Zustände sind mit 2 Bits möglich. ich benutze entweder 2, 4 oder 6 Nachkommastellen. Eine Zahl wie 0,5 * 10^-12 abzuspeichern ist dann natürlich nicht möglich, aber eben auch nicht nötig für meine Zwecke !

Zitat:

Übrigens, die Benutzung von 64 Bit Integer Variablen dürfte für Deine Zwecke wohl das Beste darstellen.
ja eben nicht, da ich Platz sparen muss und ich deswegen die Zahlen auf 32 Bit "komprimiert" habe.

Was natürlich richtig ist, (weiß nicht, ob Du das gemeint hast) .. man könnte die minimale Preisdifferenz, wenn man sie erhält, zum Beispiel 0,249999999 selbst versuchen erstmal auf 0,25 zu runden (Anhand der Größe an sich dieser Zahl). Um dann damit wieder den eigentlichen Preis zu runden.
Für alle Fälle wo ich aber wiederum nur die Zahl an sich kenne ( nicht die 0,25 ) macht sich das Verfahren sehr gut.

warum machst Du das runden so kompliziert und addierst erst noch 9/10 ?
round(wert / 0.25) * 0.25 bringt die mir gewünschten Ergebnisse.

Gruß stoxx

bit4bit 9. Jul 2006 03:08

Re: runden von 0.0099999997765
 
Hi Stoxx,

hab leider jetzt erst Zeit gehabt!

Zitat:

... da ich Platz sparen muss und ich deswegen die Zahlen auf 32 Bit "komprimiert" habe.
Dass Du Probleme mit dem Platz hattest hab ich nicht realisiert. Dein Spezialformat ist da schon genau das Richtige für die externe Speicherung.

Beim Umwandeln in Double für die internen Berechnungen kriegst Du dann aber wieder Binärbrüche mit den entsprechenden Rundungsfehlern weil der Exponent beim Floating Point Format eben die Basis 2 hat und nicht die Basis 10 (bzw. 100) wie bei Deinem Spezialformat.

Deswegen meine ich nach wie vor, dass es besser wäre, wenn du wenigstens bei allen internen Berechnungen nur ganze Zahlen benutzt, egal ob als Double oder als Int64. In Deiner Funktion SmallToDouble musst Du dafür einfach nur die Faktoren entsprechend ändern, ändert nix an der Laufzeit.

Die kleinste darstellbare Einheit wäre dann eben ein 10.000stel Cent (entspricht 6 Dezimalstellen) und Du könntest alle weiteren Probleme mit Rundungsfehlern und Hin- und Zurückwandeln vergessen! Bei Multiplikationen bzw. Divisionen mußt du dann, wie schon früher gesagt, die Anzahl der gedachten Nachkommastellen "zu Fuß" nachhalten. Ist aber trivial.

Die Int64 Darstellung hätte den Vorteil, dass alle Berechnungen sehr schnell gehen und außerdem sind die darstellbaren Beträge größer, weil ja der Platz für den Exponenten nicht benötigt wird.

Zitat:

... Was natürlich richtig ist, (weiß nicht, ob Du das gemeint hast) .. man könnte die minimale Preisdifferenz, wenn man sie erhält, zum Beispiel 0,249999999 selbst versuchen erstmal auf 0,25 zu runden (Anhand der Größe an sich dieser Zahl). Um dann damit wieder den eigentlichen Preis zu runden.
Hab ich so gemeint. Geht sauschnell und ist immer (!) korrekt. Die Preise, die Du über die API erhältst kannst Du damit dann exakt darstellen.

Zitat:

... Für alle Fälle wo ich aber wiederum nur die Zahl an sich kenne ( nicht die 0,25 ) macht sich das Verfahren sehr gut.
Also "die Zahl an sich" , wenn sie als ganze Zahl dargestellt wird (siehe oben), hat niemals Darstellungsfehler und muss daher auch nie korrigiert werden.

Wenn Du aber eine Floating Point Zahl hast, die Bruchteile (also Binärbrüche) enthält, ist mein Verfahren zur Korrektur genau so anwendbar!
Für jede benutzte Mantissenlänge in Bit (Single, Real48, Double, Extended) gibt es eindeutige Grenzwerte für die jeweils möglichen dezimalen Nachkommastellen, nach dem schlichten Motto: "Je mehr Bits vor dem Komma stehen, um so weniger Bits stehen für den Binärbruch zur Verfügung".

Dein Verfahren mit der Untersuchung eines Strings ist zwar sehr trickreich und wird auch mit der schnellen Assembler-Routine von Amateurprofi für Dich brauchbar sein, hat aber zwei Nachteile:

1. Das von mir dargestellte Verfahren zur Korrektur von Darstellungsfehlern auf Grund von Binärbrüchen mit endlicher Genauigkeit dürfte um einige Größenordnungen schneller sein, selbst wenn es nicht in Assembler kodiert wird.
High speed Idee: Der Exponent der Floating Point Variablen (Bei Single = 8 Bit) kann als Index für einen Array mit der jeweiligen Anzahl von darstellbaren Nachkommastellen verwendet werden.

2. Ich fürchte, dass sich nicht beweisen lässt, dass Dein Verfahren in jedem Fall ein richtiges Ergebnis erzeugt, oder?

Zitat:

... warum machst Du das runden so kompliziert und addierst erst noch 9/10 ?
round(wert / 0.25) * 0.25 bringt die mir gewünschten Ergebnisse.
Also die 9/10 addiere ich nur dann, wenn ich Aufrunden will,
wenn ich abrunden will addiere ich nix,
und wenn ich kaufmännisch runden will addiere ich 5/10 ( Vulgo die Hälfte )
danach schneide ich die nicht benötigten Stellen ab !
Bei Integer geht das mit einem "div" Operator, simpler geht’s nicht.
Bei Floating Point nimmst Du dann eben die Funktion Int() in Verbindung mit dem "/" Operator

Die Funktion Round rundet aber weder auf noch ab noch kaufmännisch, sondern macht ein Bankers Rounding, eine Abart des kaufmännischen Rundens. Wird für Dich auch in den meisten Fällen funktionieren .... aber:

Wenn Du das hier mal aufmerksam liest ...

http://www.dsdt.info/tipps/?id=624
http://www.delphipraxis.net/internal_redirect.php?t=35275
http://www.delphipraxis.net/internal_redirect.php?t=41935
http://www.witwib.com/de:Rundung
http://www.merlyn.demon.co.uk/pas-chop.htm

... verstehst Du bestimmt warum ich doch lieber die Kontrolle selbst behalte und reproduzierbare Ergebnisse der Benutzung der Round Funktion vorziehe.

Wünsch' einen schönen Sonntag!


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:25 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