Registriert seit: 17. Nov 2005
Ort: Hamburg
1.062 Beiträge
Delphi XE2 Professional
|
Re: runden von 0.0099999997765
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....
|