Einzelnen Beitrag anzeigen

Amateurprofi

Registriert seit: 17. Nov 2005
Ort: Hamburg
1.062 Beiträge
 
Delphi XE2 Professional
 
#10

Re: runden von 0.0099999997765

  Alt 24. Jun 2006, 16:35
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.
Gruß, Klaus
Die Titanic wurde von Profis gebaut,
die Arche Noah von einem Amateur.
... Und dieser Beitrag vom Amateurprofi....
  Mit Zitat antworten Zitat