![]() |
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 //============================================================================== |
Re: runden von 0.0099999997765
Zitat:
|
Re: runden von 0.0099999997765
Zitat:
|
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). |
Re: runden von 0.0099999997765
Zitat:
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 |
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... |
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:
Die Funktion sorgt nur dafür, das die Zahl ohne Rest durch die Granularität teilbar ist.
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 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: |
Re: runden von 0.0099999997765
Zitat:
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 :-) |
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: |
Re: runden von 0.0099999997765
Zitat:
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:
Ich habe nicht ausführlich getestet - würde ich vor Verwendung dringend empfehlen.
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 |
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:18 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 by Thomas Breitkreuz