AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

runden von 0.0099999997765

Ein Thema von stoxx · begonnen am 7. Jun 2006 · letzter Beitrag vom 9. Jul 2006
Antwort Antwort
Seite 1 von 3  1 23      
Benutzerbild von stoxx
stoxx

Registriert seit: 13. Aug 2003
1.111 Beiträge
 
#1

runden von 0.0099999997765

  Alt 7. Jun 2006, 16:34
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

//==============================================================================
Phantasie ist etwas, was sich manche Leute gar nicht vorstellen können.
  Mit Zitat antworten Zitat
Benutzerbild von Martin K
Martin K

Registriert seit: 20. Okt 2005
919 Beiträge
 
Turbo Delphi für Win32
 
#2

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 16:37
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?
Mit den Menschen ist es wie mit Computern -
es gibt Nullen und Einsen.
  Mit Zitat antworten Zitat
Benutzerbild von stoxx
stoxx

Registriert seit: 13. Aug 2003
1.111 Beiträge
 
#3

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 16:46
Zitat:
Hast du dir schonmal RoundTo in der Delphi-Hilfe angeschaut?
hmm .. hast Du Dir mein Posting überhaupt durchgelesen ?
Phantasie ist etwas, was sich manche Leute gar nicht vorstellen können.
  Mit Zitat antworten Zitat
Benutzerbild von Martin K
Martin K

Registriert seit: 20. Okt 2005
919 Beiträge
 
Turbo Delphi für Win32
 
#4

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 16:51
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).
Mit den Menschen ist es wie mit Computern -
es gibt Nullen und Einsen.
  Mit Zitat antworten Zitat
Benutzerbild von stoxx
stoxx

Registriert seit: 13. Aug 2003
1.111 Beiträge
 
#5

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 16:59
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
Phantasie ist etwas, was sich manche Leute gar nicht vorstellen können.
  Mit Zitat antworten Zitat
Benutzerbild von Martin K
Martin K

Registriert seit: 20. Okt 2005
919 Beiträge
 
Turbo Delphi für Win32
 
#6

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 17:03
Achso, jetzt verstehe ich auch das Problem erst
Du willst also nicht, dass 00 oder 99 hinterm Komma vorkommt bzw. wenn es vorkommt soll an dieser Stelle gerundet werden...
Mit den Menschen ist es wie mit Computern -
es gibt Nullen und Einsen.
  Mit Zitat antworten Zitat
alzaimar
(Moderator)

Registriert seit: 6. Mai 2005
Ort: Berlin
4.956 Beiträge
 
Delphi 2007 Enterprise
 
#7

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 18:28
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.

"Wenn ist das Nunstruck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput!"
(Monty Python "Joke Warefare")
  Mit Zitat antworten Zitat
Benutzerbild von stoxx
stoxx

Registriert seit: 13. Aug 2003
1.111 Beiträge
 
#8

Re: runden von 0.0099999997765

  Alt 7. Jun 2006, 19:31
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
Phantasie ist etwas, was sich manche Leute gar nicht vorstellen können.
  Mit Zitat antworten Zitat
alzaimar
(Moderator)

Registriert seit: 6. Mai 2005
Ort: Berlin
4.956 Beiträge
 
Delphi 2007 Enterprise
 
#9

Re: runden von 0.0099999997765

  Alt 8. Jun 2006, 19:21
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.
"Wenn ist das Nunstruck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput!"
(Monty Python "Joke Warefare")
  Mit Zitat antworten Zitat
Amateurprofi

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

Re: runden von 0.0099999997765

  Alt 24. Jun 2006, 15: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
Antwort Antwort
Seite 1 von 3  1 23      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:05 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz