Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Delphi & Runden (https://www.delphipraxis.net/140290-delphi-runden.html)

brechi 15. Sep 2009 14:35


Delphi & Runden
 
Hallo,
1) Wie kann man in Delphi abstellen, dass bei 2.5 (-> 2) abgerunden wird und bei 3.5 (-> 4) aufgerundet wird?
Bisher mache ich es über eine extra Funktion:

Delphi-Quellcode:
function Round(e: Extended): Extended;
begin
  Result := Trunc(e);
  if Frac(e) >= 0.5 then
    Result := Result + 1;
end;
bzw.

math.SimpleRoundTo(Wert, 0)

kann man den RoundMode nicht auf den von TurboPascal umstellen?

Das 2. Problem tritt dann bei Tabellen auf,
ich habe eine TTable mit einem 5/1 BCD Feld.

Diesem weise ich dem Wert 2.25 zu. Da das Feld nur eine Nachkommastelle hat wird gerunden. Auch hier wird auf 2.2 abgerundet, ich hätte aber gerne 2.3. Das Delphi Format ('%5.1f') liefert wie gewünscht auch den Wert 2.3.
Natürlich könnte ich beim zuweisen ebenfalls selbst runden, dafür sind mit aber zu viele Änderungen nötigt. Wie kann ich dem Delphi ROUND und dem interen ROUND einer TTABLE also immer sagen, dass bei 0.5 aufgerundet wird?

Forlan 15. Sep 2009 14:38

Re: Delphi & Runden
 
Math.Ceil fürs Aufrunden und Math.Floor fürs Abrunden.

himitsu 15. Sep 2009 14:38

Re: Delphi & Runden
 
[equote="die OH sagt bei 'Round'"]Anmerkung: Die Funktionsweise von Round kann mit der Prozedur Set8087CW oder der Funktion SetRoundMode gesteuert werden. [/equote]

PS: dieses Verhalten nennt sich Kaufmännisches Runden

brechi 15. Sep 2009 14:44

Re: Delphi & Runden
 
@Forlan: Ist mir schon klar, aber ich willst nicht händich machen.
@Himitsu, Sowohl das kaufmännische Runden als auch die SetRoundMode Funktion waren mir bekannt.
Jeder der 4 Modi (rmNearest, rmDown, rmUp, rmTruncate) liefert mir mit Round(2.5) jedoch eine 2 zurück.

Nach Set8087CW google ich mal.

divBy0 15. Sep 2009 14:56

Re: Delphi & Runden
 
In der Hilfe steht aber das es so ist.

Zitat:

Zitat von OH
In Delphi rundet Round einen Wert des Typs Real auf einen Integerwert.

X ist ein Ausdruck des Typs Real. Round gibt einen Int64-Wert mit dem auf die nächste Ganzzahl gerundeten Wert von X zurück. Liegt X genau in der Mitte zwischen zwei ganzen Zahlen, wird immer die gerade Zahl zurückgeliefert. Dieses Vorgehen beim Runden wird auch als "Banker's Rounding" bezeichnet.

Liegt der gerundete Wert von X außerhalb des Int64-Wertebereichs, wird ein Laufzeitfehler ausgelöst, der mit einer EInvalidOp-Exception behandelt werden kann.

Anmerkung: Die Funktionsweise von Round kann mit der Prozedur Set8087CW oder der Funktion SetRoundMode gesteuert werden.

Gewusst hab' ich das bis eben aber auch nicht... :-D

mleyen 15. Sep 2009 15:03

Re: Delphi & Runden
 
Hmmm, also bleibt alles wie es ist, und ich runde weiterhin mit:
Delphi-Quellcode:
Floor(Val + 0.5)
Oder hab ich jetzt etwas übersehen und man kann es doch umstellen? :gruebel:

brechi 15. Sep 2009 15:12

Re: Delphi & Runden
 
Korrektur:

Die Delphi Funktion ROUND rundet immer mit rmNearest.
Die Delphi Funktion math.RoundTo rundet mit dem RoundMode.

-> Trunc(RoundTo(2.5,0)) liefert:
rmNearest: 2
rmDown: 2
rmUp: 3
rmTruncate: 2

wobei rmNearest eben Bankers Round ist.

Das Problem ist jetzt: Format benutzt immer den wissenschaftlichen modus bei Format('%5.0f',[2.5]) kommt 3 raus. Bei der Zuweisung eines Variants in einer Tabelle wird aber der Bankers Round-Mode verwendet (und liefert 2) bei BCD 5.1.

Ich will aber jetzt IMMER, sowohl bei Format als auch in TTable (und am liebsten auch in Round) den Wert 3 rausbekommen.

himitsu 15. Sep 2009 15:31

Re: Delphi & Runden
 
Round + SetRoundMode(rmNearest) = normal (Round)
Round + SetRoundMode(rmDown) = Floor
Round + SetRoundMode(rmUp) = Ceil
Round + SetRoundMode(rmTruncate) = Trunc
[edit]
OK, dann RoundTo ... dachte das zählt auch für Round (hatte es aber auch noch nie verwendet oder getestet)


für Set8087CW schau dich mal nach dem ASM-Befehl FLDCW um, hab jetzt auch keine direkte Beschreibung der Bitmaske gefunden.

brechi 15. Sep 2009 16:04

Re: Delphi & Runden
 
So wie ich es bisher rausgefunden habe, wird immer Bankers-Round benutzt und hab ich habe keine Möglichkeit gefunden das jetzt direkt in der FPU auf kaufmännisches Runden umzustellen.
Demnach muss ich mir wohl doch eine Funktion schreiben die dem Feld der Tabelle mit Hilfe der Felddef die richtige Größe zuweist.

Falls jemand eine andere Lösung findet, bitte posten :)

himitsu 15. Sep 2009 16:11

Re: Delphi & Runden
 
Ich dachte mal gehört zu haben, daß man dieses Verhalten wie hier gewünscht ändern kann :gruebel:

http://www.website.masmforum.com/tut...hap1.htm#cword
kann aber auch nichts finden

Blup 15. Sep 2009 16:16

Re: Delphi & Runden
 
Du könntest zur Laufzeit an der Adresse der Round-Funktion einen Sprung in deine eigene Round-Funktion patchen.

brechi 16. Sep 2009 07:55

Re: Delphi & Runden
 
@Blup: ist mir dafür zu aufwändig.

Hier nochmal der Code falls jemand bisl testen will:

Delphi-Quellcode:
program RoundTest;

{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  db,
  dbtables;

var
  Table: TTable;
  DbaseFile: string;
  Temp: array[0..255] of char;

  Def: TFieldDef;

  Values: array[0..7] of Double = (2.5, 3.5, 2.2, 2.8, 2.25, 2.35, 2.22, 2.28);
  ValuesWanted: array[0..7] of Double = (3, 4, 2, 3, 2.3, 2.4, 2.2, 2.3);
  i: Integer;
begin
  if GetTempPathA(SizeOf(Temp), Temp) > 0 then begin
    DbaseFile := IncludeTrailingPathDelimiter(Temp) + 'test1234.dbf';
    Table := TTable.Create(nil);
    Table.TableType := ttDBase;
    Table.TableLevel := 4;
    Table.TableName := ExtractFileName(dBaseFile);
    Table.DatabaseName := ExtractFilePath(DbaseFile);

    Def := Table.FieldDefs.AddFieldDef;
    Def.Name := 'Feld';
    Def.DataType := ftBCD;
    Def.Precision := 5; // Delphi vertauscht Precision und Size
    Def.Size := 1;

    Table.CreateTable;
    Table.Open;
    for i := Low(Values) to High(Values) do begin
      Table.Append;
      if i < 4 then
        Table['Feld'] := Round(Values[i])
      else
        Table['Feld'] := Values[i];
      Table.Post;
    end;

    Table.First;

    for i := Low(Values) to High(Values) do begin
      if i < 4 then
        Writeln(Format('Format: %0.0f', [Values[i]]), ' Round: ', Table['Feld'], ' wanted: ', FloatToStr(ValuesWanted[i]))
      else
        Writeln(Format('Format: %0.1f', [Values[i]]), ' Table: ', Table['Feld'], ' wanted: ', FloatToStr(ValuesWanted[i]));

      Table.Next;
    end;
    Table.Close;
    Table.Free;

    ReadLn;
  end;
end.
Schade, dass intern bei Variant konvertierung nicht Format verwendet wird, das liefert immer das "richtige" Ergebnis.

Toll ist übrigens auch, dass bei der Verwendung von z.B. rmUpals RoundMode in Delphi bei der Optimierung nicht verwendet wird.

Delphi-Quellcode:
SetRoundMode(rmUp)
Caption := IntToStr(Round(2.5));
liefert eine 2, da durch die Optimierung die Konstante schin intern auf 2, mit dem rmNearest Verfarhen gerundet wird...

brechi 16. Sep 2009 08:17

Re: Delphi & Runden
 
Zusatzinfo:

RoundTo und SimpleRoundTo liefern auch sehr komishce Werte:

SimpleRoundTo(3.5,0) liefert 4 (wie gewünscht, rundet also auf)
SimpleRoundTo(3.35,-1) liefert 3.3 (rundet ab)

brechi 16. Sep 2009 10:32

Re: Delphi & Runden
 
Meine Lösung:

Delphi-Quellcode:

// uses BDE, Math;

// auf Nachkommastellen runden
function RoundNumber(_Value: Extended;
  _Prec: Integer): Extended;
begin
  Result := Trunc(Abs(_Value) * Power(10, _Prec) + 0.5) / Power(10, _Prec) * Sign(_Value);
end;

// Feld in Tabelle setzen
procedure SetNumber(_Table: TTable; _Field: string;
  _Number: Extended);
begin
  _Table[_Field] := RoundNumber(_Number, GetPrec(_Table, _Field));
end;

// Feld aus Tabelle auslesen
function GetNumber(_Table: TTable;
  _Field: string): Extended;
begin
  Result := RoundNumber(_Table[_Field], GetPrec(_Table, _Field));
end;

// da Table.FieldDef.Size und Precision nicht die Nachkommastellen liefert (unoptimiert):

function GetPrec(_Table: TTable; _Field: string): Integer;
var
  FldsArr: array of FLDDesc;
  i: Integer;
begin
  Result := 0;
  SetLength(FldsArr, _Table.FieldCount + 1);
  Check(DbiGetFieldDescs(_Table.handle, @FldsArr[0]));
  for i := Low(FldsArr) to High(FldsArr) do
    if SameText(FldsArr[i].szName, _Field) then
      Result := FldsArr[i].iUnits2;
end;

stho 16. Sep 2009 10:47

Re: Delphi & Runden
 
Ist vielleicht nicht die beste Methode, aber immerhin wurde sie noch nicht genannt (außer ich habs net gesehen):

Delphi-Quellcode:
Ergebniss := Zahl1 div Zahl2;
Ergebniss muss aber Integer sein.

Verbessert mich wenn ich falsch liege ;-) Arbeite erst seit 7-8 Wochen mit Delphi

DeddyH 16. Sep 2009 10:49

Re: Delphi & Runden
 
Dann würde ja immer abgerundet ;)

P.S.: Willkommen in der DP :dp:

himitsu 16. Sep 2009 11:30

Re: Delphi & Runden
 
Delphi-Quellcode:
gerundetesErgebniss := (Zahl1 + (Zahl2 div 2)) div Zahl2;
Aber das Löst ja nicht das Problem hier ... vorallem nicht vei den Variants, wo intern gerundet wird.

Eventuell könnte man da die internen Funktionen der Variants hooken und dort seine eigene Roundmethode einschleusen.


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